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

Compare commits

..

1 Commits

Author SHA1 Message Date
Yury Semikhatsky 64115bfb60 devops: set version 1.22.0, driver to 1.22.0 (#929) 2022-05-13 15:31:22 -07:00
107 changed files with 388 additions and 4963 deletions
+2 -4
View File
@@ -1,5 +1,3 @@
# text files must be lf for golden file tests to work
* text=auto eol=lf
# make project show as TS on GitHub
*.js linguist-detectable=false
*.txt eol=lf
*.json eol=lf
+12 -3
View File
@@ -31,14 +31,18 @@ jobs:
- name: Build & Install
run: mvn -B install -D skipTests --no-transfer-progress
- name: Run tests
run: mvn test --no-transfer-progress --fail-at-end
run: mvn test -DexcludedGroups=isolated --no-transfer-progress --fail-at-end
env:
BROWSER: ${{ matrix.browser }}
- name: Run tracing tests w/ sources
run: mvn test --no-transfer-progress --fail-at-end -D test=*TestTracing*
run: mvn test -DexcludedGroups=isolated --no-transfer-progress --fail-at-end -D test=*TestTracing*
env:
BROWSER: ${{ matrix.browser }}
PLAYWRIGHT_JAVA_SRC: src/test/java
- name: Run driver throw tests
run: mvn test -Dgroups=driverThrowTest --no-transfer-progress --fail-at-end
env:
BROWSER: ${{ matrix.browser }}
- name: Test Spring Boot Starter
shell: bash
env:
@@ -79,7 +83,12 @@ jobs:
- name: Build & Install
run: mvn -B install -D skipTests --no-transfer-progress
- name: Run tests
run: mvn test --no-transfer-progress --fail-at-end
run: mvn test -DexcludedGroups=isolated --no-transfer-progress --fail-at-end
env:
BROWSER: chromium
BROWSER_CHANNEL: ${{ matrix.browser-channel }}
- name: Run driver throw tests
run: mvn test -Dgroups=driverThrowTest --no-transfer-progress --fail-at-end
env:
BROWSER: chromium
BROWSER_CHANNEL: ${{ matrix.browser-channel }}
+4 -4
View File
@@ -11,9 +11,9 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom
| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->105.0.5195.19<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->16.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->103.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Chromium <!-- GEN:chromium-version -->102.0.5005.40<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->15.4<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->99.0.1<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
Headless execution is supported for all the browsers on all platforms. Check out [system requirements](https://playwright.dev/java/docs/next/intro/#system-requirements) for details.
@@ -173,7 +173,7 @@ public class InterceptNetworkRequests {
## Documentation
Check out our official [documentation site](https://playwright.dev/java).
Check out our [new documentation site](https://playwright.dev/java)!.
You can also browse [javadoc online](https://www.javadoc.io/doc/com.microsoft.playwright/playwright/latest/index.html).
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.25.0</version>
<version>1.22.0</version>
</parent>
<artifactId>driver-bundle</artifactId>
@@ -14,9 +14,7 @@
* limitations under the License.
*/
package com.microsoft.playwright.impl.driver.jar;
import com.microsoft.playwright.impl.driver.Driver;
package com.microsoft.playwright.impl;
import java.io.IOException;
import java.net.URI;
@@ -28,7 +26,6 @@ import java.util.concurrent.TimeUnit;
public class DriverJar extends Driver {
private static final String PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD = "PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD";
private static final String SELENIUM_REMOTE_URL = "SELENIUM_REMOTE_URL";
private final Path driverTempDir;
public DriverJar() throws IOException {
@@ -60,10 +57,6 @@ public class DriverJar extends Driver {
System.out.println("Skipping browsers download because `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD` env variable is set");
return;
}
if (env.get(SELENIUM_REMOTE_URL) != null || System.getenv(SELENIUM_REMOTE_URL) != null) {
logMessage("Skipping browsers download because `SELENIUM_REMOTE_URL` env variable is set");
return;
}
Path driver = driverPath();
if (!Files.exists(driver)) {
throw new RuntimeException("Failed to find driver: " + driver);
@@ -165,7 +158,7 @@ public class DriverJar extends Driver {
}
@Override
protected Path driverDir() {
Path driverDir() {
return driverTempDir;
}
}
@@ -16,15 +16,11 @@
package com.microsoft.playwright;
import com.microsoft.playwright.impl.driver.Driver;
import com.microsoft.playwright.impl.driver.jar.DriverJar;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import com.microsoft.playwright.impl.Driver;
import com.microsoft.playwright.impl.DriverJar;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.io.TempDir;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.ServerSocket;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
@@ -32,26 +28,12 @@ import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class TestInstall {
private static boolean isPortAvailable(int port) {
try (ServerSocket ignored = new ServerSocket(port)) {
return true;
} catch (IOException ignored) {
return false;
}
}
private static int unusedPort() {
for (int i = 10000; i < 11000; i++) {
if (isPortAvailable(i)) {
return i;
}
}
throw new RuntimeException("Cannot find unused local port");
}
@BeforeEach
void clearSystemProperties() {
// Clear system property to ensure that the driver is loaded from jar.
@@ -62,29 +44,15 @@ public class TestInstall {
}
@Test
void shouldThrowWhenBrowserPathIsInvalid(@TempDir Path tmpDir) throws NoSuchFieldException, IllegalAccessException {
@Tags({@Tag("isolated"), @Tag("driverThrowTest")})
void shouldThrowWhenBrowserPathIsInvalid(@TempDir Path tmpDir) {
Map<String,String> env = new HashMap<>();
// On macOS we can only use 127.0.0.1, so pick unused port instead.
// https://superuser.com/questions/458875/how-do-you-get-loopback-addresses-other-than-127-0-0-1-to-work-on-os-x
env.put("PLAYWRIGHT_DOWNLOAD_HOST", "https://127.0.0.1:" + unusedPort());
env.put("PLAYWRIGHT_DOWNLOAD_HOST", "https://127.0.0.127");
// Make sure the browsers are not installed yet by pointing at an empty dir.
env.put("PLAYWRIGHT_BROWSERS_PATH", tmpDir.toString());
env.put("PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD", "false");
// Reset instance field value to null for the test.
Field field = Driver.class.getDeclaredField("instance");
field.setAccessible(true);
Object value = field.get(Driver.class);
field.set(Driver.class, null);
for (int i = 0; i < 2; i++){
RuntimeException exception = assertThrows(RuntimeException.class, () -> Driver.ensureDriverInstalled(env, true));
String message = exception.getMessage();
assertTrue(message.contains("Failed to create driver"), message);
}
field.set(Driver.class, value);
assertThrows(RuntimeException.class, () -> Driver.ensureDriverInstalled(env, true));
assertThrows(RuntimeException.class, () -> Driver.ensureDriverInstalled(env, true));
}
@Test
@@ -113,20 +81,12 @@ public class TestInstall {
}
@Test
void playwrightDriverAlternativeImpl() throws NoSuchFieldException, IllegalAccessException {
// Reset instance field value to null for the test.
Field field = Driver.class.getDeclaredField("instance");
field.setAccessible(true);
Object value = field.get(Driver.class);
field.set(Driver.class, null);
void playwrightDriverAlternativeImpl() {
System.setProperty("playwright.driver.impl", "com.microsoft.playwright.impl.AlternativeDriver");
RuntimeException thrown =
assertThrows(
RuntimeException.class,
() -> Driver.ensureDriverInstalled(Collections.emptyMap(), false));
assertEquals("Failed to create driver", thrown.getMessage());
field.set(Driver.class, value);
}
}
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.25.0</version>
<version>1.22.0</version>
</parent>
<artifactId>driver</artifactId>
@@ -14,13 +14,14 @@
* limitations under the License.
*/
package com.microsoft.playwright.impl.driver;
package com.microsoft.playwright.impl;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.ZonedDateTime;
import java.util.Map;
import static com.microsoft.playwright.impl.driver.DriverLogging.logWithTimestamp;
import static com.microsoft.playwright.impl.DriverLogging.logWithTimestamp;
/**
* This class provides access to playwright-cli. It can be either preinstalled
@@ -43,7 +44,7 @@ public abstract class Driver {
}
@Override
protected Path driverDir() {
Path driverDir() {
return driverDir;
}
}
@@ -99,12 +100,12 @@ public abstract class Driver {
}
String driverImpl =
System.getProperty("playwright.driver.impl", "com.microsoft.playwright.impl.driver.jar.DriverJar");
System.getProperty("playwright.driver.impl", "com.microsoft.playwright.impl.DriverJar");
Class<?> jarDriver = Class.forName(driverImpl);
return (Driver) jarDriver.getDeclaredConstructor().newInstance();
}
protected abstract Path driverDir();
abstract Path driverDir();
protected static void logMessage(String message) {
// This matches log format produced by the server.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.microsoft.playwright.impl.driver;
package com.microsoft.playwright.impl;
import java.time.ZoneId;
import java.time.ZonedDateTime;
+2 -2
View File
@@ -6,7 +6,7 @@
<groupId>org.example</groupId>
<artifactId>examples</artifactId>
<version>1.25.0</version>
<version>1.22.0</version>
<name>Playwright Client Examples</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -15,7 +15,7 @@
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>1.22.0</version>
<version>1.17.0</version>
</dependency>
</dependencies>
<build>
@@ -16,7 +16,6 @@
package org.example;
import java.nio.file.Paths;
import com.microsoft.playwright.*;
public class SelectorsAndKeyboardManipulation {
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.25.0</version>
<version>1.22.0</version>
</parent>
<artifactId>playwright</artifactId>
@@ -30,15 +30,15 @@ import java.nio.file.Path;
*
* <p> **Cookie management**
*
* <p> {@code APIRequestContext} returned by {@link BrowserContext#request BrowserContext.request()} and {@link Page#request
* <p> {@code APIRequestContext} retuned by {@link BrowserContext#request BrowserContext.request()} and {@link Page#request
* Page.request()} shares cookie storage with the corresponding {@code BrowserContext}. Each API request will have {@code Cookie}
* header populated with the values from the browser context. If the API response contains {@code Set-Cookie} header it will
* automatically update {@code BrowserContext} cookies and requests made from the page will pick them up. This means that if you
* log in using this API, your e2e test will be logged in and vice versa.
*
* <p> If you want API requests to not interfere with the browser cookies you should create a new {@code APIRequestContext} by
* calling {@link APIRequest#newContext APIRequest.newContext()}. Such {@code APIRequestContext} object will have its own
* isolated cookie storage.
* <p> If you want API requests to not interfere with the browser cookies you shoud create a new {@code APIRequestContext} by calling
* {@link APIRequest#newContext APIRequest.newContext()}. Such {@code APIRequestContext} object will have its own isolated cookie
* storage.
*/
public interface APIRequestContext {
class StorageStateOptions {
@@ -20,7 +20,6 @@ import com.microsoft.playwright.options.*;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Consumer;
import java.util.regex.Pattern;
/**
* A Browser is created via {@link BrowserType#launch BrowserType.launch()}. An example of using a {@code Browser} to create a
@@ -144,17 +143,6 @@ public interface Browser extends AutoCloseable {
* 'http://per-context' } })}.
*/
public Proxy proxy;
/**
* Optional setting to control resource content management. If {@code omit} is specified, content is not persisted. If {@code attach}
* is specified, resources are persistet as separate files and all of these files are archived along with the HAR file.
* Defaults to {@code embed}, which stores content inline the HAR file as per HAR specification.
*/
public HarContentPolicy recordHarContent;
/**
* When set to {@code minimal}, only record information necessary for routing from HAR. This omits sizes, timing, page, cookies,
* security and other types of HAR information that are not used when replaying from HAR. Defaults to {@code full}.
*/
public HarMode recordHarMode;
/**
* Optional setting to control whether to omit request content from the HAR. Defaults to {@code false}.
*/
@@ -165,7 +153,6 @@ public interface Browser extends AutoCloseable {
* BrowserContext.close()} for the HAR to be saved.
*/
public Path recordHarPath;
public Object recordHarUrlFilter;
/**
* Enables video recording for all pages into the specified directory. If not specified videos are not recorded. Make sure
* to call {@link BrowserContext#close BrowserContext.close()} for videos to be saved.
@@ -187,15 +174,6 @@ public interface Browser extends AutoCloseable {
* is set.
*/
public ScreenSize screenSize;
/**
* Whether to allow sites to register Service workers. Defaults to {@code "allow"}.
* <ul>
* <li> {@code "allow"}: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API">Service Workers</a> can be
* registered.</li>
* <li> {@code "block"}: Playwright will block all registration of Service Workers.</li>
* </ul>
*/
public ServiceWorkerPolicy serviceWorkers;
/**
* Populates context with given storage state. This option can be used to initialize context with logged-in information
* obtained via {@link BrowserContext#storageState BrowserContext.storageState()}.
@@ -208,7 +186,7 @@ public interface Browser extends AutoCloseable {
*/
public Path storageStatePath;
/**
* If specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
* It specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
* that imply single target DOM element will throw when more than one element matches the selector. See {@code Locator} to learn
* more about the strict mode.
*/
@@ -385,23 +363,6 @@ public interface Browser extends AutoCloseable {
this.proxy = proxy;
return this;
}
/**
* Optional setting to control resource content management. If {@code omit} is specified, content is not persisted. If {@code attach}
* is specified, resources are persistet as separate files and all of these files are archived along with the HAR file.
* Defaults to {@code embed}, which stores content inline the HAR file as per HAR specification.
*/
public NewContextOptions setRecordHarContent(HarContentPolicy recordHarContent) {
this.recordHarContent = recordHarContent;
return this;
}
/**
* When set to {@code minimal}, only record information necessary for routing from HAR. This omits sizes, timing, page, cookies,
* security and other types of HAR information that are not used when replaying from HAR. Defaults to {@code full}.
*/
public NewContextOptions setRecordHarMode(HarMode recordHarMode) {
this.recordHarMode = recordHarMode;
return this;
}
/**
* Optional setting to control whether to omit request content from the HAR. Defaults to {@code false}.
*/
@@ -418,14 +379,6 @@ public interface Browser extends AutoCloseable {
this.recordHarPath = recordHarPath;
return this;
}
public NewContextOptions setRecordHarUrlFilter(String recordHarUrlFilter) {
this.recordHarUrlFilter = recordHarUrlFilter;
return this;
}
public NewContextOptions setRecordHarUrlFilter(Pattern recordHarUrlFilter) {
this.recordHarUrlFilter = recordHarUrlFilter;
return this;
}
/**
* Enables video recording for all pages into the specified directory. If not specified videos are not recorded. Make sure
* to call {@link BrowserContext#close BrowserContext.close()} for videos to be saved.
@@ -474,18 +427,6 @@ public interface Browser extends AutoCloseable {
this.screenSize = screenSize;
return this;
}
/**
* Whether to allow sites to register Service workers. Defaults to {@code "allow"}.
* <ul>
* <li> {@code "allow"}: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API">Service Workers</a> can be
* registered.</li>
* <li> {@code "block"}: Playwright will block all registration of Service Workers.</li>
* </ul>
*/
public NewContextOptions setServiceWorkers(ServiceWorkerPolicy serviceWorkers) {
this.serviceWorkers = serviceWorkers;
return this;
}
/**
* Populates context with given storage state. This option can be used to initialize context with logged-in information
* obtained via {@link BrowserContext#storageState BrowserContext.storageState()}.
@@ -504,7 +445,7 @@ public interface Browser extends AutoCloseable {
return this;
}
/**
* If specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
* It specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
* that imply single target DOM element will throw when more than one element matches the selector. See {@code Locator} to learn
* more about the strict mode.
*/
@@ -630,17 +571,6 @@ public interface Browser extends AutoCloseable {
* 'http://per-context' } })}.
*/
public Proxy proxy;
/**
* Optional setting to control resource content management. If {@code omit} is specified, content is not persisted. If {@code attach}
* is specified, resources are persistet as separate files and all of these files are archived along with the HAR file.
* Defaults to {@code embed}, which stores content inline the HAR file as per HAR specification.
*/
public HarContentPolicy recordHarContent;
/**
* When set to {@code minimal}, only record information necessary for routing from HAR. This omits sizes, timing, page, cookies,
* security and other types of HAR information that are not used when replaying from HAR. Defaults to {@code full}.
*/
public HarMode recordHarMode;
/**
* Optional setting to control whether to omit request content from the HAR. Defaults to {@code false}.
*/
@@ -651,7 +581,6 @@ public interface Browser extends AutoCloseable {
* BrowserContext.close()} for the HAR to be saved.
*/
public Path recordHarPath;
public Object recordHarUrlFilter;
/**
* Enables video recording for all pages into the specified directory. If not specified videos are not recorded. Make sure
* to call {@link BrowserContext#close BrowserContext.close()} for videos to be saved.
@@ -673,15 +602,6 @@ public interface Browser extends AutoCloseable {
* is set.
*/
public ScreenSize screenSize;
/**
* Whether to allow sites to register Service workers. Defaults to {@code "allow"}.
* <ul>
* <li> {@code "allow"}: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API">Service Workers</a> can be
* registered.</li>
* <li> {@code "block"}: Playwright will block all registration of Service Workers.</li>
* </ul>
*/
public ServiceWorkerPolicy serviceWorkers;
/**
* Populates context with given storage state. This option can be used to initialize context with logged-in information
* obtained via {@link BrowserContext#storageState BrowserContext.storageState()}.
@@ -694,7 +614,7 @@ public interface Browser extends AutoCloseable {
*/
public Path storageStatePath;
/**
* If specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
* It specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
* that imply single target DOM element will throw when more than one element matches the selector. See {@code Locator} to learn
* more about the strict mode.
*/
@@ -871,23 +791,6 @@ public interface Browser extends AutoCloseable {
this.proxy = proxy;
return this;
}
/**
* Optional setting to control resource content management. If {@code omit} is specified, content is not persisted. If {@code attach}
* is specified, resources are persistet as separate files and all of these files are archived along with the HAR file.
* Defaults to {@code embed}, which stores content inline the HAR file as per HAR specification.
*/
public NewPageOptions setRecordHarContent(HarContentPolicy recordHarContent) {
this.recordHarContent = recordHarContent;
return this;
}
/**
* When set to {@code minimal}, only record information necessary for routing from HAR. This omits sizes, timing, page, cookies,
* security and other types of HAR information that are not used when replaying from HAR. Defaults to {@code full}.
*/
public NewPageOptions setRecordHarMode(HarMode recordHarMode) {
this.recordHarMode = recordHarMode;
return this;
}
/**
* Optional setting to control whether to omit request content from the HAR. Defaults to {@code false}.
*/
@@ -904,14 +807,6 @@ public interface Browser extends AutoCloseable {
this.recordHarPath = recordHarPath;
return this;
}
public NewPageOptions setRecordHarUrlFilter(String recordHarUrlFilter) {
this.recordHarUrlFilter = recordHarUrlFilter;
return this;
}
public NewPageOptions setRecordHarUrlFilter(Pattern recordHarUrlFilter) {
this.recordHarUrlFilter = recordHarUrlFilter;
return this;
}
/**
* Enables video recording for all pages into the specified directory. If not specified videos are not recorded. Make sure
* to call {@link BrowserContext#close BrowserContext.close()} for videos to be saved.
@@ -960,18 +855,6 @@ public interface Browser extends AutoCloseable {
this.screenSize = screenSize;
return this;
}
/**
* Whether to allow sites to register Service workers. Defaults to {@code "allow"}.
* <ul>
* <li> {@code "allow"}: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API">Service Workers</a> can be
* registered.</li>
* <li> {@code "block"}: Playwright will block all registration of Service Workers.</li>
* </ul>
*/
public NewPageOptions setServiceWorkers(ServiceWorkerPolicy serviceWorkers) {
this.serviceWorkers = serviceWorkers;
return this;
}
/**
* Populates context with given storage state. This option can be used to initialize context with logged-in information
* obtained via {@link BrowserContext#storageState BrowserContext.storageState()}.
@@ -990,7 +873,7 @@ public interface Browser extends AutoCloseable {
return this;
}
/**
* If specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
* It specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
* that imply single target DOM element will throw when more than one element matches the selector. See {@code Locator} to learn
* more about the strict mode.
*/
@@ -1064,10 +947,6 @@ public interface Browser extends AutoCloseable {
return this;
}
}
/**
* Get the browser type (chromium, firefox or webkit) that the browser belongs to.
*/
BrowserType browserType();
/**
* In case this browser is obtained using {@link BrowserType#launch BrowserType.launch()}, closes the browser and all of
* its pages (if any were opened).
@@ -1075,10 +954,6 @@ public interface Browser extends AutoCloseable {
* <p> In case this browser is connected to, clears all created contexts belonging to this browser and disconnects from the
* browser server.
*
* <p> <strong>NOTE:</strong> This is similar to force quitting the browser. Therefore, you should call {@link BrowserContext#close
* BrowserContext.close()} on any {@code BrowserContext}'s you explicitly created earlier with {@link Browser#newContext
* Browser.newContext()} **before** calling {@link Browser#close Browser.close()}.
*
* <p> The {@code Browser} object itself is considered to be disposed and cannot be used anymore.
*/
void close();
@@ -1098,11 +973,6 @@ public interface Browser extends AutoCloseable {
boolean isConnected();
/**
* Creates a new browser context. It won't share cookies/cache with other browser contexts.
*
* <p> <strong>NOTE:</strong> If directly using this method to create {@code BrowserContext}s, it is best practice to explicitly close the returned context
* via {@link BrowserContext#close BrowserContext.close()} when your code is done with the {@code BrowserContext}, and before
* calling {@link Browser#close Browser.close()}. This will ensure the {@code context} is closed gracefully and any
* artifacts—like HARs and videos—are fully flushed and saved.
* <pre>{@code
* Browser browser = playwright.firefox().launch(); // Or 'chromium' or 'webkit'.
* // Create a new incognito browser context.
@@ -1110,10 +980,6 @@ public interface Browser extends AutoCloseable {
* // Create a new page in a pristine context.
* Page page = context.newPage();
* page.navigate('https://example.com');
*
* // Graceful close up everything
* context.close();
* browser.close();
* }</pre>
*/
default BrowserContext newContext() {
@@ -1121,11 +987,6 @@ public interface Browser extends AutoCloseable {
}
/**
* Creates a new browser context. It won't share cookies/cache with other browser contexts.
*
* <p> <strong>NOTE:</strong> If directly using this method to create {@code BrowserContext}s, it is best practice to explicitly close the returned context
* via {@link BrowserContext#close BrowserContext.close()} when your code is done with the {@code BrowserContext}, and before
* calling {@link Browser#close Browser.close()}. This will ensure the {@code context} is closed gracefully and any
* artifacts—like HARs and videos—are fully flushed and saved.
* <pre>{@code
* Browser browser = playwright.firefox().launch(); // Or 'chromium' or 'webkit'.
* // Create a new incognito browser context.
@@ -1133,10 +994,6 @@ public interface Browser extends AutoCloseable {
* // Create a new page in a pristine context.
* Page page = context.newPage();
* page.navigate('https://example.com');
*
* // Graceful close up everything
* context.close();
* browser.close();
* }</pre>
*/
BrowserContext newContext(NewContextOptions options);
@@ -67,7 +67,7 @@ public interface BrowserContext extends AutoCloseable {
* done and its response has started loading in the popup.
* <pre>{@code
* Page newPage = context.waitForPage(() -> {
* page.locator("a[target=_blank]").click();
* page.click("a[target=_blank]");
* });
* System.out.println(newPage.evaluate("location.href"));
* }</pre>
@@ -174,62 +174,6 @@ public interface BrowserContext extends AutoCloseable {
return this;
}
}
class RouteFromHAROptions {
/**
* <ul>
* <li> If set to 'abort' any request not found in the HAR file will be aborted.</li>
* <li> If set to 'fallback' falls through to the next route handler in the handler chain.</li>
* </ul>
*
* <p> Defaults to abort.
*/
public HarNotFound notFound;
/**
* If specified, updates the given HAR with the actual network information instead of serving from file.
*/
public Boolean update;
/**
* A glob pattern, regular expression or predicate to match the request URL. Only requests with URL matching the pattern
* will be served from the HAR file. If not specified, all requests are served from the HAR file.
*/
public Object url;
/**
* <ul>
* <li> If set to 'abort' any request not found in the HAR file will be aborted.</li>
* <li> If set to 'fallback' falls through to the next route handler in the handler chain.</li>
* </ul>
*
* <p> Defaults to abort.
*/
public RouteFromHAROptions setNotFound(HarNotFound notFound) {
this.notFound = notFound;
return this;
}
/**
* If specified, updates the given HAR with the actual network information instead of serving from file.
*/
public RouteFromHAROptions setUpdate(boolean update) {
this.update = update;
return this;
}
/**
* A glob pattern, regular expression or predicate to match the request URL. Only requests with URL matching the pattern
* will be served from the HAR file. If not specified, all requests are served from the HAR file.
*/
public RouteFromHAROptions setUrl(String url) {
this.url = url;
return this;
}
/**
* A glob pattern, regular expression or predicate to match the request URL. Only requests with URL matching the pattern
* will be served from the HAR file. If not specified, all requests are served from the HAR file.
*/
public RouteFromHAROptions setUrl(Pattern url) {
this.url = url;
return this;
}
}
class StorageStateOptions {
/**
* The file path to save the storage state to. If {@code path} is a relative path, then it is resolved relative to current
@@ -404,7 +348,7 @@ public interface BrowserContext extends AutoCloseable {
* "</script>\n" +
* "<button onclick=\"onClick()\">Click me</button>\n" +
* "<div></div>");
* page.locator("button").click();
* page.click("button");
* }
* }
* }
@@ -463,7 +407,7 @@ public interface BrowserContext extends AutoCloseable {
* "</script>\n" +
* "<button onclick=\"onClick()\">Click me</button>\n" +
* "<div></div>");
* page.locator("button").click();
* page.click("button");
* }
* }
* }
@@ -533,7 +477,7 @@ public interface BrowserContext extends AutoCloseable {
* "</script>\n" +
* "<button onclick=\"onClick()\">Click me</button>\n" +
* "<div></div>\n");
* page.locator("button").click();
* page.click("button");
* }
* }
* }
@@ -609,9 +553,9 @@ public interface BrowserContext extends AutoCloseable {
* Routing provides the capability to modify network requests that are made by any page in the browser context. Once route
* is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted.
*
* <p> <strong>NOTE:</strong> {@link BrowserContext#route BrowserContext.route()} will not intercept requests intercepted by Service Worker. See <a
* <p> <strong>NOTE:</strong> {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See <a
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
* using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
*
* <p> An example of a naive handler that aborts all image requests:
* <pre>{@code
@@ -661,9 +605,9 @@ public interface BrowserContext extends AutoCloseable {
* Routing provides the capability to modify network requests that are made by any page in the browser context. Once route
* is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted.
*
* <p> <strong>NOTE:</strong> {@link BrowserContext#route BrowserContext.route()} will not intercept requests intercepted by Service Worker. See <a
* <p> <strong>NOTE:</strong> {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See <a
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
* using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
*
* <p> An example of a naive handler that aborts all image requests:
* <pre>{@code
@@ -711,9 +655,9 @@ public interface BrowserContext extends AutoCloseable {
* Routing provides the capability to modify network requests that are made by any page in the browser context. Once route
* is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted.
*
* <p> <strong>NOTE:</strong> {@link BrowserContext#route BrowserContext.route()} will not intercept requests intercepted by Service Worker. See <a
* <p> <strong>NOTE:</strong> {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See <a
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
* using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
*
* <p> An example of a naive handler that aborts all image requests:
* <pre>{@code
@@ -763,9 +707,9 @@ public interface BrowserContext extends AutoCloseable {
* Routing provides the capability to modify network requests that are made by any page in the browser context. Once route
* is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted.
*
* <p> <strong>NOTE:</strong> {@link BrowserContext#route BrowserContext.route()} will not intercept requests intercepted by Service Worker. See <a
* <p> <strong>NOTE:</strong> {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See <a
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
* using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
*
* <p> An example of a naive handler that aborts all image requests:
* <pre>{@code
@@ -813,9 +757,9 @@ public interface BrowserContext extends AutoCloseable {
* Routing provides the capability to modify network requests that are made by any page in the browser context. Once route
* is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted.
*
* <p> <strong>NOTE:</strong> {@link BrowserContext#route BrowserContext.route()} will not intercept requests intercepted by Service Worker. See <a
* <p> <strong>NOTE:</strong> {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See <a
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
* using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
*
* <p> An example of a naive handler that aborts all image requests:
* <pre>{@code
@@ -865,9 +809,9 @@ public interface BrowserContext extends AutoCloseable {
* Routing provides the capability to modify network requests that are made by any page in the browser context. Once route
* is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted.
*
* <p> <strong>NOTE:</strong> {@link BrowserContext#route BrowserContext.route()} will not intercept requests intercepted by Service Worker. See <a
* <p> <strong>NOTE:</strong> {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See <a
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
* using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
*
* <p> An example of a naive handler that aborts all image requests:
* <pre>{@code
@@ -911,32 +855,6 @@ public interface BrowserContext extends AutoCloseable {
* @param handler handler function to route the request.
*/
void route(Predicate<String> url, Consumer<Route> handler, RouteOptions options);
/**
* If specified the network requests that are made in the context will be served from the HAR file. Read more about <a
* href="https://playwright.dev/java/docs/network#replaying-from-har">Replaying from HAR</a>.
*
* <p> Playwright will not serve requests intercepted by Service Worker from the HAR file. See <a
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
*
* @param har Path to a <a href="http://www.softwareishard.com/blog/har-12-spec">HAR</a> file with prerecorded network data. If {@code path}
* is a relative path, then it is resolved relative to the current working directory.
*/
default void routeFromHAR(Path har) {
routeFromHAR(har, null);
}
/**
* If specified the network requests that are made in the context will be served from the HAR file. Read more about <a
* href="https://playwright.dev/java/docs/network#replaying-from-har">Replaying from HAR</a>.
*
* <p> Playwright will not serve requests intercepted by Service Worker from the HAR file. See <a
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
*
* @param har Path to a <a href="http://www.softwareishard.com/blog/har-12-spec">HAR</a> file with prerecorded network data. If {@code path}
* is a relative path, then it is resolved relative to the current working directory.
*/
void routeFromHAR(Path har, RouteFromHAROptions options);
/**
* This setting will change the default maximum navigation time for the following methods and related shortcuts:
* <ul>
@@ -19,7 +19,6 @@ package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
import java.nio.file.Path;
import java.util.*;
import java.util.regex.Pattern;
/**
* BrowserType provides methods to launch a specific browser instance or connect to an existing one. The following is a
@@ -516,17 +515,6 @@ public interface BrowserType {
* Network proxy settings.
*/
public Proxy proxy;
/**
* Optional setting to control resource content management. If {@code omit} is specified, content is not persisted. If {@code attach}
* is specified, resources are persistet as separate files and all of these files are archived along with the HAR file.
* Defaults to {@code embed}, which stores content inline the HAR file as per HAR specification.
*/
public HarContentPolicy recordHarContent;
/**
* When set to {@code minimal}, only record information necessary for routing from HAR. This omits sizes, timing, page, cookies,
* security and other types of HAR information that are not used when replaying from HAR. Defaults to {@code full}.
*/
public HarMode recordHarMode;
/**
* Optional setting to control whether to omit request content from the HAR. Defaults to {@code false}.
*/
@@ -537,7 +525,6 @@ public interface BrowserType {
* BrowserContext.close()} for the HAR to be saved.
*/
public Path recordHarPath;
public Object recordHarUrlFilter;
/**
* Enables video recording for all pages into the specified directory. If not specified videos are not recorded. Make sure
* to call {@link BrowserContext#close BrowserContext.close()} for videos to be saved.
@@ -559,21 +546,12 @@ public interface BrowserType {
* is set.
*/
public ScreenSize screenSize;
/**
* Whether to allow sites to register Service workers. Defaults to {@code "allow"}.
* <ul>
* <li> {@code "allow"}: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API">Service Workers</a> can be
* registered.</li>
* <li> {@code "block"}: Playwright will block all registration of Service Workers.</li>
* </ul>
*/
public ServiceWorkerPolicy serviceWorkers;
/**
* Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
*/
public Double slowMo;
/**
* If specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
* It specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
* that imply single target DOM element will throw when more than one element matches the selector. See {@code Locator} to learn
* more about the strict mode.
*/
@@ -865,23 +843,6 @@ public interface BrowserType {
this.proxy = proxy;
return this;
}
/**
* Optional setting to control resource content management. If {@code omit} is specified, content is not persisted. If {@code attach}
* is specified, resources are persistet as separate files and all of these files are archived along with the HAR file.
* Defaults to {@code embed}, which stores content inline the HAR file as per HAR specification.
*/
public LaunchPersistentContextOptions setRecordHarContent(HarContentPolicy recordHarContent) {
this.recordHarContent = recordHarContent;
return this;
}
/**
* When set to {@code minimal}, only record information necessary for routing from HAR. This omits sizes, timing, page, cookies,
* security and other types of HAR information that are not used when replaying from HAR. Defaults to {@code full}.
*/
public LaunchPersistentContextOptions setRecordHarMode(HarMode recordHarMode) {
this.recordHarMode = recordHarMode;
return this;
}
/**
* Optional setting to control whether to omit request content from the HAR. Defaults to {@code false}.
*/
@@ -898,14 +859,6 @@ public interface BrowserType {
this.recordHarPath = recordHarPath;
return this;
}
public LaunchPersistentContextOptions setRecordHarUrlFilter(String recordHarUrlFilter) {
this.recordHarUrlFilter = recordHarUrlFilter;
return this;
}
public LaunchPersistentContextOptions setRecordHarUrlFilter(Pattern recordHarUrlFilter) {
this.recordHarUrlFilter = recordHarUrlFilter;
return this;
}
/**
* Enables video recording for all pages into the specified directory. If not specified videos are not recorded. Make sure
* to call {@link BrowserContext#close BrowserContext.close()} for videos to be saved.
@@ -954,18 +907,6 @@ public interface BrowserType {
this.screenSize = screenSize;
return this;
}
/**
* Whether to allow sites to register Service workers. Defaults to {@code "allow"}.
* <ul>
* <li> {@code "allow"}: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API">Service Workers</a> can be
* registered.</li>
* <li> {@code "block"}: Playwright will block all registration of Service Workers.</li>
* </ul>
*/
public LaunchPersistentContextOptions setServiceWorkers(ServiceWorkerPolicy serviceWorkers) {
this.serviceWorkers = serviceWorkers;
return this;
}
/**
* Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
*/
@@ -974,7 +915,7 @@ public interface BrowserType {
return this;
}
/**
* If specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
* It specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
* that imply single target DOM element will throw when more than one element matches the selector. See {@code Locator} to learn
* more about the strict mode.
*/
@@ -1028,9 +969,7 @@ public interface BrowserType {
}
}
/**
* This method attaches Playwright to an existing browser instance. When connecting to another browser launched via
* {@code BrowserType.launchServer} in Node.js, the major and minor version needs to match the client version (1.2.3 → is
* compatible with 1.2.x).
* This method attaches Playwright to an existing browser instance.
*
* @param wsEndpoint A browser websocket endpoint to connect to.
*/
@@ -1038,9 +977,7 @@ public interface BrowserType {
return connect(wsEndpoint, null);
}
/**
* This method attaches Playwright to an existing browser instance. When connecting to another browser launched via
* {@code BrowserType.launchServer} in Node.js, the major and minor version needs to match the client version (1.2.3 → is
* compatible with 1.2.x).
* This method attaches Playwright to an existing browser instance.
*
* @param wsEndpoint A browser websocket endpoint to connect to.
*/
@@ -16,7 +16,7 @@
package com.microsoft.playwright;
import com.microsoft.playwright.impl.driver.Driver;
import com.microsoft.playwright.impl.Driver;
import java.io.IOException;
import java.nio.file.Path;
@@ -27,14 +27,14 @@ import java.nio.file.Path;
* <p> Download event is emitted once the download starts. Download path becomes available once download completes:
* <pre>{@code
* // wait for download to start
* Download download = page.waitForDownload(() -> page.locator("a").click());
* Download download = page.waitForDownload(() -> page.click("a"));
* // wait for download to complete
* Path path = download.path();
* }</pre>
* <pre>{@code
* // wait for download to start
* Download download = page.waitForDownload(() -> {
* page.locator("a").click();
* page.click("a");
* });
* // wait for download to complete
* Path path = download.path();
@@ -586,7 +586,7 @@ public interface ElementHandle extends JSHandle {
*/
public ScreenshotCaret caret;
/**
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlayed with a pink box
* {@code #FF00FF} that completely covers its bounding box.
*/
public List<Locator> mask;
@@ -608,9 +608,7 @@ public interface ElementHandle extends JSHandle {
/**
* When set to {@code "css"}, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will
* keep screenshots small. Using {@code "device"} option will produce a single pixel per each device pixel, so screenhots of
* high-dpi devices will be twice as large or even larger.
*
* <p> Defaults to {@code "device"}.
* high-dpi devices will be twice as large or even larger. Defaults to {@code "device"}.
*/
public ScreenshotScale scale;
/**
@@ -647,7 +645,7 @@ public interface ElementHandle extends JSHandle {
return this;
}
/**
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlayed with a pink box
* {@code #FF00FF} that completely covers its bounding box.
*/
public ScreenshotOptions setMask(List<Locator> mask) {
@@ -681,9 +679,7 @@ public interface ElementHandle extends JSHandle {
/**
* When set to {@code "css"}, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will
* keep screenshots small. Using {@code "device"} option will produce a single pixel per each device pixel, so screenhots of
* high-dpi devices will be twice as large or even larger.
*
* <p> Defaults to {@code "device"}.
* high-dpi devices will be twice as large or even larger. Defaults to {@code "device"}.
*/
public ScreenshotOptions setScale(ScreenshotScale scale) {
this.scale = scale;
@@ -1214,7 +1210,7 @@ public interface ElementHandle extends JSHandle {
* This method returns the bounding box of the element, or {@code null} if the element is not visible. The bounding box is
* calculated relative to the main frame viewport - which is usually the same as the browser window.
*
* <p> Scrolling affects the returned bounding box, similarly to <a
* <p> Scrolling affects the returned bonding box, similarly to <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect">Element.getBoundingClientRect</a>.
* That means {@code x} and/or {@code y} may be negative.
*
@@ -22,7 +22,7 @@ import java.nio.file.Path;
/**
* {@code FileChooser} objects are dispatched by the page in the {@link Page#onFileChooser Page.onFileChooser()} event.
* <pre>{@code
* FileChooser fileChooser = page.waitForFileChooser(() -> page.locator("upload").click());
* FileChooser fileChooser = page.waitForFileChooser(() -> page.click("upload"));
* fileChooser.setFiles(Paths.get("myfile.pdf"));
* }</pre>
*/
@@ -2366,25 +2366,9 @@ public interface Frame {
* @param eventInit Optional event-specific initialization properties.
*/
void dispatchEvent(String selector, String type, Object eventInit, DispatchEventOptions options);
/**
*
*
* @param source A selector to search for an element to drag. If there are multiple elements satisfying the selector, the first will be
* used. See <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> for more details.
* @param target A selector to search for an element to drop onto. If there are multiple elements satisfying the selector, the first will
* be used. See <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> for more details.
*/
default void dragAndDrop(String source, String target) {
dragAndDrop(source, target, null);
}
/**
*
*
* @param source A selector to search for an element to drag. If there are multiple elements satisfying the selector, the first will be
* used. See <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> for more details.
* @param target A selector to search for an element to drop onto. If there are multiple elements satisfying the selector, the first will
* be used. See <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> for more details.
*/
void dragAndDrop(String source, String target, DragAndDropOptions options);
/**
* Returns the return value of {@code expression}.
@@ -1013,7 +1013,7 @@ public interface Locator {
*/
public ScreenshotCaret caret;
/**
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlayed with a pink box
* {@code #FF00FF} that completely covers its bounding box.
*/
public List<Locator> mask;
@@ -1035,9 +1035,7 @@ public interface Locator {
/**
* When set to {@code "css"}, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will
* keep screenshots small. Using {@code "device"} option will produce a single pixel per each device pixel, so screenhots of
* high-dpi devices will be twice as large or even larger.
*
* <p> Defaults to {@code "device"}.
* high-dpi devices will be twice as large or even larger. Defaults to {@code "device"}.
*/
public ScreenshotScale scale;
/**
@@ -1074,7 +1072,7 @@ public interface Locator {
return this;
}
/**
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlayed with a pink box
* {@code #FF00FF} that completely covers its bounding box.
*/
public ScreenshotOptions setMask(List<Locator> mask) {
@@ -1108,9 +1106,7 @@ public interface Locator {
/**
* When set to {@code "css"}, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will
* keep screenshots small. Using {@code "device"} option will produce a single pixel per each device pixel, so screenhots of
* high-dpi devices will be twice as large or even larger.
*
* <p> Defaults to {@code "device"}.
* high-dpi devices will be twice as large or even larger. Defaults to {@code "device"}.
*/
public ScreenshotOptions setScale(ScreenshotScale scale) {
this.scale = scale;
@@ -1636,7 +1632,7 @@ public interface Locator {
* This method returns the bounding box of the element, or {@code null} if the element is not visible. The bounding box is
* calculated relative to the main frame viewport - which is usually the same as the browser window.
*
* <p> Scrolling affects the returned bounding box, similarly to <a
* <p> Scrolling affects the returned bonding box, similarly to <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect">Element.getBoundingClientRect</a>.
* That means {@code x} and/or {@code y} may be negative.
*
@@ -1657,7 +1653,7 @@ public interface Locator {
* This method returns the bounding box of the element, or {@code null} if the element is not visible. The bounding box is
* calculated relative to the main frame viewport - which is usually the same as the browser window.
*
* <p> Scrolling affects the returned bounding box, similarly to <a
* <p> Scrolling affects the returned bonding box, similarly to <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect">Element.getBoundingClientRect</a>.
* That means {@code x} and/or {@code y} may be negative.
*
@@ -2123,35 +2119,13 @@ public interface Locator {
*/
void fill(String value, FillOptions options);
/**
* This method narrows existing locator according to the options, for example filters by text. It can be chained to filter
* multiple times.
* <pre>{@code
* Locator rowLocator = page.locator("tr");
* // ...
* rowLocator
* .filter(new Locator.FilterOptions().setHasText("text in column 1"))
* .filter(new Locator.FilterOptions().setHas(
* page.locator("button", new Page.LocatorOptions().setHasText("column 2 button"))
* ))
* .screenshot();
* }</pre>
* This method narrows existing locator according to the options, for example filters by text.
*/
default Locator filter() {
return filter(null);
}
/**
* This method narrows existing locator according to the options, for example filters by text. It can be chained to filter
* multiple times.
* <pre>{@code
* Locator rowLocator = page.locator("tr");
* // ...
* rowLocator
* .filter(new Locator.FilterOptions().setHasText("text in column 1"))
* .filter(new Locator.FilterOptions().setHas(
* page.locator("button", new Page.LocatorOptions().setHasText("column 2 button"))
* ))
* .screenshot();
* }</pre>
* This method narrows existing locator according to the options, for example filters by text.
*/
Locator filter(FilterOptions options);
/**
@@ -1997,62 +1997,6 @@ public interface Page extends AutoCloseable {
return this;
}
}
class RouteFromHAROptions {
/**
* <ul>
* <li> If set to 'abort' any request not found in the HAR file will be aborted.</li>
* <li> If set to 'fallback' missing requests will be sent to the network.</li>
* </ul>
*
* <p> Defaults to abort.
*/
public HarNotFound notFound;
/**
* If specified, updates the given HAR with the actual network information instead of serving from file.
*/
public Boolean update;
/**
* A glob pattern, regular expression or predicate to match the request URL. Only requests with URL matching the pattern
* will be served from the HAR file. If not specified, all requests are served from the HAR file.
*/
public Object url;
/**
* <ul>
* <li> If set to 'abort' any request not found in the HAR file will be aborted.</li>
* <li> If set to 'fallback' missing requests will be sent to the network.</li>
* </ul>
*
* <p> Defaults to abort.
*/
public RouteFromHAROptions setNotFound(HarNotFound notFound) {
this.notFound = notFound;
return this;
}
/**
* If specified, updates the given HAR with the actual network information instead of serving from file.
*/
public RouteFromHAROptions setUpdate(boolean update) {
this.update = update;
return this;
}
/**
* A glob pattern, regular expression or predicate to match the request URL. Only requests with URL matching the pattern
* will be served from the HAR file. If not specified, all requests are served from the HAR file.
*/
public RouteFromHAROptions setUrl(String url) {
this.url = url;
return this;
}
/**
* A glob pattern, regular expression or predicate to match the request URL. Only requests with URL matching the pattern
* will be served from the HAR file. If not specified, all requests are served from the HAR file.
*/
public RouteFromHAROptions setUrl(Pattern url) {
this.url = url;
return this;
}
}
class ScreenshotOptions {
/**
* When set to {@code "disabled"}, stops CSS animations, CSS transitions and Web Animations. Animations get different treatment
@@ -2080,7 +2024,7 @@ public interface Page extends AutoCloseable {
*/
public Boolean fullPage;
/**
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlayed with a pink box
* {@code #FF00FF} that completely covers its bounding box.
*/
public List<Locator> mask;
@@ -2102,9 +2046,7 @@ public interface Page extends AutoCloseable {
/**
* When set to {@code "css"}, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will
* keep screenshots small. Using {@code "device"} option will produce a single pixel per each device pixel, so screenhots of
* high-dpi devices will be twice as large or even larger.
*
* <p> Defaults to {@code "device"}.
* high-dpi devices will be twice as large or even larger. Defaults to {@code "device"}.
*/
public ScreenshotScale scale;
/**
@@ -2162,7 +2104,7 @@ public interface Page extends AutoCloseable {
return this;
}
/**
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlayed with a pink box
* {@code #FF00FF} that completely covers its bounding box.
*/
public ScreenshotOptions setMask(List<Locator> mask) {
@@ -2196,9 +2138,7 @@ public interface Page extends AutoCloseable {
/**
* When set to {@code "css"}, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will
* keep screenshots small. Using {@code "device"} option will produce a single pixel per each device pixel, so screenhots of
* high-dpi devices will be twice as large or even larger.
*
* <p> Defaults to {@code "device"}.
* high-dpi devices will be twice as large or even larger. Defaults to {@code "device"}.
*/
public ScreenshotOptions setScale(ScreenshotScale scale) {
this.scale = scale;
@@ -3581,25 +3521,9 @@ public interface Page extends AutoCloseable {
* @param eventInit Optional event-specific initialization properties.
*/
void dispatchEvent(String selector, String type, Object eventInit, DispatchEventOptions options);
/**
*
*
* @param source A selector to search for an element to drag. If there are multiple elements satisfying the selector, the first will be
* used. See <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> for more details.
* @param target A selector to search for an element to drop onto. If there are multiple elements satisfying the selector, the first will
* be used. See <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> for more details.
*/
default void dragAndDrop(String source, String target) {
dragAndDrop(source, target, null);
}
/**
*
*
* @param source A selector to search for an element to drag. If there are multiple elements satisfying the selector, the first will be
* used. See <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> for more details.
* @param target A selector to search for an element to drop onto. If there are multiple elements satisfying the selector, the first will
* be used. See <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> for more details.
*/
void dragAndDrop(String source, String target, DragAndDropOptions options);
/**
* This method changes the {@code CSS media type} through the {@code media} argument, and/or the {@code "prefers-colors-scheme"} media
@@ -4840,7 +4764,7 @@ public interface Page extends AutoCloseable {
*
* <p> <strong>NOTE:</strong> {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See <a
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
* using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
*
* <p> An example of a naive handler that aborts all image requests:
* <pre>{@code
@@ -4893,7 +4817,7 @@ public interface Page extends AutoCloseable {
*
* <p> <strong>NOTE:</strong> {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See <a
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
* using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
*
* <p> An example of a naive handler that aborts all image requests:
* <pre>{@code
@@ -4944,7 +4868,7 @@ public interface Page extends AutoCloseable {
*
* <p> <strong>NOTE:</strong> {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See <a
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
* using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
*
* <p> An example of a naive handler that aborts all image requests:
* <pre>{@code
@@ -4997,7 +4921,7 @@ public interface Page extends AutoCloseable {
*
* <p> <strong>NOTE:</strong> {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See <a
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
* using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
*
* <p> An example of a naive handler that aborts all image requests:
* <pre>{@code
@@ -5048,7 +4972,7 @@ public interface Page extends AutoCloseable {
*
* <p> <strong>NOTE:</strong> {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See <a
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
* using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
*
* <p> An example of a naive handler that aborts all image requests:
* <pre>{@code
@@ -5101,7 +5025,7 @@ public interface Page extends AutoCloseable {
*
* <p> <strong>NOTE:</strong> {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See <a
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
* using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
*
* <p> An example of a naive handler that aborts all image requests:
* <pre>{@code
@@ -5143,32 +5067,6 @@ public interface Page extends AutoCloseable {
* @param handler handler function to route the request.
*/
void route(Predicate<String> url, Consumer<Route> handler, RouteOptions options);
/**
* If specified the network requests that are made in the page will be served from the HAR file. Read more about <a
* href="https://playwright.dev/java/docs/network#replaying-from-har">Replaying from HAR</a>.
*
* <p> Playwright will not serve requests intercepted by Service Worker from the HAR file. See <a
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
*
* @param har Path to a <a href="http://www.softwareishard.com/blog/har-12-spec">HAR</a> file with prerecorded network data. If {@code path}
* is a relative path, then it is resolved relative to the current working directory.
*/
default void routeFromHAR(Path har) {
routeFromHAR(har, null);
}
/**
* If specified the network requests that are made in the page will be served from the HAR file. Read more about <a
* href="https://playwright.dev/java/docs/network#replaying-from-har">Replaying from HAR</a>.
*
* <p> Playwright will not serve requests intercepted by Service Worker from the HAR file. See <a
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
*
* @param har Path to a <a href="http://www.softwareishard.com/blog/har-12-spec">HAR</a> file with prerecorded network data. If {@code path}
* is a relative path, then it is resolved relative to the current working directory.
*/
void routeFromHAR(Path har, RouteFromHAROptions options);
/**
* Returns the buffer with the captured screenshot.
*/
@@ -58,9 +58,8 @@ public interface Request {
*/
Frame frame();
/**
* An object with the request HTTP headers. The header names are lower-cased. Note that this method does not return
* security-related headers, including cookie-related ones. You can use {@link Request#allHeaders Request.allHeaders()} for
* complete list of headers that include {@code cookie} information.
* **DEPRECATED** Incomplete list of headers as seen by the rendering engine. Use {@link Request#allHeaders
* Request.allHeaders()} instead.
*/
Map<String, String> headers();
/**
@@ -40,14 +40,8 @@ public interface Response {
*/
Frame frame();
/**
* Indicates whether this Response was fulfilled by a Service Worker's Fetch Handler (i.e. via <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent/respondWith">FetchEvent.respondWith</a>).
*/
boolean fromServiceWorker();
/**
* An object with the response HTTP headers. The header names are lower-cased. Note that this method does not return
* security-related headers, including cookie-related ones. You can use {@link Response#allHeaders Response.allHeaders()}
* for complete list of headers that include {@code cookie} information.
* **DEPRECATED** Incomplete list of headers as seen by the rendering engine. Use {@link Response#allHeaders
* Response.allHeaders()} instead.
*/
Map<String, String> headers();
/**
@@ -80,62 +80,6 @@ public interface Route {
return this;
}
}
class FallbackOptions {
/**
* If set changes the request HTTP headers. Header values will be converted to a string.
*/
public Map<String, String> headers;
/**
* If set changes the request method (e.g. GET or POST)
*/
public String method;
/**
* If set changes the post data of request
*/
public Object postData;
/**
* If set changes the request URL. New URL must have same protocol as original one. Changing the URL won't affect the route
* matching, all the routes are matched using the original request URL.
*/
public String url;
/**
* If set changes the request HTTP headers. Header values will be converted to a string.
*/
public FallbackOptions setHeaders(Map<String, String> headers) {
this.headers = headers;
return this;
}
/**
* If set changes the request method (e.g. GET or POST)
*/
public FallbackOptions setMethod(String method) {
this.method = method;
return this;
}
/**
* If set changes the post data of request
*/
public FallbackOptions setPostData(String postData) {
this.postData = postData;
return this;
}
/**
* If set changes the post data of request
*/
public FallbackOptions setPostData(byte[] postData) {
this.postData = postData;
return this;
}
/**
* If set changes the request URL. New URL must have same protocol as original one. Changing the URL won't affect the route
* matching, all the routes are matched using the original request URL.
*/
public FallbackOptions setUrl(String url) {
this.url = url;
return this;
}
}
class FulfillOptions {
/**
* Optional response body as text.
@@ -256,8 +200,8 @@ public interface Route {
* page.route("**\/*", route -> {
* // Override headers
* Map<String, String> headers = new HashMap<>(route.request().headers());
* headers.put("foo", "foo-value"); // set "foo" header
* headers.remove("bar"); // remove "bar" header
* headers.put("foo", "bar"); // set "foo" header
* headers.remove("origin"); // remove "origin" header
* route.resume(new Route.ResumeOptions().setHeaders(headers));
* });
* }</pre>
@@ -271,133 +215,13 @@ public interface Route {
* page.route("**\/*", route -> {
* // Override headers
* Map<String, String> headers = new HashMap<>(route.request().headers());
* headers.put("foo", "foo-value"); // set "foo" header
* headers.remove("bar"); // remove "bar" header
* headers.put("foo", "bar"); // set "foo" header
* headers.remove("origin"); // remove "origin" header
* route.resume(new Route.ResumeOptions().setHeaders(headers));
* });
* }</pre>
*/
void resume(ResumeOptions options);
/**
* When several routes match the given pattern, they run in the order opposite to their registration. That way the last
* registered route can always override all the previous ones. In the example below, request will be handled by the
* bottom-most handler first, then it'll fall back to the previous one and in the end will be aborted by the first
* registered route.
* <pre>{@code
* page.route("**\/*", route -> {
* // Runs last.
* route.abort();
* });
*
* page.route("**\/*", route -> {
* // Runs second.
* route.fallback();
* });
*
* page.route("**\/*", route -> {
* // Runs first.
* route.fallback();
* });
* }</pre>
*
* <p> Registering multiple routes is useful when you want separate handlers to handle different kinds of requests, for example
* API calls vs page resources or GET requests vs POST requests as in the example below.
* <pre>{@code
* // Handle GET requests.
* page.route("**\/*", route -> {
* if (!route.request().method().equals("GET")) {
* route.fallback();
* return;
* }
* // Handling GET only.
* // ...
* });
*
* // Handle POST requests.
* page.route("**\/*", route -> {
* if (!route.request().method().equals("POST")) {
* route.fallback();
* return;
* }
* // Handling POST only.
* // ...
* });
* }</pre>
*
* <p> One can also modify request while falling back to the subsequent handler, that way intermediate route handler can modify
* url, method, headers and postData of the request.
* <pre>{@code
* page.route("**\/*", route -> {
* // Override headers
* Map<String, String> headers = new HashMap<>(route.request().headers());
* headers.put("foo", "foo-value"); // set "foo" header
* headers.remove("bar"); // remove "bar" header
* route.fallback(new Route.ResumeOptions().setHeaders(headers));
* });
* }</pre>
*/
default void fallback() {
fallback(null);
}
/**
* When several routes match the given pattern, they run in the order opposite to their registration. That way the last
* registered route can always override all the previous ones. In the example below, request will be handled by the
* bottom-most handler first, then it'll fall back to the previous one and in the end will be aborted by the first
* registered route.
* <pre>{@code
* page.route("**\/*", route -> {
* // Runs last.
* route.abort();
* });
*
* page.route("**\/*", route -> {
* // Runs second.
* route.fallback();
* });
*
* page.route("**\/*", route -> {
* // Runs first.
* route.fallback();
* });
* }</pre>
*
* <p> Registering multiple routes is useful when you want separate handlers to handle different kinds of requests, for example
* API calls vs page resources or GET requests vs POST requests as in the example below.
* <pre>{@code
* // Handle GET requests.
* page.route("**\/*", route -> {
* if (!route.request().method().equals("GET")) {
* route.fallback();
* return;
* }
* // Handling GET only.
* // ...
* });
*
* // Handle POST requests.
* page.route("**\/*", route -> {
* if (!route.request().method().equals("POST")) {
* route.fallback();
* return;
* }
* // Handling POST only.
* // ...
* });
* }</pre>
*
* <p> One can also modify request while falling back to the subsequent handler, that way intermediate route handler can modify
* url, method, headers and postData of the request.
* <pre>{@code
* page.route("**\/*", route -> {
* // Override headers
* Map<String, String> headers = new HashMap<>(route.request().headers());
* headers.put("foo", "foo-value"); // set "foo" header
* headers.remove("bar"); // remove "bar" header
* route.fallback(new Route.ResumeOptions().setHeaders(headers));
* });
* }</pre>
*/
void fallback(FallbackOptions options);
/**
* Fulfills route's request with given response.
*
@@ -63,7 +63,7 @@ public interface Selectors {
* // Use the selector prefixed with its name.
* Locator button = page.locator("tag=button");
* // Combine it with other selector engines.
* page.locator("tag=div >> text=\"Click me\"").click();
* page.click("tag=div >> text=\"Click me\"");
* // Can use it in any methods supporting selectors.
* int buttonCount = (int) page.locator("tag=button").count();
* browser.close();
@@ -98,7 +98,7 @@ public interface Selectors {
* // Use the selector prefixed with its name.
* Locator button = page.locator("tag=button");
* // Combine it with other selector engines.
* page.locator("tag=div >> text=\"Click me\"").click();
* page.click("tag=div >> text=\"Click me\"");
* // Can use it in any methods supporting selectors.
* int buttonCount = (int) page.locator("tag=button").count();
* browser.close();
@@ -131,7 +131,7 @@ public interface Selectors {
* // Use the selector prefixed with its name.
* Locator button = page.locator("tag=button");
* // Combine it with other selector engines.
* page.locator("tag=div >> text=\"Click me\"").click();
* page.click("tag=div >> text=\"Click me\"");
* // Can use it in any methods supporting selectors.
* int buttonCount = (int) page.locator("tag=button").count();
* browser.close();
@@ -166,7 +166,7 @@ public interface Selectors {
* // Use the selector prefixed with its name.
* Locator button = page.locator("tag=button");
* // Combine it with other selector engines.
* page.locator("tag=div >> text=\"Click me\"").click();
* page.click("tag=div >> text=\"Click me\"");
* // Can use it in any methods supporting selectors.
* int buttonCount = (int) page.locator("tag=button").count();
* browser.close();
@@ -192,7 +192,7 @@ public interface Tracing {
* page.navigate("https://playwright.dev");
*
* context.tracing().startChunk();
* page.locator("text=Get Started").click();
* page.click("text=Get Started");
* // Everything between startChunk and stopChunk will be recorded in the trace.
* context.tracing().stopChunk(new Tracing.StopChunkOptions()
* .setPath(Paths.get("trace1.zip")));
@@ -219,7 +219,7 @@ public interface Tracing {
* page.navigate("https://playwright.dev");
*
* context.tracing().startChunk();
* page.locator("text=Get Started").click();
* page.click("text=Get Started");
* // Everything between startChunk and stopChunk will be recorded in the trace.
* context.tracing().stopChunk(new Tracing.StopChunkOptions()
* .setPath(Paths.get("trace1.zip")));
@@ -31,7 +31,7 @@ import java.util.regex.Pattern;
* @Test
* void statusBecomesSubmitted() {
* ...
* page.locator("#submit-button").click();
* page.click("#submit-button");
* assertThat(page.locator(".status")).hasText("Submitted");
* }
* }
@@ -156,11 +156,6 @@ public interface LocatorAssertions {
}
}
class ContainsTextOptions {
/**
* Whether to perform case-insensitive match. {@code ignoreCase} option takes precedence over the corresponding regular
* expression flag if specified.
*/
public Boolean ignoreCase;
/**
* Time to retry the assertion for.
*/
@@ -170,14 +165,6 @@ public interface LocatorAssertions {
*/
public Boolean useInnerText;
/**
* Whether to perform case-insensitive match. {@code ignoreCase} option takes precedence over the corresponding regular
* expression flag if specified.
*/
public ContainsTextOptions setIgnoreCase(boolean ignoreCase) {
this.ignoreCase = ignoreCase;
return this;
}
/**
* Time to retry the assertion for.
*/
@@ -278,11 +265,6 @@ public interface LocatorAssertions {
}
}
class HasTextOptions {
/**
* Whether to perform case-insensitive match. {@code ignoreCase} option takes precedence over the corresponding regular
* expression flag if specified.
*/
public Boolean ignoreCase;
/**
* Time to retry the assertion for.
*/
@@ -292,14 +274,6 @@ public interface LocatorAssertions {
*/
public Boolean useInnerText;
/**
* Whether to perform case-insensitive match. {@code ignoreCase} option takes precedence over the corresponding regular
* expression flag if specified.
*/
public HasTextOptions setIgnoreCase(boolean ignoreCase) {
this.ignoreCase = ignoreCase;
return this;
}
/**
* Time to retry the assertion for.
*/
@@ -329,20 +303,6 @@ public interface LocatorAssertions {
return this;
}
}
class HasValuesOptions {
/**
* Time to retry the assertion for.
*/
public Double timeout;
/**
* Time to retry the assertion for.
*/
public HasValuesOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
/**
* Makes the assertion check for the opposite condition. For example, this code tests that the Locator doesn't contain text
* {@code "error"}:
@@ -498,29 +458,9 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).containsText("substring");
* }</pre>
*
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> Elements from a **subset** of this list contain text from the expected array, respectively.</li>
* <li> The matching subset of elements has the same order as the expected array.</li>
* <li> Each text value from the expected array is matched by some element from the list.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <pre>{@code
* // ✓ Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
*
* // ✖ No item contains this text
* assertThat(page.locator("ul > li")).containsText(new String[] {"Some 33"});
*
* // ✖ Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).containsText(new String[] {"Text 3"});
* assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -535,29 +475,9 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).containsText("substring");
* }</pre>
*
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> Elements from a **subset** of this list contain text from the expected array, respectively.</li>
* <li> The matching subset of elements has the same order as the expected array.</li>
* <li> Each text value from the expected array is matched by some element from the list.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <pre>{@code
* // ✓ Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
*
* // ✖ No item contains this text
* assertThat(page.locator("ul > li")).containsText(new String[] {"Some 33"});
*
* // ✖ Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).containsText(new String[] {"Text 3"});
* assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -570,29 +490,9 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).containsText("substring");
* }</pre>
*
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> Elements from a **subset** of this list contain text from the expected array, respectively.</li>
* <li> The matching subset of elements has the same order as the expected array.</li>
* <li> Each text value from the expected array is matched by some element from the list.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <pre>{@code
* // ✓ Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
*
* // ✖ No item contains this text
* assertThat(page.locator("ul > li")).containsText(new String[] {"Some 33"});
*
* // ✖ Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).containsText(new String[] {"Text 3"});
* assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -607,29 +507,9 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).containsText("substring");
* }</pre>
*
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> Elements from a **subset** of this list contain text from the expected array, respectively.</li>
* <li> The matching subset of elements has the same order as the expected array.</li>
* <li> Each text value from the expected array is matched by some element from the list.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <pre>{@code
* // ✓ Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
*
* // ✖ No item contains this text
* assertThat(page.locator("ul > li")).containsText(new String[] {"Some 33"});
*
* // ✖ Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).containsText(new String[] {"Text 3"});
* assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -642,29 +522,9 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).containsText("substring");
* }</pre>
*
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> Elements from a **subset** of this list contain text from the expected array, respectively.</li>
* <li> The matching subset of elements has the same order as the expected array.</li>
* <li> Each text value from the expected array is matched by some element from the list.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <pre>{@code
* // ✓ Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
*
* // ✖ No item contains this text
* assertThat(page.locator("ul > li")).containsText(new String[] {"Some 33"});
*
* // ✖ Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).containsText(new String[] {"Text 3"});
* assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -679,29 +539,9 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).containsText("substring");
* }</pre>
*
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> Elements from a **subset** of this list contain text from the expected array, respectively.</li>
* <li> The matching subset of elements has the same order as the expected array.</li>
* <li> Each text value from the expected array is matched by some element from the list.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <pre>{@code
* // ✓ Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
*
* // ✖ No item contains this text
* assertThat(page.locator("ul > li")).containsText(new String[] {"Some 33"});
*
* // ✖ Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).containsText(new String[] {"Text 3"});
* assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -714,29 +554,9 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).containsText("substring");
* }</pre>
*
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> Elements from a **subset** of this list contain text from the expected array, respectively.</li>
* <li> The matching subset of elements has the same order as the expected array.</li>
* <li> Each text value from the expected array is matched by some element from the list.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <pre>{@code
* // ✓ Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
*
* // ✖ No item contains this text
* assertThat(page.locator("ul > li")).containsText(new String[] {"Some 33"});
*
* // ✖ Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).containsText(new String[] {"Text 3"});
* assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -751,29 +571,9 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).containsText("substring");
* }</pre>
*
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> Elements from a **subset** of this list contain text from the expected array, respectively.</li>
* <li> The matching subset of elements has the same order as the expected array.</li>
* <li> Each text value from the expected array is matched by some element from the list.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <pre>{@code
* // ✓ Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
*
* // ✖ No item contains this text
* assertThat(page.locator("ul > li")).containsText(new String[] {"Some 33"});
*
* // ✖ Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).containsText(new String[] {"Text 3"});
* assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -824,11 +624,9 @@ public interface LocatorAssertions {
*/
void hasAttribute(String name, Pattern value, HasAttributeOptions options);
/**
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a relaxed
* regular expression.
* Ensures the {@code Locator} points to an element with given CSS class.
* <pre>{@code
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected"));
* assertThat(page.locator("#component")).hasClass("selected row");
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
@@ -842,11 +640,9 @@ public interface LocatorAssertions {
hasClass(expected, null);
}
/**
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a relaxed
* regular expression.
* Ensures the {@code Locator} points to an element with given CSS class.
* <pre>{@code
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected"));
* assertThat(page.locator("#component")).hasClass("selected row");
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
@@ -858,11 +654,9 @@ public interface LocatorAssertions {
*/
void hasClass(String expected, HasClassOptions options);
/**
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a relaxed
* regular expression.
* Ensures the {@code Locator} points to an element with given CSS class.
* <pre>{@code
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected"));
* assertThat(page.locator("#component")).hasClass("selected row");
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
@@ -876,11 +670,9 @@ public interface LocatorAssertions {
hasClass(expected, null);
}
/**
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a relaxed
* regular expression.
* Ensures the {@code Locator} points to an element with given CSS class.
* <pre>{@code
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected"));
* assertThat(page.locator("#component")).hasClass("selected row");
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
@@ -892,11 +684,9 @@ public interface LocatorAssertions {
*/
void hasClass(Pattern expected, HasClassOptions options);
/**
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a relaxed
* regular expression.
* Ensures the {@code Locator} points to an element with given CSS class.
* <pre>{@code
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected"));
* assertThat(page.locator("#component")).hasClass("selected row");
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
@@ -910,11 +700,9 @@ public interface LocatorAssertions {
hasClass(expected, null);
}
/**
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a relaxed
* regular expression.
* Ensures the {@code Locator} points to an element with given CSS class.
* <pre>{@code
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected"));
* assertThat(page.locator("#component")).hasClass("selected row");
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
@@ -926,11 +714,9 @@ public interface LocatorAssertions {
*/
void hasClass(String[] expected, HasClassOptions options);
/**
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a relaxed
* regular expression.
* Ensures the {@code Locator} points to an element with given CSS class.
* <pre>{@code
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected"));
* assertThat(page.locator("#component")).hasClass("selected row");
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
@@ -944,11 +730,9 @@ public interface LocatorAssertions {
hasClass(expected, null);
}
/**
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a relaxed
* regular expression.
* Ensures the {@code Locator} points to an element with given CSS class.
* <pre>{@code
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected"));
* assertThat(page.locator("#component")).hasClass("selected row");
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
@@ -1094,28 +878,9 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
* }</pre>
*
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> The number of elements equals the number of expected values in the array.</li>
* <li> Elements from the list have text matching expected array values, one by one, in order.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <pre>{@code
* // ✓ Has the right items in the right order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 3", "Text 2", "Text 1"});
*
* // ✖ Last item does not match
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text"});
*
* // ✖ Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -1130,28 +895,9 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
* }</pre>
*
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> The number of elements equals the number of expected values in the array.</li>
* <li> Elements from the list have text matching expected array values, one by one, in order.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <pre>{@code
* // ✓ Has the right items in the right order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 3", "Text 2", "Text 1"});
*
* // ✖ Last item does not match
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text"});
*
* // ✖ Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -1164,28 +910,9 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
* }</pre>
*
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> The number of elements equals the number of expected values in the array.</li>
* <li> Elements from the list have text matching expected array values, one by one, in order.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <pre>{@code
* // ✓ Has the right items in the right order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 3", "Text 2", "Text 1"});
*
* // ✖ Last item does not match
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text"});
*
* // ✖ Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -1200,28 +927,9 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
* }</pre>
*
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> The number of elements equals the number of expected values in the array.</li>
* <li> Elements from the list have text matching expected array values, one by one, in order.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <pre>{@code
* // ✓ Has the right items in the right order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 3", "Text 2", "Text 1"});
*
* // ✖ Last item does not match
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text"});
*
* // ✖ Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -1234,28 +942,9 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
* }</pre>
*
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> The number of elements equals the number of expected values in the array.</li>
* <li> Elements from the list have text matching expected array values, one by one, in order.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <pre>{@code
* // ✓ Has the right items in the right order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 3", "Text 2", "Text 1"});
*
* // ✖ Last item does not match
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text"});
*
* // ✖ Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -1270,28 +959,9 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
* }</pre>
*
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> The number of elements equals the number of expected values in the array.</li>
* <li> Elements from the list have text matching expected array values, one by one, in order.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <pre>{@code
* // ✓ Has the right items in the right order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 3", "Text 2", "Text 1"});
*
* // ✖ Last item does not match
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text"});
*
* // ✖ Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -1304,28 +974,9 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
* }</pre>
*
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> The number of elements equals the number of expected values in the array.</li>
* <li> Elements from the list have text matching expected array values, one by one, in order.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <pre>{@code
* // ✓ Has the right items in the right order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 3", "Text 2", "Text 1"});
*
* // ✖ Last item does not match
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text"});
*
* // ✖ Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -1340,28 +991,9 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
* }</pre>
*
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> The number of elements equals the number of expected values in the array.</li>
* <li> Elements from the list have text matching expected array values, one by one, in order.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <pre>{@code
* // ✓ Has the right items in the right order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 3", "Text 2", "Text 1"});
*
* // ✖ Last item does not match
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text"});
*
* // ✖ Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -1411,61 +1043,5 @@ public interface LocatorAssertions {
* @param value Expected value.
*/
void hasValue(Pattern value, HasValueOptions options);
/**
* Ensures the {@code Locator} points to multi-select/combobox (i.e. a {@code select} with the {@code multiple} attribute) and the specified
* values are selected.
*
* <p> For example, given the following element:
* <pre>{@code
* page.locator("id=favorite-colors").selectOption(["R", "G"]);
* assertThat(page.locator("id=favorite-colors")).hasValues(new Pattern[] { Pattern.compile("R"), Pattern.compile("G") });
* }</pre>
*
* @param values Expected options currently selected.
*/
default void hasValues(String[] values) {
hasValues(values, null);
}
/**
* Ensures the {@code Locator} points to multi-select/combobox (i.e. a {@code select} with the {@code multiple} attribute) and the specified
* values are selected.
*
* <p> For example, given the following element:
* <pre>{@code
* page.locator("id=favorite-colors").selectOption(["R", "G"]);
* assertThat(page.locator("id=favorite-colors")).hasValues(new Pattern[] { Pattern.compile("R"), Pattern.compile("G") });
* }</pre>
*
* @param values Expected options currently selected.
*/
void hasValues(String[] values, HasValuesOptions options);
/**
* Ensures the {@code Locator} points to multi-select/combobox (i.e. a {@code select} with the {@code multiple} attribute) and the specified
* values are selected.
*
* <p> For example, given the following element:
* <pre>{@code
* page.locator("id=favorite-colors").selectOption(["R", "G"]);
* assertThat(page.locator("id=favorite-colors")).hasValues(new Pattern[] { Pattern.compile("R"), Pattern.compile("G") });
* }</pre>
*
* @param values Expected options currently selected.
*/
default void hasValues(Pattern[] values) {
hasValues(values, null);
}
/**
* Ensures the {@code Locator} points to multi-select/combobox (i.e. a {@code select} with the {@code multiple} attribute) and the specified
* values are selected.
*
* <p> For example, given the following element:
* <pre>{@code
* page.locator("id=favorite-colors").selectOption(["R", "G"]);
* assertThat(page.locator("id=favorite-colors")).hasValues(new Pattern[] { Pattern.compile("R"), Pattern.compile("G") });
* }</pre>
*
* @param values Expected options currently selected.
*/
void hasValues(Pattern[] values, HasValuesOptions options);
}
@@ -31,7 +31,7 @@ import java.util.regex.Pattern;
* @Test
* void navigatesToLoginPage() {
* ...
* page.locator("#login").click();
* page.click("#login");
* assertThat(page).hasURL(Pattern.compile(".*\/login"));
* }
* }
@@ -120,7 +120,7 @@ public interface PageAssertions {
* assertThat(page).hasURL(".com");
* }</pre>
*
* @param urlOrRegExp Expected URL string or RegExp.
* @param urlOrRegExp Expected substring or RegExp.
*/
default void hasURL(String urlOrRegExp) {
hasURL(urlOrRegExp, null);
@@ -131,7 +131,7 @@ public interface PageAssertions {
* assertThat(page).hasURL(".com");
* }</pre>
*
* @param urlOrRegExp Expected URL string or RegExp.
* @param urlOrRegExp Expected substring or RegExp.
*/
void hasURL(String urlOrRegExp, HasURLOptions options);
/**
@@ -140,7 +140,7 @@ public interface PageAssertions {
* assertThat(page).hasURL(".com");
* }</pre>
*
* @param urlOrRegExp Expected URL string or RegExp.
* @param urlOrRegExp Expected substring or RegExp.
*/
default void hasURL(Pattern urlOrRegExp) {
hasURL(urlOrRegExp, null);
@@ -151,7 +151,7 @@ public interface PageAssertions {
* assertThat(page).hasURL(".com");
* }</pre>
*
* @param urlOrRegExp Expected URL string or RegExp.
* @param urlOrRegExp Expected substring or RegExp.
*/
void hasURL(Pattern urlOrRegExp, HasURLOptions options);
}
@@ -20,7 +20,6 @@ import com.microsoft.playwright.APIResponse;
import com.microsoft.playwright.Locator;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.impl.APIResponseAssertionsImpl;
import com.microsoft.playwright.impl.AssertionsTimeout;
import com.microsoft.playwright.impl.LocatorAssertionsImpl;
import com.microsoft.playwright.impl.PageAssertionsImpl;
@@ -38,7 +37,7 @@ import com.microsoft.playwright.impl.PageAssertionsImpl;
* @Test
* void statusBecomesSubmitted() {
* ...
* page.locator("#submit-button").click();
* page.click("#submit-button");
* assertThat(page.locator(".status")).hasText("Submitted");
* }
* }
@@ -87,17 +86,5 @@ public interface PlaywrightAssertions {
return new PageAssertionsImpl(page);
}
/**
* Changes default timeout for Playwright assertions from 5 seconds to the specified value.
* <pre>{@code
* PlaywrightAssertions.setDefaultAssertionTimeout(30_000);
* }</pre>
*
* @param timeout Timeout in milliseconds.
*/
static void setDefaultAssertionTimeout(double milliseconds) {
AssertionsTimeout.setDefaultTimeout(milliseconds);
}
}
@@ -21,7 +21,6 @@ import com.microsoft.playwright.assertions.APIResponseAssertions;
import org.opentest4j.AssertionFailedError;
import java.util.List;
import java.util.regex.Pattern;
public class APIResponseAssertionsImpl implements APIResponseAssertions {
private final APIResponse actual;
@@ -55,20 +54,6 @@ public class APIResponseAssertionsImpl implements APIResponseAssertions {
if (!log.isEmpty()) {
log = "\nCall log:\n" + log;
}
String contentType = actual.headers().get("content-type");
boolean isTextEncoding = contentType == null ? false : isTextualMimeType(contentType);
String responseText = "";
if (isTextEncoding) {
String text = actual.text();
if (text != null) {
responseText = "\nResponse text:\n" + (text.length() > 1000 ? text.substring(0, 1000) : text);
}
}
throw new AssertionFailedError(message + log + responseText);
}
static boolean isTextualMimeType(String mimeType) {
return Pattern.matches("^(text/.*?|application/(json|(x-)?javascript|xml.*?|ecmascript|graphql|x-www-form-urlencoded)|image/svg(\\+xml)?|application/.*?(\\+json|\\+xml))(;\\s*charset=.*)?$", mimeType);
throw new AssertionFailedError(message + log);
}
}
@@ -52,7 +52,7 @@ class AssertionsBase {
void expectImpl(String expression, FrameExpectOptions expectOptions, Object expected, String message) {
if (expectOptions.timeout == null) {
expectOptions.timeout = AssertionsTimeout.defaultTimeout;
expectOptions.timeout = 5_000.0;
}
if (expectOptions.isNot) {
message = message.replace("expected to", "expected not to");
@@ -1,30 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.microsoft.playwright.PlaywrightException;
public class AssertionsTimeout {
static double defaultTimeout = 5_000;
public static void setDefaultTimeout(double ms) {
if (ms < 0) {
throw new PlaywrightException("Timeout cannot be negative");
}
defaultTimeout = ms;
}
}
@@ -20,20 +20,21 @@ import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.*;
import com.microsoft.playwright.options.BindingCallback;
import com.microsoft.playwright.options.Cookie;
import com.microsoft.playwright.options.FunctionCallback;
import com.microsoft.playwright.options.Geolocation;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.Serialization.addHarUrlFilter;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.isSafeCloseError;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -53,17 +54,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
final TimeoutSettings timeoutSettings = new TimeoutSettings();
Path videosDir;
URL baseUrl;
final Map<String, HarRecorder> harRecorders = new HashMap<>();
static class HarRecorder {
final Path path;
final HarContentPolicy contentPolicy;
HarRecorder(Path har, HarContentPolicy policy) {
path = har;
contentPolicy = policy;
}
}
Path recordHarPath;
enum EventType {
CLOSE,
@@ -83,13 +74,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
this.tracing = connection.getExistingObject(initializer.getAsJsonObject("tracing").get("guid").getAsString());
tracing.isRemote = browser != null && browser.isRemote;
this.request = connection.getExistingObject(initializer.getAsJsonObject("requestContext").get("guid").getAsString());
}
void setRecordHar(Path path, HarContentPolicy policy) {
if (path != null) {
harRecorders.put("", new HarRecorder(path, policy));
}
this.request = connection.getExistingObject(initializer.getAsJsonObject("APIRequestContext").get("guid").getAsString());
}
void setBaseUrl(String spec) {
@@ -196,31 +181,15 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
isClosedOrClosing = true;
try {
for (Map.Entry<String, HarRecorder> entry : harRecorders.entrySet()) {
JsonObject params = new JsonObject();
params.addProperty("harId", entry.getKey());
JsonObject json = sendMessage("harExport", params).getAsJsonObject();
if (recordHarPath != null) {
JsonObject json = sendMessage("harExport").getAsJsonObject();
ArtifactImpl artifact = connection.getExistingObject(json.getAsJsonObject("artifact").get("guid").getAsString());
// In case of CDP connection browser is null but since the connection is established by
// the driver it is safe to consider the artifact local.
if (browser() != null && browser().isRemote) {
artifact.isRemote = true;
}
// Server side will compress artifact if content is attach or if file is .zip.
HarRecorder harParams = entry.getValue();
boolean isCompressed = harParams.contentPolicy == HarContentPolicy.ATTACH || harParams.path.toString().endsWith(".zip");
boolean needCompressed = harParams.path.toString().endsWith(".zip");
if (isCompressed && !needCompressed) {
String tmpPath = harParams.path + ".tmp";
artifact.saveAs(Paths.get(tmpPath));
JsonObject unzipParams = new JsonObject();
unzipParams.addProperty("zipFile", tmpPath);
unzipParams.addProperty("harFile", harParams.path.toString());
connection.localUtils.sendMessage("harUnzip", unzipParams);
} else {
artifact.saveAs(harParams.path);
}
artifact.saveAs(recordHarPath);
artifact.delete();
}
@@ -367,7 +336,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override
public void route(String url, Consumer<Route> handler, RouteOptions options) {
route(new UrlMatcher(baseUrl, url), handler, options);
route(new UrlMatcher(this.baseUrl, url), handler, options);
}
@Override
@@ -380,21 +349,6 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
route(new UrlMatcher(url), handler, options);
}
@Override
public void routeFromHAR(Path har, RouteFromHAROptions options) {
if (options == null) {
options = new RouteFromHAROptions();
}
if (options.update != null && options.update) {
recordIntoHar(null, har, options);
return;
}
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) {
withLogging("BrowserContext.route", () -> {
routes.add(matcher, handler, options == null ? null : options.times);
@@ -406,22 +360,6 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
});
}
void recordIntoHar(PageImpl page, Path har, RouteFromHAROptions options) {
JsonObject params = new JsonObject();
if (page != null) {
params.add("page", page.toProtocolRef());
}
JsonObject jsonOptions = new JsonObject();
jsonOptions.addProperty("path", har.toAbsolutePath().toString());
jsonOptions.addProperty("content", HarContentPolicy.ATTACH.name().toLowerCase());
jsonOptions.addProperty("mode", HarMode.MINIMAL.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, HarContentPolicy.ATTACH));
}
@Override
public void setDefaultNavigationTimeout(double timeout) {
withLogging("BrowserContext.setDefaultNavigationTimeout", () -> {
@@ -536,12 +474,11 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
}
void handleRoute(RouteImpl route) {
Router.HandleResult handled = routes.handle(route);
if (handled == Router.HandleResult.FoundMatchingHandler) {
void handleRoute(Route route) {
boolean handled = routes.handle(route);
if (handled) {
maybeDisableNetworkInterception();
}
if (!route.isHandled()){
} else {
route.resume();
}
}
@@ -553,7 +490,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override
protected void handleEvent(String event, JsonObject params) {
if ("route".equals(event)) {
RouteImpl route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString());
Route route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString());
handleRoute(route);
} else if ("page".equals(event)) {
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
@@ -19,21 +19,20 @@ package com.microsoft.playwright.impl;
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 com.microsoft.playwright.Browser;
import com.microsoft.playwright.BrowserContext;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.PlaywrightException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Consumer;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.Serialization.addHarUrlFilter;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.*;
import static com.microsoft.playwright.impl.Utils.convertType;
import static com.microsoft.playwright.impl.Utils.isSafeCloseError;
class BrowserImpl extends ChannelOwner implements Browser {
final Set<BrowserContextImpl> contexts = new HashSet<>();
@@ -41,7 +40,7 @@ class BrowserImpl extends ChannelOwner implements Browser {
boolean isRemote;
boolean isConnectedOverWebSocket;
private boolean isConnected = true;
BrowserTypeImpl browserType;
LocalUtils localUtils;
enum EventType {
DISCONNECTED,
@@ -61,11 +60,6 @@ class BrowserImpl extends ChannelOwner implements Browser {
listeners.remove(EventType.DISCONNECTED, handler);
}
@Override
public BrowserType browserType() {
return browserType;
}
@Override
public void close() {
withLogging("Browser.close", () -> closeImpl());
@@ -118,9 +112,6 @@ class BrowserImpl extends ChannelOwner implements Browser {
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);
}
if (options.storageStatePath != null) {
try {
@@ -136,50 +127,21 @@ 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) {
if (options.recordHarPath != null) {
JsonObject recordHar = new JsonObject();
recordHar.addProperty("path", options.recordHarPath.toString());
if (options.recordHarOmitContent != null) {
recordHar.addProperty("omitContent", true);
}
params.remove("recordHarPath");
params.remove("recordHarOmitContent");
params.add("recordHar", recordHar);
} else if (options.recordHarOmitContent != null) {
throw new PlaywrightException("recordHarOmitContent is set but recordHarPath is null");
}
if (options.recordVideoDir != null) {
JsonObject recordVideo = new JsonObject();
@@ -209,7 +171,8 @@ class BrowserImpl extends ChannelOwner implements Browser {
if (options.baseURL != null) {
context.setBaseUrl(options.baseURL);
}
context.setRecordHar(recordHarPath, harContentPolicy);
context.recordHarPath = options.recordHarPath;
context.tracing().localUtils = localUtils;
contexts.add(context);
return context;
}
@@ -230,7 +193,9 @@ class BrowserImpl extends ChannelOwner implements Browser {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
if (page != null) {
params.add("page", ((PageImpl) page).toProtocolRef());
JsonObject jsonPage = new JsonObject();
jsonPage.addProperty("guid", ((PageImpl) page).guid);
params.add("page", jsonPage);
}
sendMessage("startTracing", params);
}
@@ -22,15 +22,12 @@ 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.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.convertType;
class BrowserTypeImpl extends ChannelOwner implements BrowserType {
LocalUtils localUtils;
@@ -51,7 +48,7 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
JsonElement result = sendMessage("launch", params);
BrowserImpl browser = connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("browser").get("guid").getAsString());
browser.browserType = this;
browser.localUtils = localUtils;
return browser;
}
@@ -85,7 +82,7 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
JsonObject json = sendMessage("connect", params).getAsJsonObject();
JsonPipe pipe = connection.getExistingObject(json.getAsJsonObject("pipe").get("guid").getAsString());
Connection connection = new Connection(pipe, this.connection.env);
Connection connection = new Connection(pipe);
PlaywrightImpl playwright = connection.initializePlaywright();
if (!playwright.initializer.has("preLaunchedBrowser")) {
try {
@@ -99,7 +96,7 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
BrowserImpl browser = connection.getExistingObject(playwright.initializer.getAsJsonObject("preLaunchedBrowser").get("guid").getAsString());
browser.isRemote = true;
browser.isConnectedOverWebSocket = true;
browser.browserType = this;
browser.localUtils = localUtils;
Consumer<JsonPipe> connectionCloseListener = t -> browser.notifyRemoteClosed();
pipe.onClose(connectionCloseListener);
browser.onDisconnected(b -> {
@@ -133,7 +130,7 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
BrowserImpl browser = connection.getExistingObject(json.getAsJsonObject("browser").get("guid").getAsString());
browser.isRemote = true;
browser.browserType = this;
browser.localUtils = localUtils;
if (json.has("defaultContext")) {
String contextId = json.getAsJsonObject("defaultContext").get("guid").getAsString();
BrowserContextImpl defaultContext = connection.getExistingObject(contextId);
@@ -155,52 +152,20 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
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);
}
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();
params.addProperty("userDataDir", userDataDir.toString());
if (recordHar != null) {
if (options.recordHarPath != null) {
JsonObject recordHar = new JsonObject();
recordHar.addProperty("path", options.recordHarPath.toString());
if (options.recordHarOmitContent != null) {
recordHar.addProperty("omitContent", true);
}
params.remove("recordHarPath");
params.remove("recordHarOmitContent");
params.add("recordHar", recordHar);
} else if (options.recordHarOmitContent != null) {
throw new PlaywrightException("recordHarOmitContent is set but recordHarPath is null");
}
if (options.recordVideoDir != null) {
JsonObject recordVideo = new JsonObject();
@@ -230,7 +195,8 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
if (options.baseURL != null) {
context.setBaseUrl(options.baseURL);
}
context.setRecordHar(recordHarPath, harContentPolicy);
context.recordHarPath = options.recordHarPath;
context.tracing().localUtils = localUtils;
return context;
}
@@ -26,7 +26,7 @@ import java.util.function.Supplier;
class ChannelOwner extends LoggingSupport {
final Connection connection;
private ChannelOwner parent;
private final ChannelOwner parent;
private final Map<String, ChannelOwner> objects = new HashMap<>();
final String type;
@@ -68,12 +68,6 @@ class ChannelOwner extends LoggingSupport {
objects.clear();
}
void adopt(ChannelOwner child) {
child.parent.objects.remove(child.guid);
objects.put(child.guid, child);
child.parent = this;
}
<T> T withWaitLogging(String apiName, Supplier<T> code) {
return new WaitForEventLogger<>(this, apiName, code).get();
}
@@ -114,10 +108,4 @@ class ChannelOwner extends LoggingSupport {
void handleEvent(String event, JsonObject parameters) {
}
JsonObject toProtocolRef() {
JsonObject json = new JsonObject();
json.addProperty("guid", guid);
return json;
}
}
@@ -64,8 +64,6 @@ public class Connection {
String debug = System.getenv("DEBUG");
isLogging = (debug != null) && debug.contains("pw:channel");
}
LocalUtils localUtils;
final Map<String, String> env;
class Root extends ChannelOwner {
Root(Connection connection) {
@@ -80,14 +78,13 @@ public class Connection {
}
}
Connection(Transport transport, Map<String, String> env) {
this.env = env;
Connection(Transport transport) {
if (isLogging) {
transport = new TransportLogger(transport);
}
this.transport = transport;
root = new Root(this);
stackTraceCollector = StackTraceCollector.createFromEnv(env);
stackTraceCollector = StackTraceCollector.createFromEnv();
}
boolean isCollectingStacks() {
@@ -141,10 +138,6 @@ public class Connection {
return (PlaywrightImpl) this.root.initialize();
}
LocalUtils localUtils() {
return localUtils;
}
public <T> T getExistingObject(String guid) {
@SuppressWarnings("unchecked") T result = (T) objects.get(guid);
if (result == null)
@@ -201,24 +194,18 @@ public class Connection {
createRemoteObject(message.guid, message.params);
return;
}
if (message.method.equals("__dispose__")) {
ChannelOwner object = objects.get(message.guid);
if (object == null) {
throw new PlaywrightException("Cannot find object to dispose: " + message.guid);
}
object.disconnect();
return;
}
ChannelOwner object = objects.get(message.guid);
if (object == null) {
throw new PlaywrightException("Cannot find object to call " + message.method + ": " + message.guid);
}
if (message.method.equals("__adopt__")) {
String childGuid = message.params.get("guid").getAsString();
ChannelOwner child = objects.get(childGuid);
if (child == null) {
throw new PlaywrightException("Unknown new child: " + childGuid);
}
object.adopt(child);
return;
}
if (message.method.equals("__dispose__")) {
object.disconnect();
return;
}
object.handleEvent(message.method, message.params);
}
@@ -283,8 +270,7 @@ public class Connection {
result = new JsonPipe(parent, type, guid, initializer);
break;
case "LocalUtils":
localUtils = new LocalUtils(parent, type, guid, initializer);
result = localUtils;
result = new LocalUtils(parent, type, guid, initializer);
break;
case "Page":
result = new PageImpl(parent, type, guid, initializer);
@@ -23,6 +23,7 @@ import com.microsoft.playwright.*;
import com.microsoft.playwright.options.*;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -1047,13 +1048,6 @@ public class FrameImpl extends ChannelOwner implements Frame {
if (add != null) {
WaitUntilState state = loadStateFromProtocol(add.getAsString());
loadStates.add(state);
if (parentFrame == null && page != null) {
if (state == LOAD) {
page.listeners.notify(PageImpl.EventType.LOAD, page);
} else if (state == DOMCONTENTLOADED) {
page.listeners.notify(PageImpl.EventType.DOMCONTENTLOADED, page);
}
}
internalListeners.notify(InternalEventType.LOADSTATE, state);
}
JsonElement remove = params.get("remove");
@@ -1,109 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.google.gson.JsonObject;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.Request;
import com.microsoft.playwright.Route;
import com.microsoft.playwright.options.HarNotFound;
import java.nio.file.Path;
import java.util.Base64;
import java.util.Map;
import static com.microsoft.playwright.impl.LoggingSupport.isApiLoggingEnabled;
import static com.microsoft.playwright.impl.LoggingSupport.logApi;
import static com.microsoft.playwright.impl.Serialization.fromNameValues;
import static com.microsoft.playwright.impl.Serialization.gson;
public class HARRouter {
private final LocalUtils localUtils;
private final HarNotFound defaultAction;
private final String harId;
HARRouter(LocalUtils localUtils, Path harFile, HarNotFound defaultAction) {
this.localUtils = localUtils;
this.defaultAction = defaultAction;
JsonObject params = new JsonObject();
params.addProperty("file", harFile.toString());
JsonObject json = localUtils.sendMessage("harOpen", params).getAsJsonObject();
if (json.has("error")) {
throw new PlaywrightException(json.get("error").getAsString());
}
harId = json.get("harId").getAsString();
}
void handle(Route route) {
Request request = route.request();
JsonObject params = new JsonObject();
params.addProperty("harId", harId);
params.addProperty("url", request.url());
params.addProperty("method", request.method());
params.add("headers", gson().toJsonTree(request.headersArray()));
if (request.postDataBuffer() != null) {
String base64 = Base64.getEncoder().encodeToString(request.postDataBuffer());
params.addProperty("postData", base64);
}
params.addProperty("isNavigationRequest", request.isNavigationRequest());
JsonObject response = localUtils.sendMessage("harLookup", params).getAsJsonObject();
String action = response.get("action").getAsString();
if ("redirect".equals(action)) {
String redirectURL = response.get("redirectURL").getAsString();
if (isApiLoggingEnabled()) {
logApi("HAR: " + route.request().url() + " redirected to " + redirectURL);
}
((RouteImpl) route).redirectNavigationRequest(redirectURL);
return;
}
if ("fulfill".equals(action)) {
int status = response.get("status").getAsInt();
Map<String, String> headers = fromNameValues(response.getAsJsonArray("headers"));
byte[] buffer = Base64.getDecoder().decode(response.get("body").getAsString());
route.fulfill(new Route.FulfillOptions()
.setStatus(status)
.setHeaders(headers)
.setBodyBytes(buffer));
return;
}
if ("error".equals(action)) {
if (isApiLoggingEnabled()) {
logApi("HAR: " + response.get("message").getAsString());
}
// Report the error, but fall through to the default handler.
}
if (defaultAction == HarNotFound.FALLBACK) {
route.fallback();
return;
}
// By default abort not matching requests.
route.abort();
}
void dispose() {
JsonObject params = new JsonObject();
params.addProperty("harId", harId);
localUtils.sendMessageAsync("harClose", params);
}
}
@@ -19,7 +19,6 @@ package com.microsoft.playwright.impl;
import com.microsoft.playwright.Locator;
import com.microsoft.playwright.assertions.LocatorAssertions;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
@@ -40,7 +39,6 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
public void containsText(String text, ContainsTextOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = text;
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));
@@ -49,7 +47,6 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
@Override
public void containsText(Pattern pattern, ContainsTextOptions options) {
ExpectedTextValue expected = expectedRegex(pattern);
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));
@@ -61,7 +58,6 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
for (String text : strings) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = text;
expected.ignoreCase = shouldIgnoreCase(options);
expected.matchSubstring = true;
expected.normalizeWhiteSpace = true;
list.add(expected);
@@ -74,7 +70,6 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
List<ExpectedTextValue> list = new ArrayList<>();
for (Pattern pattern : patterns) {
ExpectedTextValue expected = expectedRegex(pattern);
expected.ignoreCase = shouldIgnoreCase(options);
expected.matchSubstring = true;
expected.normalizeWhiteSpace = true;
list.add(expected);
@@ -208,7 +203,6 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
public void hasText(String text, HasTextOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = text;
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));
@@ -217,7 +211,6 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
@Override
public void hasText(Pattern pattern, HasTextOptions options) {
ExpectedTextValue expected = expectedRegex(pattern);
expected.ignoreCase = shouldIgnoreCase(options);
// Just match substring, same as containsText.
expected.matchSubstring = true;
expected.normalizeWhiteSpace = true;
@@ -230,7 +223,6 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
for (String text : strings) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = text;
expected.ignoreCase = shouldIgnoreCase(options);
expected.matchSubstring = false;
expected.normalizeWhiteSpace = true;
list.add(expected);
@@ -243,7 +235,6 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
List<ExpectedTextValue> list = new ArrayList<>();
for (Pattern pattern : patterns) {
ExpectedTextValue expected = expectedRegex(pattern);
expected.ignoreCase = shouldIgnoreCase(options);
expected.matchSubstring = true;
expected.normalizeWhiteSpace = true;
list.add(expected);
@@ -264,28 +255,6 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expectImpl("to.have.value", expected, pattern, "Locator expected to have value matching regex", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasValues(String[] values, HasValuesOptions options) {
List<ExpectedTextValue> list = new ArrayList<>();
for (String text : values) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = text;
list.add(expected);
}
expectImpl("to.have.values", list, values, "Locator expected to have values", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasValues(Pattern[] patterns, HasValuesOptions options) {
List<ExpectedTextValue> list = new ArrayList<>();
for (Pattern pattern : patterns) {
ExpectedTextValue expected = expectedRegex(pattern);
expected.matchSubstring = true;
list.add(expected);
}
expectImpl("to.have.values", list, patterns, "Locator expected to have values matching regex", convertType(options, FrameExpectOptions.class));
}
@Override
public void isChecked(IsCheckedOptions options) {
String expression = (options != null && options.checked != null && !options.checked) ? "to.be.unchecked" : "to.be.checked";
@@ -336,17 +305,5 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
public LocatorAssertions not() {
return new LocatorAssertionsImpl(actualLocator, !isNot);
}
private static Boolean shouldIgnoreCase(Object options) {
if (options == null) {
return null;
}
try {
Field fromField = options.getClass().getDeclaredField("ignoreCase");
Object value = fromField.get(options);
return (Boolean) value;
} catch (NoSuchFieldException | IllegalAccessException e) {
return null;
}
}
}
@@ -67,8 +67,7 @@ class LocatorImpl implements Locator {
if (options.hasText != null) {
if (options.hasText instanceof Pattern) {
Pattern pattern = (Pattern) options.hasText;
String jsRegex = "/" + pattern.pattern() + "/" + toJsRegexFlags(pattern);
selector += " >> has=" + gson().toJson("text=" + jsRegex);
selector += " >> :scope:text-matches(" + escapeWithQuotes(pattern.pattern()) + ", \"" + toJsRegexFlags(pattern) + "\")";
} else if (options.hasText instanceof String) {
String text = (String) options.hasText;
selector += " >> :scope:has-text(" + escapeWithQuotes(text) + ")";
@@ -511,7 +510,9 @@ class LocatorImpl implements Locator {
JsonObject toProtocol() {
JsonObject result = new JsonObject();
result.add("frame", frame.toProtocolRef());
JsonObject frameJson = new JsonObject();
frameJson.addProperty("guid", frame.guid);
result.add("frame", frameJson);
result.addProperty("selector", selector);
return result;
}
@@ -60,11 +60,7 @@ class LoggingSupport {
System.err.println(timestamp + " " + message);
}
static boolean isApiLoggingEnabled() {
return isEnabled;
}
static void logApi(String message) {
private void logApi(String message) {
// This matches log format produced by the server.
logWithTimestamp("pw:api " + message);
}
@@ -175,6 +175,10 @@ public class PageImpl extends ChannelOwner implements Page {
}
}
}
} else if ("load".equals(event)) {
listeners.notify(EventType.LOAD, this);
} else if ("domcontentloaded".equals(event)) {
listeners.notify(EventType.DOMCONTENTLOADED, this);
} else if ("frameAttached".equals(event)) {
String guid = params.getAsJsonObject("frame").get("guid").getAsString();
FrameImpl frame = connection.getExistingObject(guid);
@@ -194,12 +198,11 @@ public class PageImpl extends ChannelOwner implements Page {
}
listeners.notify(EventType.FRAMEDETACHED, frame);
} else if ("route".equals(event)) {
RouteImpl route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString());
Router.HandleResult handled = routes.handle(route);
if (handled == Router.HandleResult.FoundMatchingHandler) {
Route route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString());
boolean handled = routes.handle(route);
if (handled) {
maybeDisableNetworkInterception();
}
if (!route.isHandled()) {
} else {
browserContext.handleRoute(route);
}
} else if ("video".equals(event)) {
@@ -969,21 +972,6 @@ public class PageImpl extends ChannelOwner implements Page {
route(new UrlMatcher(url), handler, options);
}
@Override
public void routeFromHAR(Path har, RouteFromHAROptions options) {
if (options == null) {
options = new RouteFromHAROptions();
}
if (options.update != null && options.update) {
browserContext.recordIntoHar(this, har, convertType(options, BrowserContext.RouteFromHAROptions.class));
return;
}
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) {
withLogging("Page.route", () -> {
routes.add(matcher, handler, options == null ? null : options.times);
@@ -21,7 +21,6 @@ import com.microsoft.playwright.APIRequest;
import com.microsoft.playwright.Playwright;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.Selectors;
import com.microsoft.playwright.impl.driver.Driver;
import java.io.IOException;
import java.nio.file.Path;
@@ -44,7 +43,7 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
pb.environment().putAll(env);
Driver.setRequiredEnvironmentVariables(pb);
Process p = pb.start();
Connection connection = new Connection(new PipeTransport(p.getInputStream(), p.getOutputStream()), env);
Connection connection = new Connection(new PipeTransport(p.getInputStream(), p.getOutputStream()));
PlaywrightImpl result = connection.initializePlaywright();
result.driverProcess = p;
result.initSharedSelectors(null);
@@ -31,7 +31,6 @@ class SerializedValue{
// Possible values: { 'null, 'undefined, 'NaN, 'Infinity, '-Infinity, '-0 }
String v;
String d;
String u;
public static class R {
String p;
String f;
@@ -86,7 +85,6 @@ class ExpectedTextValue {
String string;
String regexSource;
String regexFlags;
Boolean ignoreCase;
Boolean matchSubstring;
Boolean normalizeWhiteSpace;
}
@@ -42,14 +42,6 @@ public class RequestImpl extends ChannelOwner implements Request {
String failure;
Timing timing;
boolean didFailOrFinish;
private FallbackOverrides fallbackOverrides;
static class FallbackOverrides {
String url;
String method;
byte[] postData;
Map<String, String> headers;
}
RequestImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
@@ -83,9 +75,6 @@ public class RequestImpl extends ChannelOwner implements Request {
@Override
public Map<String, String> headers() {
if (fallbackOverrides != null && fallbackOverrides.headers != null) {
return new RawHeaders(Utils.toHeadersList(fallbackOverrides.headers)).headers();
}
return headers.headers();
}
@@ -106,26 +95,19 @@ public class RequestImpl extends ChannelOwner implements Request {
@Override
public String method() {
if (fallbackOverrides != null && fallbackOverrides.method != null) {
return fallbackOverrides.method;
}
return initializer.get("method").getAsString();
}
@Override
public String postData() {
byte[] buffer = postDataBuffer();
if (buffer == null) {
if (postData == null) {
return null;
}
return new String(buffer, StandardCharsets.UTF_8);
return new String(postData, StandardCharsets.UTF_8);
}
@Override
public byte[] postDataBuffer() {
if (fallbackOverrides != null && fallbackOverrides.postData != null) {
return fallbackOverrides.postData;
}
return postData;
}
@@ -174,9 +156,6 @@ public class RequestImpl extends ChannelOwner implements Request {
@Override
public String url() {
if (fallbackOverrides != null && fallbackOverrides.url != null) {
return fallbackOverrides.url;
}
return initializer.get("url").getAsString();
}
@@ -185,9 +164,6 @@ public class RequestImpl extends ChannelOwner implements Request {
}
private RawHeaders getRawHeaders() {
if (fallbackOverrides != null && fallbackOverrides.headers != null) {
return new RawHeaders(Utils.toHeadersList(fallbackOverrides.headers));
}
if (rawHeaders != null) {
return rawHeaders;
}
@@ -200,26 +176,4 @@ public class RequestImpl extends ChannelOwner implements Request {
rawHeaders = new RawHeaders(asList(gson().fromJson(rawHeadersJson, HttpHeader[].class)));
return rawHeaders;
}
void applyFallbackOverrides(FallbackOverrides overrides) {
if (fallbackOverrides == null) {
fallbackOverrides = new FallbackOverrides();
}
if (overrides.url != null) {
fallbackOverrides.url = overrides.url;
}
if (overrides.method != null) {
fallbackOverrides.method = overrides.method;
}
if (overrides.headers != null) {
fallbackOverrides.headers = overrides.headers;
}
if (overrides.postData != null) {
fallbackOverrides.postData = overrides.postData;
}
}
FallbackOverrides fallbackOverridesForResume() {
return fallbackOverrides;
}
}
@@ -83,11 +83,6 @@ public class ResponseImpl extends ChannelOwner implements Response {
return request().frame();
}
@Override
public boolean fromServiceWorker() {
return initializer.get("fromServiceWorker").getAsBoolean();
}
@Override
public Map<String, String> headers() {
return headers.headers();
@@ -17,7 +17,6 @@
package com.microsoft.playwright.impl;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Frame;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.Route;
@@ -28,8 +27,6 @@ import java.util.Base64;
import java.util.LinkedHashMap;
import java.util.Map;
import static com.microsoft.playwright.impl.Utils.convertType;
public class RouteImpl extends ChannelOwner implements Route {
private boolean handled;
@@ -47,69 +44,41 @@ public class RouteImpl extends ChannelOwner implements Route {
});
}
boolean isHandled() {
return handled;
}
@Override
public void resume(ResumeOptions options) {
startHandling();
applyOverrides(convertType(options, FallbackOptions.class));
withLogging("Route.resume", () -> resumeImpl(request().fallbackOverridesForResume()));
withLogging("Route.resume", () -> resumeImpl(options));
}
@Override
public void fallback(FallbackOptions options) {
if (handled) {
throw new PlaywrightException("Route is already handled!");
}
applyOverrides(options);
}
private void applyOverrides(FallbackOptions options) {
private void resumeImpl(ResumeOptions options) {
if (options == null) {
return;
options = new ResumeOptions();
}
RequestImpl.FallbackOverrides overrides = new RequestImpl.FallbackOverrides();
overrides.url = options.url;
overrides.method = options.method;
overrides.headers = options.headers;
if (options.postData != null) {
overrides.postData = getPostDataBytes(options.postData);
}
request().applyFallbackOverrides(overrides);
}
private void resumeImpl(RequestImpl.FallbackOverrides options) {
JsonObject params = new JsonObject();
if (options != null) {
if (options.url != null) {
params.addProperty("url", options.url);
}
if (options.method != null) {
params.addProperty("method", options.method);
}
if (options.headers != null) {
params.add("headers", Serialization.toProtocol(options.headers));
}
if (options.postData != null) {
String base64 = Base64.getEncoder().encodeToString(options.postData);
params.addProperty("postData", base64);
if (options.url != null) {
params.addProperty("url", options.url);
}
if (options.method != null) {
params.addProperty("method", options.method);
}
if (options.headers != null) {
params.add("headers", Serialization.toProtocol(options.headers));
}
if (options.postData != null) {
byte[] bytes = null;
if (options.postData instanceof byte[]) {
bytes = (byte[]) options.postData;
} else if (options.postData instanceof String) {
bytes = ((String) options.postData).getBytes(StandardCharsets.UTF_8);
} else {
throw new PlaywrightException("postData must be either String or byte[], found: " + options.postData.getClass().getName());
}
String base64 = Base64.getEncoder().encodeToString(bytes);
params.addProperty("postData", base64);
}
sendMessageAsync("continue", params);
}
private static byte[] getPostDataBytes(Object postData) {
if (postData instanceof byte[]) {
return (byte[]) postData;
}
if (postData instanceof String) {
return ((String) postData).getBytes(StandardCharsets.UTF_8);
}
throw new PlaywrightException("postData must be either String or byte[], found: " + postData.getClass().getName());
}
@Override
public void fulfill(FulfillOptions options) {
startHandling();
@@ -199,14 +168,6 @@ public class RouteImpl extends ChannelOwner implements Route {
return connection.getExistingObject(initializer.getAsJsonObject("request").get("guid").getAsString());
}
void redirectNavigationRequest(String redirectURL) {
startHandling();
JsonObject params = new JsonObject();
params.addProperty("url", redirectURL);
// TODO: _raceWithPageClose ?
sendMessageAsync("redirectNavigationRequest", params);
}
private void startHandling() {
if (handled) {
throw new PlaywrightException("Route is already handled!");
@@ -19,7 +19,6 @@ package com.microsoft.playwright.impl;
import com.microsoft.playwright.Route;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -38,7 +37,10 @@ class Router {
this.times = times;
}
boolean handle(RouteImpl route) {
boolean handle(Route route) {
if (times != null && times <= 0) {
return false;
}
if (!matcher.test(route.request().url())) {
return false;
}
@@ -68,21 +70,15 @@ class Router {
return routes.size();
}
enum HandleResult { NoMatchingHandler, FoundMatchingHandler}
HandleResult handle(RouteImpl route) {
HandleResult result = HandleResult.NoMatchingHandler;
for (Iterator<RouteInfo> it = routes.iterator(); it.hasNext();) {
RouteInfo info = it.next();
boolean handle(Route route) {
for (RouteInfo info : routes) {
if (info.handle(route)) {
result = HandleResult.FoundMatchingHandler;
if (info.isDone()) {
it.remove();
}
if (route.isHandled()) {
break;
routes.remove(info);
}
return true;
}
}
return result;
return false;
}
}
@@ -28,19 +28,12 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Type;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.time.Instant;
import java.util.*;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.Utils.toJsRegexFlags;
import static com.microsoft.playwright.impl.Utils.fromJsRegexFlags;
class Serialization {
private static final Gson gson = new GsonBuilder().disableHtmlEscaping()
private static final Gson gson = new GsonBuilder()
.registerTypeAdapter(SameSiteAttribute.class, new SameSiteAdapter().nullSafe())
.registerTypeAdapter(BrowserChannel.class, new ToLowerCaseAndDashSerializer<BrowserChannel>())
.registerTypeAdapter(ColorScheme.class, new ToLowerCaseAndDashSerializer<ColorScheme>())
@@ -51,7 +44,6 @@ class Serialization {
.registerTypeAdapter(ScreenshotType.class, new ToLowerCaseSerializer<ScreenshotType>())
.registerTypeAdapter(ScreenshotScale.class, new ToLowerCaseSerializer<ScreenshotScale>())
.registerTypeAdapter(ScreenshotCaret.class, new ToLowerCaseSerializer<ScreenshotCaret>())
.registerTypeAdapter(ServiceWorkerPolicy.class, new ToLowerCaseAndDashSerializer<ServiceWorkerPolicy>())
.registerTypeAdapter(MouseButton.class, new ToLowerCaseSerializer<MouseButton>())
.registerTypeAdapter(LoadState.class, new ToLowerCaseSerializer<LoadState>())
.registerTypeAdapter(WaitUntilState.class, new ToLowerCaseSerializer<WaitUntilState>())
@@ -148,14 +140,6 @@ class Serialization {
result.n = (Integer) value;
} else if (value instanceof String) {
result.s = (String) value;
} else if (value instanceof Date) {
result.d = ((Date)value).toInstant().toString();
} else if (value instanceof URL) {
result.u = ((URL)value).toString();
} else if (value instanceof Pattern) {
result.r = new SerializedValue.R();
result.r.p = ((Pattern)value).pattern();
result.r.f = toJsRegexFlags(((Pattern)value));
} else {
HashableValue mapKey = new HashableValue(value);
Integer id = valueToId.get(mapKey);
@@ -219,17 +203,6 @@ class Serialization {
return (T) value.b;
if (value.s != null)
return (T) value.s;
if (value.u != null) {
try {
return (T)(new URL(value.u));
} catch (MalformedURLException e) {
throw new PlaywrightException("Unexpected value: " + value.u, e);
}
}
if (value.d != null)
return (T)(Date.from(Instant.parse(value.d)));
if (value.r != null)
return (T)(Pattern.compile(value.r.p, fromJsRegexFlags(value.r.f)));
if (value.v != null) {
switch (value.v) {
case "undefined":
@@ -314,7 +287,9 @@ class Serialization {
static JsonArray toProtocol(ElementHandle[] handles) {
JsonArray jsonElements = new JsonArray();
for (ElementHandle handle : handles) {
jsonElements.add(((ElementHandleImpl) handle).toProtocolRef());
JsonObject jsonHandle = new JsonObject();
jsonHandle.addProperty("guid", ((ElementHandleImpl) handle).guid);
jsonElements.add(jsonHandle);
}
return jsonElements;
}
@@ -323,16 +298,6 @@ class Serialization {
return toNameValueArray(map);
}
static void addHarUrlFilter(JsonObject options, Object urlFilter) {
if (urlFilter instanceof String) {
options.addProperty("urlGlob", (String) urlFilter);
} else if (urlFilter instanceof Pattern) {
Pattern pattern = (Pattern) urlFilter;
options.addProperty("urlRegexSource", pattern.pattern());
options.addProperty("urlRegexFlags", toJsRegexFlags(pattern));
}
}
static JsonArray toNameValueArray(Map<String, ?> map) {
JsonArray array = new JsonArray();
for (Map.Entry<String, ?> e : map.entrySet()) {
@@ -344,15 +309,6 @@ class Serialization {
return array;
}
static Map<String, String> fromNameValues(JsonArray array) {
Map<String, String> map = new LinkedHashMap<>();
for (JsonElement element : array) {
JsonObject pair = element.getAsJsonObject();
map.put(pair.get("name").getAsString(), pair.get("value").getAsString());
}
return map;
}
static List<String> parseStringList(JsonArray array) {
List<String> result = new ArrayList<>();
for (JsonElement e : array) {
@@ -383,7 +339,9 @@ class Serialization {
private static class HandleSerializer implements JsonSerializer<JSHandleImpl> {
@Override
public JsonElement serialize(JSHandleImpl src, Type typeOfSrc, JsonSerializationContext context) {
return src.toProtocolRef();
JsonObject json = new JsonObject();
json.addProperty("guid", src.guid);
return json;
}
}
@@ -28,25 +28,18 @@ import java.util.*;
import java.util.stream.Collectors;
class StackTraceCollector {
static final String PLAYWRIGHT_JAVA_SRC = "PLAYWRIGHT_JAVA_SRC";
private final List<Path> srcDirs;
private final Map<Path, String> classToSourceCache = new HashMap<>();
static StackTraceCollector createFromEnv(Map<String, String> env) {
String srcRoots = null;
if (env != null) {
srcRoots = env.get(PLAYWRIGHT_JAVA_SRC);
}
if (srcRoots == null) {
srcRoots = System.getenv(PLAYWRIGHT_JAVA_SRC);
}
static StackTraceCollector createFromEnv() {
String srcRoots = System.getenv("PLAYWRIGHT_JAVA_SRC");
if (srcRoots == null) {
return null;
}
List<Path> srcDirs = Arrays.stream(srcRoots.split(File.pathSeparator)).map(p -> Paths.get(p)).collect(Collectors.toList());
for (Path srcDir: srcDirs) {
if (!Files.exists(srcDir.toAbsolutePath())) {
throw new PlaywrightException("Source location specified in " + PLAYWRIGHT_JAVA_SRC + " doesn't exist: '" + srcDir.toAbsolutePath() + "'");
throw new PlaywrightException("Source location specified in PLAYWRIGHT_JAVA_SRC doesn't exist: '" + srcDir.toAbsolutePath() + "'");
}
}
return new StackTraceCollector(srcDirs);
@@ -26,6 +26,7 @@ import java.nio.file.Path;
import static com.microsoft.playwright.impl.Serialization.gson;
class TracingImpl extends ChannelOwner implements Tracing {
LocalUtils localUtils;
boolean isRemote;
TracingImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
@@ -59,7 +60,7 @@ class TracingImpl extends ChannelOwner implements Tracing {
// Add local sources to the remote trace if necessary.
if (isRemote && json.has("sourceEntries")) {
JsonArray entries = json.getAsJsonArray("sourceEntries");
connection.localUtils.zip(path, entries);
localUtils.zip(path, entries);
}
}
@@ -176,18 +176,11 @@ class Utils {
} catch (IOException e) {
throw new PlaywrightException("Failed to copy file to remote server.", e);
}
jsonStreams.add(temp.toProtocolRef());
jsonStreams.add(temp.toProtocol());
}
params.add("streams", jsonStreams);
} else {
Path[] absolute = Arrays.stream(files).map(f -> {
try {
return f.toRealPath();
} catch (IOException e) {
throw new PlaywrightException("Cannot get absolute file path", e);
}
}).toArray(Path[]::new);
params.add("localPaths", toJsonArray(absolute));
params.add("localPaths", toJsonArray(files));
}
}
@@ -277,17 +270,6 @@ class Utils {
return map;
}
static List<HttpHeader> toHeadersList(Map<String, String> headers) {
List<HttpHeader> list = new ArrayList<>();
for (Map.Entry<String, String> entry: headers.entrySet()) {
HttpHeader header = new HttpHeader();
header.name = entry.getKey();
header.value = entry.getValue();
list.add(header);
}
return list;
}
static String toJsRegexFlags(Pattern pattern) {
String regexFlags = "";
if ((pattern.flags() & Pattern.CASE_INSENSITIVE) != 0) {
@@ -307,18 +289,4 @@ class Utils {
}
return regexFlags;
}
static int fromJsRegexFlags(String regexFlags) {
int flags = 0;
if (regexFlags.contains("i")) {
flags |= Pattern.CASE_INSENSITIVE;
}
if (regexFlags.contains("s")) {
flags |= Pattern.DOTALL;
}
if (regexFlags.contains("m")) {
flags |= Pattern.MULTILINE;
}
return flags;
}
}
@@ -72,9 +72,6 @@ class VideoImpl implements Video {
@Override
public void saveAs(Path path) {
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) {
@@ -30,4 +30,10 @@ class WritableStream extends ChannelOwner {
}
};
}
JsonObject toProtocol() {
JsonObject json = new JsonObject();
json.addProperty("guid", guid);
return json;
}
}
@@ -1,23 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.options;
public enum HarContentPolicy {
OMIT,
EMBED,
ATTACH
}
@@ -1,22 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.options;
public enum HarMode {
FULL,
MINIMAL
}
@@ -1,22 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.options;
public enum HarNotFound {
ABORT,
FALLBACK
}
@@ -19,8 +19,7 @@ package com.microsoft.playwright.options;
import com.microsoft.playwright.impl.RequestOptionsImpl;
/**
* The {@code RequestOptions} allows to create form data to be sent via {@code APIRequestContext}. Playwright will automatically
* determine content type of the request.
* The {@code RequestOptions} allows to create form data to be sent via {@code APIRequestContext}.
* <pre>{@code
* context.request().post(
* "https://example.com/submit",
@@ -28,33 +27,6 @@ import com.microsoft.playwright.impl.RequestOptionsImpl;
* .setQueryParam("page", 1)
* .setData("My data"));
* }</pre>
*
* <p> **Uploading html form data**
*
* <p> {@code FormData} class can be used to send a form to the server, by default the request will use
* {@code application/x-www-form-urlencoded} encoding:
* <pre>{@code
* context.request().post("https://example.com/signup", RequestOptions.create().setForm(
* FormData.create()
* .set("firstName", "John")
* .set("lastName", "Doe")));
* }</pre>
*
* <p> You can also send files as fields of an html form. The data will be encoded using <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST">{@code multipart/form-data}</a>:
* <pre>{@code
* Path path = Paths.get("members.csv");
* APIResponse response = context.request().post("https://example.com/upload_members",
* RequestOptions.create().setMultipart(FormData.create().set("membersList", path)));
* }</pre>
*
* <p> Alternatively, you can build the file payload manually:
* <pre>{@code
* FilePayload filePayload = new FilePayload("members.csv", "text/csv",
* "Alice, 33\nJohn, 35\n".getBytes(StandardCharsets.UTF_8));
* APIResponse response = context.request().post("https://example.com/upload_members",
* RequestOptions.create().setMultipart(FormData.create().set("membersList", filePayload)));
* }</pre>
*/
public interface RequestOptions {
/**
@@ -1,22 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.options;
public enum ServiceWorkerPolicy {
ALLOW,
BLOCK
}
@@ -195,7 +195,6 @@ public class Server implements HttpHandler {
String resourcePath = "resources" + path;
InputStream resource = getClass().getClassLoader().getResourceAsStream(resourcePath);
if (resource == null) {
exchange.getResponseHeaders().add("Content-Type", "text/plain");
exchange.sendResponseHeaders(404, 0);
try (Writer writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("File not found: " + resourcePath);
@@ -19,11 +19,8 @@ package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import org.opentest4j.AssertionFailedError;
import java.io.OutputStreamWriter;
import java.io.Writer;
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class TestAPIResponseAssertions extends TestBase {
@Test
@@ -41,62 +38,14 @@ public class TestAPIResponseAssertions extends TestBase {
@Test
void fail() {
APIResponse res = page.request().get(server.PREFIX + "/unknown");
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> assertThat(res).isOK());
assertTrue(e.getMessage().contains("→ GET " + server.PREFIX + "/unknown"), "Actual error: " + e.toString());
assertTrue(e.getMessage().contains("← 404 Not Found"), "Actual error: " + e.toString());
}
@Test
void shouldPrintResponseTextIfIdOkFails() {
APIResponse res = page.request().get(server.PREFIX + "/unknown");
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> assertThat(res).isOK());
assertTrue(e.getMessage().contains("File not found"), "Actual error: " + e.toString());
}
@Test
void shouldOnlyPrintResponseWithTextContentTypeIfIsOkFails() {
{
server.setRoute("/text-content-type", exchange -> {
exchange.getResponseHeaders().set("Content-type", "text/plain");
exchange.sendResponseHeaders(404, 0);
try (Writer writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("Text error");
}
});
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> assertThat(page.request().get(server.PREFIX + "/text-content-type")).isOK());
assertTrue(e.getMessage().contains("Text error"), "Actual error: " + e);
}
{
server.setRoute("/svg-xml-content-type", exchange -> {
exchange.getResponseHeaders().set("Content-type", "image/svg+xml");
exchange.sendResponseHeaders(404, 0);
try (Writer writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("Json error");
}
});
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> assertThat(page.request().get(server.PREFIX + "/svg-xml-content-type")).isOK());
assertTrue(e.getMessage().contains("Json error"), "Actual error: " + e);
}
{
server.setRoute("/no-content-type", exchange -> {
exchange.sendResponseHeaders(404, 0);
try (Writer writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("No content type error");
}
});
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> assertThat(page.request().get(server.PREFIX + "/no-content-type")).isOK());
assertFalse(e.getMessage().contains("No content type error"), "Actual error: " + e);
}
{
server.setRoute("/image-content-type", exchange -> {
exchange.getResponseHeaders().set("Content-type", "image/bmp");
exchange.sendResponseHeaders(404, 0);
try (Writer writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("Image type error");
}
});
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> assertThat(page.request().get(server.PREFIX + "/image-content-type")).isOK());
assertFalse(e.getMessage().contains("Image type error"), "Actual error: " + e);
boolean didThrow = false;
try {
assertThat(res).isOK();
} catch (AssertionFailedError e) {
didThrow = true;
assertTrue(e.getMessage().contains("→ GET " + server.PREFIX + "/unknown"), "Actual error: " + e.toString());
assertTrue(e.getMessage().contains("← 404 Not Found"), "Actual error: " + e.toString());
}
assertTrue(didThrow);
}
}
@@ -72,12 +72,8 @@ public class TestBase {
return options;
}
Playwright.CreateOptions playwrightOptions() {
return null;
}
void initBrowserType() {
playwright = Playwright.create(playwrightOptions());
playwright = Playwright.create();
browserType = Utils.getBrowserTypeFromEnv(playwright);
}
@@ -97,9 +97,4 @@ public class TestBrowser extends TestBase {
assertNotNull(browser);
browser.close();
}
@Test
void shouldReturnBrowserType() {
assertEquals(browserType, browser.browserType());
}
}
@@ -65,19 +65,13 @@ public class TestBrowserContextCookies extends TestBase {
" return document.cookie;\n" +
" }");
assertEquals("username=John Doe", documentCookie);
int timestamp = (Integer) page.evaluate("+(new Date('1/1/2038'))/1000");
Cookie cookie = context.cookies().get(0);
assertEquals("username", cookie.name);
assertEquals("John Doe", cookie.value);
assertEquals("localhost", cookie.domain);
assertEquals("/", cookie.path);
// Browsers start to cap cookies with 400 days max expires value.
// See https://github.com/httpwg/http-extensions/pull/1732
// Chromium patch: https://chromium.googlesource.com/chromium/src/+/aaa5d2b55478eac2ee642653dcd77a50ac3faff6
// We want to make sure that expires date is at least 400 days in future.
Double timestamp = (Double) page.evaluate("const FOUR_HUNDRED_DAYS = 1000 * 60 * 60 * 24 * 400;\n" +
" const FIVE_MINUTES = 1000 * 60 * 5; // relax condition a bit to make sure test is not flaky.\n" +
" (Date.now() + FOUR_HUNDRED_DAYS - FIVE_MINUTES) / 1000;");
assertTrue(cookie.expires > timestamp, cookie.expires + " > " + timestamp + " failed.");
assertEquals(timestamp, cookie.expires);
assertEquals(false, cookie.httpOnly);
assertEquals(false, cookie.secure);
if (isChromium()) {
@@ -1,457 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import com.microsoft.playwright.options.HarContentPolicy;
import com.microsoft.playwright.options.HarMode;
import com.microsoft.playwright.options.HarNotFound;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIf;
import org.junit.jupiter.api.io.TempDir;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.regex.Pattern;
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 org.junit.jupiter.api.Assertions.*;
public class TestBrowserContextHar extends TestBase {
@Test
void shouldContextRouteFromHARMatchingTheMethodAndFollowingRedirects() {
Path path = Paths.get("src/test/resources/har-fulfill.har");
context.routeFromHAR(path);
Page page = context.newPage();
page.navigate("http://no.playwright/");
// HAR contains a redirect for the script that should be followed automatically.
assertEquals("foo", page.evaluate("window.value"));
// HAR contains a POST for the css file that should not be used.
assertThat(page.locator("body")).hasCSS("background-color", "rgb(255, 0, 0)");
}
@Test
void shouldPageRouteFromHARMatchingTheMethodAndFollowingRedirects() {
Path path = Paths.get("src/test/resources/har-fulfill.har");
Page page = context.newPage();
page.routeFromHAR(path);
page.navigate("http://no.playwright/");
// HAR contains a redirect for the script that should be followed automatically.
assertEquals("foo", page.evaluate("window.value"));
// HAR contains a POST for the css file that should not be used.
assertThat(page.locator("body")).hasCSS("background-color", "rgb(255, 0, 0)");
}
@Test
void fallbackContinueShouldContinueWhenNotFoundInHar() {
Path path = Paths.get("src/test/resources/har-fulfill.har");
context.routeFromHAR(path, new BrowserContext.RouteFromHAROptions().setNotFound(HarNotFound.FALLBACK));
Page page = context.newPage();
page.navigate(server.PREFIX + "/one-style.html");
assertThat(page.locator("body")).hasCSS("background-color", "rgb(255, 192, 203)");
}
@Test
void byDefaultShouldAbortRequestsNotFoundInHar() {
Path path = Paths.get("src/test/resources/har-fulfill.har");
context.routeFromHAR(path);
Page page = context.newPage();
try {
page.navigate(server.EMPTY_PAGE);
fail("did not throw");
} catch (PlaywrightException e) {
}
}
@Test
void fallbackContinueShouldContinueRequestsOnBadHar(@TempDir Path tmpDir) throws IOException {
Path path = tmpDir.resolve("test.har");
try (Writer stream = new OutputStreamWriter(Files.newOutputStream(path))) {
stream.write("{ \"log\" : {} }");
}
context.routeFromHAR(path, new BrowserContext.RouteFromHAROptions().setNotFound(HarNotFound.FALLBACK));
Page page = context.newPage();
page.navigate(server.PREFIX + "/one-style.html");
assertThat(page.locator("body")).hasCSS("background-color", "rgb(255, 192, 203)");
}
@Test
void shouldOnlyHandleRequestsMatchingUrlFilter() {
Path path = Paths.get("src/test/resources/har-fulfill.har");
context.routeFromHAR(path, new BrowserContext.RouteFromHAROptions().setNotFound(HarNotFound.FALLBACK).setUrl("**/*.js"));
Page page = context.newPage();
context.route("http://no.playwright/", route -> {
assertEquals("http://no.playwright/", route.request().url());
route.fulfill(new Route.FulfillOptions()
.setStatus(200)
.setContentType("text/html")
.setBody("<script src='./script.js'></script><div>hello</div>"));
});
page.navigate("http://no.playwright/");
// HAR contains a redirect for the script that should be followed automatically.
assertEquals("foo", page.evaluate("window.value"));
assertThat(page.locator("body")).hasCSS("background-color", "rgba(0, 0, 0, 0)");
}
@Test
void shouldOnlyContextRouteFromHARRequestsMatchingUrlFilter() {
Path path = Paths.get("src/test/resources/har-fulfill.har");
context.routeFromHAR(path, new BrowserContext.RouteFromHAROptions().setUrl("**/*.js"));
Page page = context.newPage();
context.route("http://no.playwright/", route -> {
assertEquals("http://no.playwright/", route.request().url());
route.fulfill(new Route.FulfillOptions()
.setStatus(200)
.setContentType("text/html")
.setBody("<script src='./script.js'></script><div>hello</div>"));
});
page.navigate("http://no.playwright/");
// HAR contains a redirect for the script that should be followed automatically.
assertEquals("foo", page.evaluate("window.value"));
assertThat(page.locator("body")).hasCSS("background-color", "rgba(0, 0, 0, 0)");
}
@Test
void shouldOnlyPageRouteFromHARRequestsMatchingUrlFilter() {
Path path = Paths.get("src/test/resources/har-fulfill.har");
Page page = context.newPage();
page.routeFromHAR(path, new Page.RouteFromHAROptions().setUrl("**/*.js"));
context.route("http://no.playwright/", route -> {
assertEquals("http://no.playwright/", route.request().url());
route.fulfill(new Route.FulfillOptions()
.setStatus(200)
.setContentType("text/html")
.setBody("<script src='./script.js'></script><div>hello</div>"));
});
page.navigate("http://no.playwright/");
// HAR contains a redirect for the script that should be followed automatically.
assertEquals("foo", page.evaluate("window.value"));
assertThat(page.locator("body")).hasCSS("background-color", "rgba(0, 0, 0, 0)");
}
@Test
void shouldSupportRegexFilter() {
Path path = Paths.get("src/test/resources/har-fulfill.har");
context.routeFromHAR(path, new BrowserContext.RouteFromHAROptions().setUrl(Pattern.compile(".*(\\.js|.*\\.css|no.playwright\\/)$")));
Page page = context.newPage();
page.navigate("http://no.playwright/");
assertEquals("foo", page.evaluate("window.value"));
assertThat(page.locator("body")).hasCSS("background-color", "rgb(255, 0, 0)");
}
@Test
void newPageShouldFulfillFromHarMatchingTheMethodAndFollowingRedirects() {
Path path = Paths.get("src/test/resources/har-fulfill.har");
Page page = browser.newPage();
page.routeFromHAR(path);
page.navigate("http://no.playwright/");
// HAR contains a redirect for the script that should be followed automatically.
assertEquals("foo", page.evaluate("window.value"));
// HAR contains a POST for the css file that should not be used.
assertThat(page.locator("body")).hasCSS("background-color", "rgb(255, 0, 0)");
page.close();
}
@Test
void shouldChangeDocumentURLAfterRedirectedNavigation() {
Path path = Paths.get("src/test/resources/har-redirect.har");
context.routeFromHAR(path);
Page page = context.newPage();
Response response = page.waitForNavigation(() -> {
page.navigate("https://theverge.com/");
page.waitForURL("https://www.theverge.com/");
});
assertThat(page).hasURL("https://www.theverge.com/");
assertEquals("https://www.theverge.com/", response.request().url());
assertEquals("https://www.theverge.com/", page.evaluate("location.href"));
}
@Test
void shouldChangeDocumentURLAfterRedirectedNavigationOnClick() {
Path path = Paths.get("src/test/resources/har-redirect.har");
context.routeFromHAR(path, new BrowserContext.RouteFromHAROptions().setUrl(Pattern.compile(".*theverge.*")));
Page page = context.newPage();
page.navigate(server.EMPTY_PAGE);
page.setContent("<a href='https://theverge.com/'>click me</a>");
Response response = page.waitForNavigation(() -> page.click("text=click me"));
assertThat(page).hasURL("https://www.theverge.com/");
assertEquals("https://www.theverge.com/", response.request().url());
assertEquals("https://www.theverge.com/", page.evaluate("location.href"));
}
@Test
void shouldGoBackToRedirectedNavigation() {
Path path = Paths.get("src/test/resources/har-redirect.har");
context.routeFromHAR(path, new BrowserContext.RouteFromHAROptions().setUrl(Pattern.compile(".*theverge.*")));
Page page = context.newPage();
page.navigate("https://theverge.com/");
page.navigate(server.EMPTY_PAGE);
assertThat(page).hasURL(server.EMPTY_PAGE);
Response response = page.goBack();
assertThat(page).hasURL("https://www.theverge.com/");
assertEquals("https://www.theverge.com/", response.request().url());
assertEquals("https://www.theverge.com/", page.evaluate("location.href"));
}
@Test
@DisabledIf(value="isFirefox", disabledReason="Flaky in Firefox, upstream as well")
void shouldGoForwardToRedirectedNavigation() {
Path path = Paths.get("src/test/resources/har-redirect.har");
context.routeFromHAR(path, new BrowserContext.RouteFromHAROptions().setUrl(Pattern.compile(".*theverge.*")));
Page page = context.newPage();
page.navigate(server.EMPTY_PAGE);
assertThat(page).hasURL(server.EMPTY_PAGE);
page.navigate("https://theverge.com/");
assertThat(page).hasURL("https://www.theverge.com/");
page.goBack();
assertThat(page).hasURL(server.EMPTY_PAGE);
Response response = page.goForward();
assertThat(page).hasURL("https://www.theverge.com/");
assertEquals("https://www.theverge.com/", response.request().url());
assertEquals("https://www.theverge.com/", page.evaluate("() => location.href"));
}
@Test
void shouldReloadRedirectedNavigation() {
Path path = Paths.get("src/test/resources/har-redirect.har");
context.routeFromHAR(path, new BrowserContext.RouteFromHAROptions().setUrl(Pattern.compile(".*theverge.*")));
Page page = context.newPage();
page.navigate("https://theverge.com/");
assertThat(page).hasURL("https://www.theverge.com/");
Response response = page.reload();
assertThat(page).hasURL("https://www.theverge.com/");
assertEquals("https://www.theverge.com/", response.request().url());
assertEquals("https://www.theverge.com/", page.evaluate("() => location.href"));
}
@Test
void shouldFulfillFromHarWithContentInAFile() {
Path path = Paths.get("src/test/resources/har-sha1.har");
context.routeFromHAR(path);
Page page = context.newPage();
page.navigate("http://no.playwright/");
assertEquals("<html><head></head><body>Hello, world</body></html>", page.content());
}
@Test
void shouldRoundTripHarZip(@TempDir Path tmpDir) {
Path harPath = tmpDir.resolve("har.zip");
try (BrowserContext context1 = browser.newContext(new Browser.NewContextOptions()
.setRecordHarPath(harPath)
.setRecordHarMode(HarMode.MINIMAL))) {
Page page1 = context1.newPage();
page1.navigate(server.PREFIX + "/one-style.html");
}
try (BrowserContext context2 = browser.newContext()) {
context2.routeFromHAR(harPath, new BrowserContext.RouteFromHAROptions().setNotFound(HarNotFound.ABORT));
Page page2 = context2.newPage();
page2.navigate(server.PREFIX + "/one-style.html");
assertTrue(page2.content().contains("hello, world!"));
assertThat(page2.locator("body")).hasCSS("background-color", "rgb(255, 192, 203)");
}
}
@Test
void shouldProduceExtractedZip(@TempDir Path tmpDir) throws IOException {
Path harPath = tmpDir.resolve("har.har");
try (BrowserContext context1 = browser.newContext(new Browser.NewContextOptions()
.setRecordHarPath(harPath)
.setRecordHarMode(HarMode.MINIMAL)
.setRecordHarContent(ATTACH))) {
Page page1 = context1.newPage();
page1.navigate(server.PREFIX + "/one-style.html");
}
assertTrue(Files.exists(harPath));
String har = new String(Files.readAllBytes(harPath), StandardCharsets.UTF_8);
assertFalse(har.contains("background-color"));
try (BrowserContext context2 = browser.newContext()) {
context2.routeFromHAR(harPath, new BrowserContext.RouteFromHAROptions().setNotFound(HarNotFound.ABORT));
Page page2 = context2.newPage();
page2.navigate(server.PREFIX + "/one-style.html");
assertTrue(page2.content().contains("hello, world!"));
assertThat(page2.locator("body")).hasCSS("background-color", "rgb(255, 192, 203)");
}
}
@Test
void shouldRoundTripExtractedHarZip(@TempDir Path tmpDir) throws IOException {
Path harPath = tmpDir.resolve("har.zip");
try (BrowserContext context1 = browser.newContext(new Browser.NewContextOptions()
.setRecordHarPath(harPath)
.setRecordHarMode(HarMode.MINIMAL))) {
Page page1 = context1.newPage();
page1.navigate(server.PREFIX + "/one-style.html");
}
Path harDir = tmpDir.resolve("hardir");
extractZip(harPath, harDir);
try (BrowserContext context2 = browser.newContext()) {
context2.routeFromHAR(harDir.resolve("har.har"));
Page page2 = context2.newPage();
page2.navigate(server.PREFIX + "/one-style.html");
assertTrue(page2.content().contains("hello, world!"));
assertThat(page2.locator("body")).hasCSS("background-color", "rgb(255, 192, 203)");
}
}
@Test
void shouldRoundTripHarWithPostData(@TempDir Path tmpDir) {
server.setRoute("/echo", exchange -> {
exchange.sendResponseHeaders(200, 0);
try (OutputStream out = exchange.getResponseBody()) {
copy(exchange.getRequestBody(), out);
}
});
String fetchFunction = "async body => {\n" +
" const response = await fetch('/echo', { method: 'POST', body });\n" +
" return await response.text();\n" +
" }\n";
Path harPath = tmpDir.resolve("har.zip");
try (BrowserContext context1 = browser.newContext(new Browser.NewContextOptions()
.setRecordHarPath(harPath)
.setRecordHarMode(HarMode.MINIMAL))) {
Page page1 = context1.newPage();
page1.navigate(server.EMPTY_PAGE);
assertEquals("1", page1.evaluate(fetchFunction, "1"));
assertEquals("2", page1.evaluate(fetchFunction, "2"));
assertEquals("3", page1.evaluate(fetchFunction, "3"));
}
server.reset();
try (BrowserContext context2 = browser.newContext()) {
context2.routeFromHAR(harPath);
Page page2 = context2.newPage();
page2.navigate(server.EMPTY_PAGE);
assertEquals("1", page2.evaluate(fetchFunction, "1"));
assertEquals("2", page2.evaluate(fetchFunction, "2"));
assertEquals("3", page2.evaluate(fetchFunction, "3"));
assertEquals("3", page2.evaluate(fetchFunction, "3"));
try {
page2.evaluate(fetchFunction, "4");
fail("did not throw");
} catch (PlaywrightException e) {
}
}
}
@Test
void shouldDisambiguateByHeader(@TempDir Path tmpDir) {
server.setRoute("/echo", exchange -> {
exchange.sendResponseHeaders(200, 0);
try (OutputStream out = exchange.getResponseBody()) {
List<String> values = exchange.getRequestHeaders().get("baz");
try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write(values == null ? "<no header>" : String.join(", ", values));
}
}
});
String fetchFunction = "async bazValue => {\n" +
" const response = await fetch('/echo', {\n" +
" method: 'POST',\n" +
" body: '',\n" +
" headers: {\n" +
" foo: 'foo-value',\n" +
" bar: 'bar-value',\n" +
" baz: bazValue,\n" +
" }\n" +
" });\n" +
" return await response.text();\n" +
" }\n";
Path harPath = tmpDir.resolve("har.zip");
try (BrowserContext context1 = browser.newContext(new Browser.NewContextOptions()
.setRecordHarPath(harPath)
.setRecordHarMode(HarMode.MINIMAL))) {
Page page1 = context1.newPage();
page1.navigate(server.EMPTY_PAGE);
assertEquals("baz1", page1.evaluate(fetchFunction, "baz1"));
assertEquals("baz2", page1.evaluate(fetchFunction, "baz2"));
assertEquals("baz3", page1.evaluate(fetchFunction, "baz3"));
}
server.reset();
try (BrowserContext context2 = browser.newContext()) {
context2.routeFromHAR(harPath);
Page page2 = context2.newPage();
page2.navigate(server.EMPTY_PAGE);
assertEquals("baz1", page2.evaluate(fetchFunction, "baz1"));
assertEquals("baz2", page2.evaluate(fetchFunction, "baz2"));
assertEquals("baz3", page2.evaluate(fetchFunction, "baz3"));
assertEquals("baz1", page2.evaluate(fetchFunction, "baz4"));
}
}
@Test
void shouldUpdateHarZipForContext(@TempDir Path tmpDir) {
Path harPath = tmpDir.resolve("har.zip");
try (BrowserContext context1 = browser.newContext()) {
context1.routeFromHAR(harPath, new BrowserContext.RouteFromHAROptions().setUpdate(true));
Page page1 = context1.newPage();
page1.navigate(server.PREFIX + "/one-style.html");
}
try (BrowserContext context2 = browser.newContext()) {
context2.routeFromHAR(harPath, new BrowserContext.RouteFromHAROptions().setNotFound(HarNotFound.ABORT));
Page page2 = context2.newPage();
page2.navigate(server.PREFIX + "/one-style.html");
assertTrue(page2.content().contains("hello, world!"));
assertThat(page2.locator("body")).hasCSS("background-color", "rgb(255, 192, 203)");
}
}
@Test
void shouldUpdateHarZipForPage(@TempDir Path tmpDir) {
Path harPath = tmpDir.resolve("har.zip");
try (BrowserContext context1 = browser.newContext()) {
Page page1 = context1.newPage();
page1.routeFromHAR(harPath, new Page.RouteFromHAROptions().setUpdate(true));
page1.navigate(server.PREFIX + "/one-style.html");
}
try (BrowserContext context2 = browser.newContext()) {
Page page2 = context2.newPage();
page2.routeFromHAR(harPath, new Page.RouteFromHAROptions().setNotFound(HarNotFound.ABORT));
page2.navigate(server.PREFIX + "/one-style.html");
assertTrue(page2.content().contains("hello, world!"));
assertThat(page2.locator("body")).hasCSS("background-color", "rgb(255, 192, 203)");
}
}
@Test
void shouldUpdateExtractedHarZipForPage(@TempDir Path tmpDir) {
Path harPath = tmpDir.resolve("har.har");
try (BrowserContext context1 = browser.newContext()) {
Page page1 = context1.newPage();
page1.routeFromHAR(harPath, new Page.RouteFromHAROptions().setUpdate(true));
page1.navigate(server.PREFIX + "/one-style.html");
}
try (BrowserContext context2 = browser.newContext()) {
Page page2 = context2.newPage();
page2.routeFromHAR(harPath, new Page.RouteFromHAROptions().setNotFound(HarNotFound.ABORT));
page2.navigate(server.PREFIX + "/one-style.html");
assertTrue(page2.content().contains("hello, world!"));
assertThat(page2.locator("body")).hasCSS("background-color", "rgb(255, 192, 203)");
}
}
}
@@ -28,19 +28,19 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
public class TestBrowserContextLocale extends TestBase {
@Test
void shouldAffectAcceptLanguageHeader() throws ExecutionException, InterruptedException {
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("fr-FR"));
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("fr-CH"));
Page page = context.newPage();
Future<Server.Request> request = server.futureRequest("/empty.html");
page.navigate(server.EMPTY_PAGE);
assertEquals("fr-FR", request.get().headers.get("accept-language").get(0).substring(0, 5));
assertEquals("fr-CH", request.get().headers.get("accept-language").get(0).substring(0, 5));
context.close();
}
@Test
void shouldAffectNavigatorLanguage() {
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("fr-FR"));
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("fr-CH"));
Page page = context.newPage();
assertEquals("fr-FR", page.evaluate("() => navigator.language"));
assertEquals("fr-CH", page.evaluate("() => navigator.language"));
context.close();
}
@@ -55,7 +55,7 @@ public class TestBrowserContextLocale extends TestBase {
context.close();
}
{
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("fr-FR"));
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("fr-CH"));
Page page = context.newPage();
page.navigate(server.EMPTY_PAGE);
assertEquals("1 000 000,5", page.evaluate("() => (1000000.50).toLocaleString().replace(/\\s/g, ' ')"));
@@ -87,7 +87,7 @@ public class TestBrowserContextLocale extends TestBase {
@Test
void shouldFormatNumberInPopups() {
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("fr-FR"));
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("fr-CH"));
Page page = context.newPage();
page.navigate(server.EMPTY_PAGE);
Page popup = page.waitForPopup(() -> page.evaluate(
@@ -100,14 +100,14 @@ public class TestBrowserContextLocale extends TestBase {
@Test
void shouldAffectNavigatorLanguageInPopups() {
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("fr-FR"));
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("fr-CH"));
Page page = context.newPage();
page.navigate(server.EMPTY_PAGE);
Page popup = page.waitForPopup(() -> page.evaluate(
"url => window.open(url)", server.PREFIX + "/formatted-number.html"));
popup.waitForLoadState(LoadState.DOMCONTENTLOADED);
Object result = popup.evaluate("window.initialNavigatorLanguage");
assertEquals("fr-FR", result);
assertEquals("fr-CH", result);
context.close();
}
@@ -155,7 +155,7 @@ public class TestBrowserContextLocale extends TestBase {
defaultLocale = getContextLocale.apply(context);
context.close();
}
String localeOverride = "es-MX".equals(defaultLocale) ? "de-DE" : "es-MX";
String localeOverride = "ru-RU".equals(defaultLocale) ? "de-DE" : "ru-RU";
{
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale(localeOverride));
assertEquals(localeOverride, getContextLocale.apply(context));
@@ -19,7 +19,6 @@ package com.microsoft.playwright;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
@@ -63,28 +62,28 @@ public class TestBrowserContextRoute extends TestBase {
List<Integer> intercepted = new ArrayList<>();
context.route("**/*", route -> {
intercepted.add(1);
route.fallback();
route.resume();
});
context.route("**/empty.html", route -> {
intercepted.add(2);
route.fallback();
route.resume();
});
context.route("**/empty.html", route -> {
intercepted.add(3);
route.fallback();
route.resume();
});
Consumer<Route> handler4 = route -> {
intercepted.add(4);
route.fallback();
route.resume();
};
context.route("**/empty.html", handler4);
page.navigate(server.EMPTY_PAGE);
assertEquals(asList(4, 3, 2, 1), intercepted);
assertEquals(asList(4), intercepted);
intercepted.clear();
context.unroute("**/empty.html", handler4);
page.navigate(server.EMPTY_PAGE);
assertEquals(asList(3, 2, 1), intercepted);
assertEquals(asList(3), intercepted);
intercepted.clear();
context.unroute("**/empty.html");
@@ -208,118 +207,4 @@ public class TestBrowserContextRoute extends TestBase {
assertTrue(e.getMessage().contains("New URL must have same protocol as overridden URL"), e.getMessage());
}
}
@Test
void shouldChainFallback() {
List<Integer> intercepted = new ArrayList<>();
context.route("**/empty.html", route -> {
intercepted.add(1);
route.fallback();
});
context.route("**/empty.html", route -> {
intercepted.add(2);
route.fallback();
});
context.route("**/empty.html", route -> {
intercepted.add(3);
route.fallback();
});
page.navigate(server.EMPTY_PAGE);
assertEquals(asList(3, 2, 1), intercepted);
}
@Test
void shouldNotChainFulfill() {
boolean[] failed = {false};
context.route("**/empty.html", route -> {
failed[0] = true;
});
context.route("**/empty.html", route -> {
route.fulfill(new Route.FulfillOptions().setStatus(200).setBody("fulfilled"));
});
context.route("**/empty.html", route -> {
route.fallback();
});
Response response = page.navigate(server.EMPTY_PAGE);
byte[] body = response.body();
assertEquals("fulfilled", new String(body, StandardCharsets.UTF_8));
assertFalse(failed[0]);
}
@Test
void shouldNotChainAbort() {
boolean[] failed = {false};
context.route("**/empty.html", route -> {
failed[0] = true;
});
context.route("**/empty.html", route -> {
route.abort();
});
context.route("**/empty.html", route -> {
route.fallback();
});
try {
page.navigate(server.EMPTY_PAGE);
fail("did not throw");
} catch (PlaywrightException e) {
assertNotNull(e);
}
assertFalse(failed[0]);
}
@Test
void shouldChainFallbackIntoPage() {
List<Integer> intercepted = new ArrayList<>();
context.route("**/empty.html", route -> {
intercepted.add(1);
route.fallback();
});
context.route("**/empty.html", route -> {
intercepted.add(2);
route.fallback();
});
context.route("**/empty.html", route -> {
intercepted.add(3);
route.fallback();
});
page.route("**/empty.html", route -> {
intercepted.add(4);
route.fallback();
});
page.route("**/empty.html", route -> {
intercepted.add(5);
route.fallback();
});
page.route("**/empty.html", route -> {
intercepted.add(6);
route.fallback();
});
page.navigate(server.EMPTY_PAGE);
assertEquals(asList(6, 5, 4, 3, 2, 1), intercepted);
}
@Test
void shouldFallBackAsync() {
List<Integer> intercepted = new ArrayList<>();
context.route("**/empty.html", route -> {
intercepted.add(1);
page.waitForTimeout(50);
route.fallback();
});
context.route("**/empty.html", route -> {
intercepted.add(2);
page.waitForTimeout(100);
route.fallback();
});
context.route("**/empty.html", route -> {
intercepted.add(3);
page.waitForTimeout(150);
route.fallback();
});
page.navigate(server.EMPTY_PAGE);
assertEquals(asList(3, 2, 1), intercepted);
}
}
@@ -1,42 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import com.microsoft.playwright.options.ServiceWorkerPolicy;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class TestBrowserContextServiceWorkerPolicy extends TestBase {
@Test
void shouldAllowServiceWorkersByDefault() {
page.navigate(server.PREFIX + "/serviceworkers/empty/sw.html");
assertNotNull(page.evaluate("() => window['registrationPromise']"));
}
@Test
void blocksServiceWorkerRegistration() {
try (BrowserContext context = browser.newContext(new Browser.NewContextOptions().setServiceWorkers(ServiceWorkerPolicy.BLOCK))) {
Page page = context.newPage();
ConsoleMessage message = page.waitForConsoleMessage(new Page.WaitForConsoleMessageOptions()
.setPredicate(m -> "Service Worker registration blocked by Playwright".equals(m.text())),
() -> page.navigate(server.PREFIX + "/serviceworkers/empty/sw.html"));
assertNotNull(message);
}
}
}
@@ -16,7 +16,8 @@
package com.microsoft.playwright;
import com.microsoft.playwright.impl.driver.Driver;
import com.microsoft.playwright.impl.Driver;
import com.microsoft.playwright.options.WaitForSelectorState;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
@@ -498,7 +499,7 @@ public class TestBrowserTypeConnect extends TestBase {
Path trace = tmpDir.resolve("trace1.zip");
context.tracing().stop(new Tracing.StopOptions().setPath(trace));
Map<String, byte[]> entries = parseZip(trace);
Map<String, byte[]> entries = parseTrace(trace);
Map<String, byte[]> sources = entries.entrySet().stream().filter(e -> e.getKey().endsWith(".txt")).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
assertEquals(1, sources.size());
@@ -82,8 +82,8 @@ public class TestDefaultBrowserContext2 extends TestBase {
@Test
void shouldSupportLocaleOption() {
Page page = launchPersistent(new BrowserType.LaunchPersistentContextOptions()
.setLocale("fr-FR"));
assertEquals("fr-FR", page.evaluate("navigator.language"));
.setLocale("fr-CH"));
assertEquals("fr-CH", page.evaluate("navigator.language"));
}
@Test
@@ -16,56 +16,47 @@
package com.microsoft.playwright;
import com.google.gson.*;
import com.microsoft.playwright.options.HarContentPolicy;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.api.condition.DisabledIf;
import org.junit.jupiter.api.condition.EnabledIf;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import java.util.regex.Pattern;
import java.time.Instant;
import static com.microsoft.playwright.Utils.getOS;
import static com.microsoft.playwright.options.LoadState.DOMCONTENTLOADED;
import static org.junit.jupiter.api.Assertions.*;
public class TestHar extends TestBase {
private PageWithHar pageWithHar;
private static JsonObject parseHar(Path harFile) throws IOException {
try (FileReader json = new FileReader(harFile.toFile())) {
return new Gson().fromJson(json, JsonObject.class).getAsJsonObject("log");
}
}
private class PageWithHar {
final Path harFile;
final BrowserContext context;
final Page page;
PageWithHar() throws IOException {
this(new Browser.NewContextOptions(), null);
}
PageWithHar(Browser.NewContextOptions options, Path harFilePath) throws IOException {
harFile = harFilePath == null ? Files.createTempFile("test-", ".har") : harFilePath;
context = browser.newContext(options
harFile = Files.createTempFile("test-", ".har");
context = browser.newContext(new Browser.NewContextOptions()
.setRecordHarPath(harFile).setIgnoreHTTPSErrors(true));
page = context.newPage();
}
JsonObject log() throws IOException {
context.close();
return parseHar(harFile);
}
Map<String, byte[]> parseZip() throws IOException {
context.close();
return Utils.parseZip(harFile);
try (FileReader json = new FileReader(harFile.toFile())) {
return new Gson().fromJson(json, JsonObject.class).getAsJsonObject("log");
}
}
void dispose() throws IOException {
@@ -185,144 +176,4 @@ public class TestHar extends TestBase {
}
assertTrue(foundUserContentType);
}
@Test
void shouldFilterByGlob(@TempDir Path tmpDir) throws IOException {
Path harPath = tmpDir.resolve("test.har");
BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.setBaseURL(server.PREFIX)
.setRecordHarPath(harPath)
.setRecordHarUrlFilter("/*.css")
.setIgnoreHTTPSErrors(true));
Page page = context.newPage();
page.navigate("/har.html");
context.close();
JsonObject log = parseHar(harPath);
JsonArray entries = log.getAsJsonArray("entries");
// There are 2 entries for the same .css request in firefox.
if (isFirefox()) {
assertEquals(2, entries.size());
} else {
assertEquals(1, entries.size());
}
assertTrue(entries.get(0).getAsJsonObject().getAsJsonObject("request").get("url").getAsString().endsWith("one-style.css"));
}
@Test
void shouldFilterByRegexp(@TempDir Path tmpDir) throws IOException {
Path harPath = tmpDir.resolve("test.har");
BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.setBaseURL(server.PREFIX)
.setRecordHarPath(harPath)
.setRecordHarUrlFilter(Pattern.compile("HAR.X?HTML", Pattern.CASE_INSENSITIVE))
.setIgnoreHTTPSErrors(true));
Page page = context.newPage();
page.navigate(server.PREFIX + "/har.html");
context.close();
JsonObject log = parseHar(harPath);
JsonArray entries = log.getAsJsonArray("entries");
assertEquals(1, entries.size());
assertTrue(entries.get(0).getAsJsonObject().getAsJsonObject("request").get("url").getAsString().endsWith("har.html"));
}
@Test
void shouldOmitContent(@TempDir Path tmpDir) throws IOException {
Path harPath = tmpDir.resolve("test.har");
PageWithHar pageWithHar = new PageWithHar(new Browser.NewContextOptions()
.setRecordHarContent(HarContentPolicy.OMIT), harPath);
pageWithHar.page.navigate(server.PREFIX + "/har.html");
pageWithHar.page.evaluate("() => fetch('/pptr.png').then(r => r.arrayBuffer())");
JsonObject log = pageWithHar.log();
pageWithHar.dispose();
JsonArray entries = log.getAsJsonArray("entries");
assertFalse(entries.get(0).getAsJsonObject()
.getAsJsonObject("response")
.getAsJsonObject("content")
.has("text"));
assertFalse(entries.get(0).getAsJsonObject()
.getAsJsonObject("response")
.getAsJsonObject("content")
.has("_file"));
}
@Test
void shouldOmitContentLegacy(@TempDir Path tmpDir) throws IOException {
Path harPath = tmpDir.resolve("test.har");
PageWithHar pageWithHar = new PageWithHar(new Browser.NewContextOptions()
.setRecordHarOmitContent(true), harPath);
pageWithHar.page.navigate(server.PREFIX + "/har.html");
pageWithHar.page.evaluate("() => fetch('/pptr.png').then(r => r.arrayBuffer())");
JsonObject log = pageWithHar.log();
pageWithHar.dispose();
JsonArray entries = log.getAsJsonArray("entries");
assertFalse(entries.get(0).getAsJsonObject()
.getAsJsonObject("response")
.getAsJsonObject("content")
.has("text"));
assertFalse(entries.get(0).getAsJsonObject()
.getAsJsonObject("response")
.getAsJsonObject("content")
.has("_file"));
}
@Test
void shouldAttachContent(@TempDir Path tmpDir) throws IOException {
Path harPath = tmpDir.resolve("test.har.zip");
PageWithHar pageWithHar = new PageWithHar(new Browser.NewContextOptions()
.setRecordHarContent(HarContentPolicy.ATTACH), harPath);
pageWithHar.page.navigate(server.PREFIX + "/har.html");
pageWithHar.page.evaluate("() => fetch('/pptr.png').then(r => r.arrayBuffer())");
Map<String, byte[]> zip = pageWithHar.parseZip();
JsonObject log = new Gson().fromJson(new InputStreamReader(new ByteArrayInputStream(zip.get("har.har"))), JsonObject.class).getAsJsonObject("log");
pageWithHar.dispose();
JsonArray entries = log.getAsJsonArray("entries");
{
JsonObject content = firstEntryFor(entries, "har.html")
.getAsJsonObject("response")
.getAsJsonObject("content");
assertFalse(content.has("encoding"));
assertEquals("text/html", content.get("mimeType").getAsString());
assertTrue(content.get("_file").getAsString().contains("75841480e2606c03389077304342fac2c58ccb1b"));
assertTrue(content.get("size").getAsInt() >= 96);
assertEquals(0, content.get("compression").getAsInt());
}
{
// TODO: figure out why there is more than one entry in Firefox.
JsonObject content = firstEntryFor(entries, "one-style.css")
.getAsJsonObject("response")
.getAsJsonObject("content");
assertFalse(content.has("encoding"));
assertEquals("text/css", content.get("mimeType").getAsString());
assertTrue(content.get("_file").getAsString().contains("79f739d7bc88e80f55b9891a22bf13a2b4e18adb"));
assertTrue(content.get("size").getAsInt() >= 37);
assertEquals(0, content.get("compression").getAsInt());
}
{
JsonObject content = firstEntryFor(entries, "pptr.png")
.getAsJsonObject("response")
.getAsJsonObject("content");
assertFalse(content.has("encoding"));
assertEquals("image/png", content.get("mimeType").getAsString());
assertTrue(content.get("_file").getAsString().contains("a4c3a18f0bb83f5d9fe7ce561e065c36205762fa"));
assertTrue(content.get("size").getAsInt() >= 6000);
assertEquals(0, content.get("compression").getAsInt());
}
assertTrue(new String(zip.get("75841480e2606c03389077304342fac2c58ccb1b.html"), StandardCharsets.UTF_8).contains("HAR Page"));
assertTrue(new String(zip.get("79f739d7bc88e80f55b9891a22bf13a2b4e18adb.css"), StandardCharsets.UTF_8).contains("pink"));
assertEquals(firstEntryFor(entries, "pptr.png")
.getAsJsonObject("response")
.getAsJsonObject("content")
.get("size").getAsInt(), zip.get("a4c3a18f0bb83f5d9fe7ce561e065c36205762fa.png").length);
}
private static JsonObject firstEntryFor(JsonArray entries, String name) {
for (int i = 0; i < entries.size(); i++) {
JsonObject entry = entries.get(i).getAsJsonObject();
String url = entry.getAsJsonObject("request").get("url").getAsString();
if (url.endsWith(name)) {
return entry;
}
}
return null;
}
}
@@ -1,46 +0,0 @@
package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import java.util.stream.Collectors;
import static com.microsoft.playwright.Utils.mapOf;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class TestJavaSourceLocationInConstructor extends TestBase {
private static final String SRC_DIRS = System.getenv("PLAYWRIGHT_JAVA_SRC") == null ? "src/test/java" : System.getenv("PLAYWRIGHT_JAVA_SRC");
@Override
Playwright.CreateOptions playwrightOptions() {
return new Playwright.CreateOptions().setEnv(mapOf("PLAYWRIGHT_JAVA_SRC", SRC_DIRS));
}
@Test
void shouldSupportSourcesLocationPassedToPlaywrightCreate(@TempDir Path tmpDir) throws IOException {
context.tracing().start(new Tracing.StartOptions().setSources(true));
page.navigate(server.EMPTY_PAGE);
page.setContent("<button>Click</button>");
page.click("'Click'");
Path trace = tmpDir.resolve("trace1.zip");
context.tracing().stop(new Tracing.StopOptions().setPath(trace));
Map<String, byte[]> entries = Utils.parseZip(trace);
Map<String, byte[]> sources = entries.entrySet().stream().filter(e -> e.getKey().endsWith(".txt")).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
assertEquals(1, sources.size());
String path = getClass().getName().replace('.', File.separatorChar);
String[] srcRoots = SRC_DIRS.split(File.pathSeparator);
// Resolve in the last specified source dir.
Path sourceFile = Paths.get(srcRoots[srcRoots.length - 1], path + ".java");
byte[] thisFile = Files.readAllBytes(sourceFile);
assertEquals(new String(thisFile, UTF_8), new String(sources.values().iterator().next(), UTF_8));
}
}
@@ -17,7 +17,6 @@
package com.microsoft.playwright;
import com.microsoft.playwright.assertions.LocatorAssertions;
import com.microsoft.playwright.assertions.PlaywrightAssertions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.opentest4j.AssertionFailedError;
@@ -73,28 +72,6 @@ public class TestLocatorAssertions extends TestBase {
}
}
@Test
void containsTextWTextPass() {
page.setContent("<div id=node>Text content</div>");
Locator locator = page.locator("#node");
assertThat(locator).containsText("Text");
// Should normalize whitespace.
assertThat(locator).containsText(" ext cont\n ");
// Should support ignoreCase.
assertThat(locator).containsText("EXT", new LocatorAssertions.ContainsTextOptions().setIgnoreCase(true));
// Should support falsy ignoreCase.
assertThat(locator).not().containsText("TEXT", new LocatorAssertions.ContainsTextOptions().setIgnoreCase(false));
}
@Test
void containsTextWTextArrayPass() {
page.setContent("<div>Text \n1</div><div>Text2</div><div>Text3</div>");
Locator locator = page.locator("div");
assertThat(locator).containsText(new String[] {"ext 1", "ext3"});
// Should support ignoreCase.
assertThat(locator).containsText(new String[] {"EXT 1", "eXt3"}, new LocatorAssertions.ContainsTextOptions().setIgnoreCase(true));
}
@Test
void hasTextWRegexPass() {
page.setContent("<div id=node>Text content</div>");
@@ -102,10 +79,6 @@ public class TestLocatorAssertions extends TestBase {
assertThat(locator).hasText(Pattern.compile("Te.t"));
// Should not normalize whitespace.
assertThat(locator).hasText(Pattern.compile("Text.+content"));
// Should respect ignoreCase.
assertThat(locator).hasText(Pattern.compile("text content"), new LocatorAssertions.HasTextOptions().setIgnoreCase(true));
// Should override regex flag with ignoreCase.
assertThat(locator).not().hasText(Pattern.compile("text content", Pattern.CASE_INSENSITIVE), new LocatorAssertions.HasTextOptions().setIgnoreCase(false));
}
@Test
@@ -128,10 +101,6 @@ public class TestLocatorAssertions extends TestBase {
Locator locator = page.locator("#node");
// Should normalize whitespace.
assertThat(locator).hasText("Text content");
// Should support ignoreCase.
assertThat(locator).hasText("text CONTENT", new LocatorAssertions.HasTextOptions().setIgnoreCase(true));
// Should support falsy ignoreCase.
assertThat(locator).not().hasText("TEXT", new LocatorAssertions.HasTextOptions().setIgnoreCase(false));
}
@Test
@@ -162,8 +131,6 @@ public class TestLocatorAssertions extends TestBase {
Locator locator = page.locator("div");
// Should normalize whitespace.
assertThat(locator).hasText(new String[] {"Text 1", "Text 2a"});
// Should support ignoreCase.
assertThat(locator).hasText(new String[] {"tEXT 1", "TExt 2A"}, new LocatorAssertions.HasTextOptions().setIgnoreCase(true));
}
@Test
@@ -582,109 +549,6 @@ public class TestLocatorAssertions extends TestBase {
}
}
@Test
void hasValuesWorksWithText() {
page.setContent("<select multiple>\n" +
" <option value=\"R\">Red</option>\n" +
" <option value=\"G\">Green</option>\n" +
" <option value=\"B\">Blue</option>\n" +
" </select>");
Locator locator = page.locator("select");
locator.selectOption(new String[] {"R", "G"});
assertThat(locator).hasValues(new String[]{"R", "G"});
}
@Test
void hasValuesFollowsLabels() {
page.setContent("<label for=\"colors\">Pick a Color</label>\n" +
" <select id=\"colors\" multiple>\n" +
" <option value=\"R\">Red</option>\n" +
" <option value=\"G\">Green</option>\n" +
" <option value=\"B\">Blue</option>\n" +
" </select>");
Locator locator = page.locator("text=Pick a Color");
locator.selectOption(new String[] {"R", "G"});
assertThat(locator).hasValues(new String[]{"R", "G"});
}
@Test
void hasValuesExactMatchWithText() {
page.setContent("<select multiple>\n" +
" <option value=\"RR\">Red</option>\n" +
" <option value=\"GG\">Green</option>\n" +
" </select>");
Locator locator = page.locator("select");
locator.selectOption(new String[] {"RR", "GG"});
try {
assertThat(locator).hasValues(new String[]{"R", "G"}, new LocatorAssertions.HasValuesOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("[R, G]", e.getExpected().getStringRepresentation());
assertEquals("[RR, GG]", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have values"), e.getMessage());
}
}
@Test
void hasValuesWorksWithRegex() {
page.setContent("<select multiple>\n" +
" <option value=\"R\">Red</option>\n" +
" <option value=\"G\">Green</option>\n" +
" <option value=\"B\">Blue</option>\n" +
" </select>");
Locator locator = page.locator("select");
locator.selectOption(new String[] {"R", "G"});
assertThat(locator).hasValues(new Pattern[]{ Pattern.compile("R"), Pattern.compile("G")});
}
@Test
void hasValuesFailsWhenItemsNotSelected() {
page.setContent("<select multiple>\n" +
" <option value=\"R\">Red</option>\n" +
" <option value=\"G\">Green</option>\n" +
" <option value=\"B\">Blue</option>\n" +
" </select>");
Locator locator = page.locator("select");
locator.selectOption(new String[] {"B"}, new Locator.SelectOptionOptions().setTimeout(1000));
try {
assertThat(locator).hasValues(new Pattern[]{ Pattern.compile("R"), Pattern.compile("G")});
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("[R, G]", e.getExpected().getStringRepresentation());
assertEquals("[B]", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have values matching regex"), e.getMessage());
}
}
@Test
void hasValuesFailsWhenMultipleNotSpecified() {
page.setContent("<select>\n" +
" <option value=\"R\">Red</option>\n" +
" <option value=\"G\">Green</option>\n" +
" <option value=\"B\">Blue</option>\n" +
" </select>");
Locator locator = page.locator("select");
locator.selectOption(new String[] {"B"});
try {
assertThat(locator).hasValues(new Pattern[]{ Pattern.compile("R"), Pattern.compile("G")});
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Not a select element with a multiple attribute"), e.getMessage());
}
}
@Test
void hasValuesFailsWhenNotASelectElement() {
page.setContent("<input value=\"foo\" />");
Locator locator = page.locator("input");
try {
assertThat(locator).hasValues(new Pattern[]{ Pattern.compile("R"), Pattern.compile("G")}, new LocatorAssertions.HasValuesOptions().setTimeout(1000));
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Not a select element with a multiple attribute"), e.getMessage());
}
}
@Test
void isCheckedPass() {
page.setContent("<input type=checkbox checked></input>");
@@ -981,35 +845,4 @@ public class TestLocatorAssertions extends TestBase {
page.locator("#searchResultTableDiv .x-grid3-row").count();
assertThat(page.locator("#searchResultTableDiv .x-grid3-row")).hasCount(0);
}
@Test
void defaultTimeoutHasTextFail() {
page.setContent("<div></div>");
Locator locator = page.locator("div");
PlaywrightAssertions.setDefaultAssertionTimeout(1000);
AssertionFailedError exception = assertThrows(AssertionFailedError.class, () -> assertThat(locator).hasText("foo"));
assertTrue(exception.getMessage().contains("Locator.expect with timeout 1000ms"), exception.getMessage());
// Restore default.
PlaywrightAssertions.setDefaultAssertionTimeout(5_000);
}
@Test
void defaultTimeoutHasTextPass() {
page.setContent("<div>foo</div>");
Locator locator = page.locator("div");
PlaywrightAssertions.setDefaultAssertionTimeout(1000);
assertThat(locator).hasText("foo");
// Restore default.
PlaywrightAssertions.setDefaultAssertionTimeout(5_000);
}
@Test
void defaultTimeoutZeroHasTextPass() {
page.setContent("<div>foo</div>");
Locator locator = page.locator("div");
PlaywrightAssertions.setDefaultAssertionTimeout(0);
assertThat(locator).hasText("foo");
// Restore default.
PlaywrightAssertions.setDefaultAssertionTimeout(5_000);
}
}
@@ -19,9 +19,8 @@ package com.microsoft.playwright;
import com.microsoft.playwright.options.WaitForSelectorState;
import org.junit.jupiter.api.Test;
import java.util.regex.Pattern;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class TestLocatorMisc extends TestBase{
@Test
@@ -50,22 +49,4 @@ public class TestLocatorMisc extends TestBase{
page.evalOnSelector("div", "div => setTimeout(() => div.innerHTML = '', 500)");
locator.waitFor(new Locator.WaitForOptions().setState(WaitForSelectorState.HIDDEN));
}
@Test
void locatorsHasDoesNotEncodeUnicode() {
page.navigate(server.EMPTY_PAGE);
Locator[] locators = new Locator[]{
page.locator("button", new Page.LocatorOptions().setHasText("Драматург")),
page.locator("button", new Page.LocatorOptions().setHasText(Pattern.compile("Драматург"))),
page.locator("button", new Page.LocatorOptions().setHas(page.locator("text=Драматург")))
};
for (Locator locator: locators) {
try {
locator.click(new Locator.ClickOptions().setTimeout(100));
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Драматург"), e.getMessage());
}
}
}
}
@@ -1,66 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import com.microsoft.playwright.options.LoadState;
import org.junit.jupiter.api.Test;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.List;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class TestPageAutowaitingBasic extends TestBase {
@Test
void shouldWorkWithNoWaitAfterTrue() {
server.setRoute("/empty.html", exchange -> {});
page.setContent("<a id='anchor' href='${server.EMPTY_PAGE}'>empty.html</a>");
page.click("a", new Page.ClickOptions().setNoWaitAfter(true));
}
@Test
void shouldWorkWithDblclickNoWaitAfterTrue() {
server.setRoute("/empty.html", exchange -> {});
page.setContent("<a id='anchor' href='${server.EMPTY_PAGE}'>empty.html</a>");
page.dblclick("a", new Page.DblclickOptions().setNoWaitAfter(true));
}
@Test
void shouldWorkWithWaitForLoadStateLoad() {
List<String> messages = new ArrayList<>();
server.setRoute("/empty.html", exchange -> {
messages.add("route");
exchange.getResponseHeaders().add("content-type", "text/html");
exchange.sendResponseHeaders(200, 0);
try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("<link rel='stylesheet' href='./one-style.css'>");
} catch (RuntimeException e) {
e.printStackTrace();
throw e;
}
});
page.setContent("<a id='anchor' href='" + server.EMPTY_PAGE + "'>empty.html</a>");
page.onLoad(p -> messages.add("clickload"));
page.click("a");
page.waitForLoadState(LoadState.LOAD);
messages.add("load");
assertEquals(asList("route", "clickload", "load"), messages);
}
}
@@ -39,7 +39,7 @@ public class TestPageBasic extends TestBase {
newPage.evaluate("() => new Promise(r => {})");
fail("evaluate should throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Target page, context or browser has been closed"), e.getMessage());
assertTrue(e.getMessage().contains("Target closed"), e.getMessage());
}
}
@@ -164,24 +164,6 @@ public class TestPageBasic extends TestBase {
page.waitForLoadState(DOMCONTENTLOADED);
}
@Test
void shouldPassSelfAsArgumentToDomcontentloadedEvent() {
Page[] eventPage = {null};
page.onDOMContentLoaded(p -> eventPage[0] = p);
page.navigate("about:blank");
page.waitForLoadState(DOMCONTENTLOADED);
assertEquals(page, eventPage[0]);
}
@Test
void shouldPassSelfAsArgumentToLoadEvent() {
Page[] eventPage = {null};
page.onLoad(p -> eventPage[0] = p);
page.navigate("about:blank");
page.waitForLoadState(LOAD);
assertEquals(page, eventPage[0]);
}
// TODO: downloads
void shouldFailWithErrorUponDisconnect() {
}
@@ -20,11 +20,6 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIf;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.Date;
import java.net.MalformedURLException;
import java.net.URL;
import java.time.ZonedDateTime;
import static com.microsoft.playwright.Utils.mapOf;
import static java.util.Arrays.asList;
@@ -579,56 +574,20 @@ public class TestPageEvaluate extends TestBase {
assertTrue(((String) error).contains("Error: error message"));
}
@Test
void shouldEvaluateDate() {
Object result = page.evaluate("() => ({ date: new Date('2020-05-27T01:31:38.506Z') })");
Date expected = Date.from(ZonedDateTime.parse("2020-05-27T01:31:38.506Z").toInstant());
assertEquals(mapOf("date", expected), result);
// TODO: Date values are not supported in java.
}
@Test
void shouldRoundtripDate() {
Date date = Date.from(ZonedDateTime.parse("2020-05-27T01:31:38.506Z").toInstant());
Object result = page.evaluate("date => date", date);
assertTrue(result instanceof Date);
assertEquals(date.toString(), result.toString());
// TODO: Date values are not supported in java.
}
@Test
void shouldRoundtripRegex() {
Pattern regex = Pattern.compile("hello", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
Object result = page.evaluate("regex => regex", regex);
assertTrue(result instanceof Pattern);
assertEquals(regex.toString(), result.toString());
assertEquals(regex.flags(), ((Pattern)result).flags());
// Not applicable
}
@Test
void shouldJsonValueDate() {
JSHandle resultHandle = page.evaluateHandle("() => ({ date: new Date('2020-05-27T01:31:38.506Z') })");
assertEquals(mapOf("date", Date.from(ZonedDateTime.parse("2020-05-27T01:31:38.506Z").toInstant())), resultHandle.jsonValue());
}
@Test
void shouldEvaluateUrl() throws MalformedURLException {
Object result = page.evaluate("() => ({ url: new URL('https://example.com/') })");
assertEquals(mapOf("url", new URL("https://example.com/")), result);
}
@Test
void shouldRoundtripUrl() throws MalformedURLException {
URL url = new URL("https://example.com/");
Object result = page.evaluate("url => url", url);
assertTrue(result instanceof URL);
assertEquals(url.toString(), result.toString());
}
@Test
void shouldRoundtripComplexUrl() throws MalformedURLException {
URL url = new URL("https://user:password@www.contoso.com:80/Home/Index.htm?q1=v1&q2=v2#FragmentName");
Object result = page.evaluate("url => url", url);
assertTrue(result instanceof URL);
assertEquals(url.toString(), result.toString());
// TODO: Date values are not supported in java.
}
@Test
@@ -1,60 +0,0 @@
package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import java.util.HashMap;
import static org.junit.jupiter.api.Assertions.*;
public class TestPageInterception extends TestBase {
@Test
void shouldWorkWithNavigationSmoke() {
HashMap<String, Request> requests = new HashMap<>();
page.route("**/*", route -> {
String[] parts = route.request().url().split("/");
requests.put(parts[parts.length - 1], route.request());
route.resume();
});
server.setRedirect("/rrredirect", "/frames/one-frame.html");
page.navigate(server.PREFIX + "/rrredirect");
assertTrue(requests.get("rrredirect").isNavigationRequest());
assertTrue(requests.get("frame.html").isNavigationRequest());
assertFalse(requests.get("script.js").isNavigationRequest());
assertFalse(requests.get("style.css").isNavigationRequest());
}
@Test
void shouldInterceptAfterAServiceWorker() {
page.navigate(server.PREFIX + "/serviceworkers/fetchdummy/sw.html");
page.evaluate("() => window['activationPromise']");
// Sanity check.
Object swResponse = page.evaluate("() => window['fetchDummy']('foo')");
assertEquals("responseFromServiceWorker:foo", swResponse);
page.route("**/foo", route -> {
int slash = route.request().url().lastIndexOf("/");
String name = route.request().url().substring(slash + 1);
route.fulfill(new Route.FulfillOptions()
.setStatus(200)
.setContentType("text/css").setBody("responseFromInterception:" + name));
});
// Page route is applied after service worker fetch event.
Object swResponse2 = page.evaluate("() => window['fetchDummy']('foo')");
assertEquals("responseFromServiceWorker:foo", swResponse2);
// Page route is not applied to service worker initiated fetch.
Object nonInterceptedResponse = page.evaluate("() => window['fetchDummy']('passthrough')");
assertEquals("FAILURE: Not Found", nonInterceptedResponse);
// Firefox does not want to fetch the redirect for some reason.
if (!isFirefox()) {
// Page route is not applied to service worker initiated fetch with redirect.
server.setRedirect("/serviceworkers/fetchdummy/passthrough", "/simple.json");
Object redirectedResponse = page.evaluate("() => window['fetchDummy']('passthrough')");
assertEquals("{\"foo\": \"bar\"}\n", redirectedResponse);
}
}
}
@@ -119,33 +119,6 @@ public class TestPageLocatorQuery extends TestBase {
assertEquals("Hello \"world\"", page.locator("div", new Page.LocatorOptions().setHasText(pattern)).textContent());
}
@Test
void shouldFilterByCaseInsensitiveRegexInAChild() {
page.setContent("<div class=\"test\"><h5>Title Text</h5></div>");
Pattern pattern = Pattern.compile("^title text$", Pattern.CASE_INSENSITIVE);
assertThat(page.locator("div", new Page.LocatorOptions().setHasText(pattern))).hasText("Title Text");
}
@Test
void shouldFilterByCaseInsensitiveRegexInMultipleChildren() {
page.setContent("<div class=\"test\"><h5>Title</h5> <h2><i>Text</i></h2></div>`");
Pattern pattern = Pattern.compile("^title text$", Pattern.CASE_INSENSITIVE);
assertThat(page.locator("div", new Page.LocatorOptions().setHasText(pattern))).hasClass("test");
}
@Test
void shouldFilterByRegexWithSpecialSymbols() {
page.setContent("<div class=\"test\"><h5>First/\"and\"</h5><h2><i>Second\\</i></h2></div>");
Pattern pattern = Pattern.compile("first\\/\".*\"second\\\\$", Pattern.CASE_INSENSITIVE);
assertThat(page.locator("div", new Page.LocatorOptions().setHasText(pattern))).hasClass("test");
}
@Test
void shouldFilterByTextWithAmpersand() {
page.setContent("<div>Save & Continue</div>");
assertEquals("Save & Continue", page.locator("div",
new Page.LocatorOptions().setHasText("Save & Continue")).textContent());
}
@Test
void shouldSupportHasLocator() {
page.setContent("<div><span>hello</span></div><div><span>world</span></div>");
@@ -20,10 +20,7 @@ import com.google.gson.Gson;
import com.microsoft.playwright.options.HttpHeader;
import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.Semaphore;
import java.util.stream.Collectors;
@@ -96,21 +93,4 @@ public class TestPageNetworkRequest extends TestBase {
String cookie = response.request().allHeaders().get("cookie");
assertEquals("myCookie=myValue; myOtherCookie=myOtherValue", cookie);
}
@Test
void shouldReportPostDataFor403Request() throws InterruptedException, ExecutionException {
server.setRoute("/upload", exchange -> {
exchange.sendResponseHeaders(403, 0);
exchange.getResponseBody().close();
});
Future<Server.Request> serverRequest = server.futureRequest("/upload");
page.navigate(server.EMPTY_PAGE);
Request request = page.waitForRequest("**/*", () -> {
page.evaluate("() => fetch('/upload', { method: 'POST', body: 'test'})");
});
assertEquals("test", new String(serverRequest.get().postBody, StandardCharsets.UTF_8));
assertEquals("test", request.postData());
assertEquals("POST", request.method());
assertEquals(403, request.response().status());
}
}
@@ -18,52 +18,10 @@ package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import java.io.OutputStreamWriter;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
public class TestPageRequestContinue extends TestBase {
@Test
void shouldDeleteHeaderWithUndefinedValue() throws ExecutionException, InterruptedException {
// https://github.com/microsoft/playwright/issues/13106
page.navigate(server.PREFIX + "/empty.html");
Future<Server.Request> serverRequest = server.futureRequest("/something");
server.setRoute("/something", exchange -> {
exchange.getResponseHeaders().add("Access-Control-Allow-Origin", "*");
exchange.sendResponseHeaders(200, 0);
try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("done");
}
});
Request[] interceptedRequest = {null};
page.route(server.PREFIX + "/something", route -> {
interceptedRequest[0] = route.request();
Map<String, String> headers = route.request().allHeaders();
headers.remove("foo");
route.resume(new Route.ResumeOptions().setHeaders(headers));
});
Object text = page.evaluate("async url => {\n" +
" const data = await fetch(url, {\n" +
" headers: {\n" +
" foo: 'a',\n" +
" bar: 'b',\n" +
" }\n" +
" });\n" +
" return data.text();\n" +
" }", server.PREFIX + "/something");
assertEquals("done", text);
assertNull(interceptedRequest[0].headers().get("foo"));
assertNull(serverRequest.get().headers.get("foo"));
assertEquals(asList("b"), serverRequest.get().headers.get("bar"));
}
@Test
void shouldNotThrowWhenContinuingAfterPageIsClosed() {
boolean[] done = {false};
@@ -1,276 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.*;
public class TestPageRequestFallback extends TestBase {
@Test
void shouldWork() {
page.route("**/*", route -> route.fallback());
page.navigate(server.EMPTY_PAGE);
}
@Test
void shouldFallBack() {
List<Integer> intercepted = new ArrayList<>();
page.route("**/empty.html", route -> {
intercepted.add(1);
route.fallback();
});
page.route("**/empty.html", route -> {
intercepted.add(2);
route.fallback();
});
page.route("**/empty.html", route -> {
intercepted.add(3);
route.fallback();
});
page.navigate(server.EMPTY_PAGE);
assertEquals(asList(3, 2, 1), intercepted);
}
@Test
void shouldFallBackAsync() {
List<Integer> intercepted = new ArrayList<>();
page.route("**/empty.html", route -> {
intercepted.add(1);
page.waitForTimeout(50);
route.fallback();
});
page.route("**/empty.html", route -> {
intercepted.add(2);
page.waitForTimeout(100);
route.fallback();
});
page.route("**/empty.html", route -> {
intercepted.add(3);
page.waitForTimeout(150);
route.fallback();
});
page.navigate(server.EMPTY_PAGE);
assertEquals(asList(3, 2, 1), intercepted);
}
@Test
void shouldNotChainFulfill() {
boolean[] failed = {false};
page.route("**/empty.html", route -> {
failed[0] = true;
});
page.route("**/empty.html", route -> {
route.fulfill(new Route.FulfillOptions().setStatus(200).setBody("fulfilled"));
});
page.route("**/empty.html", route -> {
route.fallback();
});
Response response = page.navigate(server.EMPTY_PAGE);
assertEquals("fulfilled", response.text());
assertFalse(failed[0]);
}
@Test
void shouldNotChainAbort() {
boolean[] failed = {false};
page.route("**/empty.html", route -> {
failed[0] = true;
});
page.route("**/empty.html", route -> {
route.abort();
});
page.route("**/empty.html", route -> {
route.fallback();
});
try {
Response response = page.navigate(server.EMPTY_PAGE);
fail("did not throw");
assertEquals("fulfilled", response.text());
} catch (PlaywrightException e) {
}
assertFalse(failed[0]);
}
@Test
void shouldFallBackAfterException() {
page.route("**/empty.html", route -> {
route.resume();
});
page.route("**/empty.html", route -> {
try {
route.fulfill(new Route.FulfillOptions());
} catch (PlaywrightException e) {
route.fallback();
}
});
page.navigate(server.EMPTY_PAGE);
}
@Test
void shouldChainOnce() {
page.route("**/empty.html", route -> {
route.fulfill(new Route.FulfillOptions().setStatus(200).setBody("fulfilled one"));
}, new Page.RouteOptions().setTimes(1));
page.route("**/empty.html", route -> {
route.fallback();
}, new Page.RouteOptions().setTimes(1));
Response response = page.navigate(server.EMPTY_PAGE);
assertEquals("fulfilled one", response.text());
}
@Test
void shouldAmendHTTPHeaders() throws ExecutionException, InterruptedException {
List<String> values = new ArrayList<>();
page.route("**/sleep.zzz", route -> {
values.add(route.request().headers().get("foo"));
values.add(route.request().headerValue("FOO"));
route.resume();
});
page.route("**/*", route -> {
Map<String, String> headers = route.request().headers();
headers.put("FOO", "bar");
route.fallback(new Route.FallbackOptions().setHeaders(headers));
});
page.navigate(server.EMPTY_PAGE);
Future<Server.Request> request = server.futureRequest("/sleep.zzz");
page.evaluate("() => fetch('/sleep.zzz')");
values.addAll(request.get().headers.get("foo"));
assertEquals(asList("bar", "bar", "bar"), values);
}
@Test
void shouldDeleteHeaderWithUndefinedValue() throws ExecutionException, InterruptedException {
// https://github.com/microsoft/playwright/issues/13106
page.navigate(server.PREFIX + "/empty.html");
Future<Server.Request> serverRequest = server.futureRequest("/something");
server.setRoute("/something", exchange -> {
exchange.getResponseHeaders().add("Access-Control-Allow-Origin", "*");
exchange.sendResponseHeaders(200, 0);
try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("done");
}
});
Request[] interceptedRequest = {null};
page.route(server.PREFIX + "/something", route -> {
interceptedRequest[0] = route.request();
route.resume();
});
page.route(server.PREFIX + "/something", route -> {
Map<String, String> headers = route.request().allHeaders();
headers.remove("foo");
route.fallback(new Route.FallbackOptions().setHeaders(headers));
});
Object text = page.evaluate("async url => {\n" +
" const data = await fetch(url, {\n" +
" headers: {\n" +
" foo: 'a',\n" +
" bar: 'b',\n" +
" }\n" +
" });\n" +
" return data.text();\n" +
" }", server.PREFIX + "/something");
assertEquals("done", text);
assertNull(interceptedRequest[0].headers().get("foo"));
assertNull(serverRequest.get().headers.get("foo"));
assertEquals(asList("b"), serverRequest.get().headers.get("bar"));
}
@Test
void shouldAmendMethod() throws ExecutionException, InterruptedException {
Future<Server.Request> sRequest = server.futureRequest("/sleep.zzz");
page.navigate(server.EMPTY_PAGE);
String[] method = {null};
page.route("**/*", route -> {
method[0] = route.request().method();
route.resume();
});
page.route("**/*", route -> route.fallback(new Route.FallbackOptions().setMethod("POST")));
Request request = page.waitForRequest("**/sleep.zzz", () -> page.evaluate("() => fetch('/sleep.zzz')"));
assertEquals("POST", method[0]);
assertEquals("POST", request.method());
assertEquals("POST", sRequest.get().method);
}
@Test
void shouldOverrideRequestUrl() throws ExecutionException, InterruptedException {
Future<Server.Request> request = server.futureRequest("/global-var.html");
String[] url = {null};
page.route("**/global-var.html", route -> {
url[0] = route.request().url();
route.resume();
});
page.route("**/foo", route -> route.fallback(new Route.FallbackOptions().setUrl(server.PREFIX + "/global-var.html")));
Response response = page.waitForResponse("**/*", () -> page.navigate(server.PREFIX + "/foo"));
assertEquals(server.PREFIX + "/global-var.html", url[0]);
assertEquals(server.PREFIX + "/foo", response.url());
assertEquals(123, page.evaluate("() => window['globalVar']"));
assertEquals("GET", request.get().method);
}
@Test
void shouldAmendPostData() throws ExecutionException, InterruptedException {
page.navigate(server.EMPTY_PAGE);
String[] postData = {null};
page.route("**/*", route -> {
postData[0] = route.request().postData();
route.resume();
});
page.route("**/*", route -> {
route.fallback(new Route.FallbackOptions().setPostData("doggo"));
});
Future<Server.Request> serverRequest = server.futureRequest("/sleep.zzz");
page.evaluate("() => fetch('/sleep.zzz', { method: 'POST', body: 'birdy' })");
assertEquals("doggo", postData[0]);
assertEquals("doggo", new String(serverRequest.get().postBody, StandardCharsets.UTF_8));
}
@Test
void shouldAmendBinaryPostData() throws ExecutionException, InterruptedException {
page.navigate(server.EMPTY_PAGE);
byte[] arr = new byte[256];
for (int i = 0; i < arr.length; i++) {
arr[i] = (byte) i;
}
byte[][] postDataBuffer = {null};
page.route("**/*", route -> {
postDataBuffer[0] = route.request().postDataBuffer();
route.resume();
});
page.route("**/*", route -> {
route.fallback(new Route.FallbackOptions().setPostData(arr));
});
Future<Server.Request> serverRequest = server.futureRequest("/sleep.zzz");
page.evaluate("() => fetch('/sleep.zzz', { method: 'POST', body: 'birdy' })");
byte[] buffer = serverRequest.get().postBody;
assertArrayEquals(arr, buffer);
assertArrayEquals(arr, postDataBuffer[0]);
}
}
@@ -21,7 +21,6 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIf;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -67,29 +66,29 @@ public class TestPageRoute extends TestBase {
List<Integer> intercepted = new ArrayList<>();
page.route("**/*", route -> {
intercepted.add(1);
route.fallback();
route.resume();
});
page.route("**/empty.html", route -> {
intercepted.add(2);
route.fallback();
route.resume();
});
page.route("**/empty.html", route -> {
intercepted.add(3);
route.fallback();
route.resume();
});
Consumer<Route> handler4 = route -> {
intercepted.add(4);
route.fallback();
route.resume();
};
page.route("**/empty.html", handler4);
page.navigate(server.EMPTY_PAGE);
assertEquals(asList(4, 3, 2, 1), intercepted);
assertEquals(asList(4), intercepted);
intercepted.clear();
page.unroute("**/empty.html", handler4);
page.navigate(server.EMPTY_PAGE);
assertEquals(asList(3, 2, 1), intercepted);
assertEquals(asList(3), intercepted);
intercepted.clear();
page.unroute("**/empty.html");
@@ -103,25 +102,25 @@ public class TestPageRoute extends TestBase {
Predicate<String> predicate = r -> true;
page.route(predicate, route -> {
intercepted.add(1);
route.fallback();
route.resume();
});
page.route(predicate, route -> {
intercepted.add(2);
route.fallback();
route.resume();
});
Consumer<Route> handler3 = route -> {
intercepted.add(3);
route.fallback();
route.resume();
};
page.route(predicate, handler3);
page.navigate(server.EMPTY_PAGE);
assertEquals(asList(3, 2, 1), intercepted);
assertEquals(asList(3), intercepted);
intercepted.clear();
page.unroute(predicate, handler3);
page.navigate(server.EMPTY_PAGE);
assertEquals(asList(2, 1), intercepted);
assertEquals(asList(2), intercepted);
intercepted.clear();
page.unroute(predicate);
@@ -476,7 +475,7 @@ public class TestPageRoute extends TestBase {
}
@Test
void shouldThrowIfResumeIsCalledAfterRouteHandlerFinished() {
void shouldNotThrowInvalidInterceptionIdIfTheRequestWasCancelled() {
page.setContent("<iframe></iframe>");
Route[] route = {null};
page.route("**/*", r -> route[0] = r);
@@ -486,9 +485,8 @@ public class TestPageRoute extends TestBase {
page.evalOnSelector("iframe", "frame => frame.remove()");
try {
route[0].resume();
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Route is already handled!"), e.getMessage());
fail("Should not throw");
}
}
@@ -725,25 +723,4 @@ public class TestPageRoute extends TestBase {
});
assertEquals(server.PREFIX, response.headerValue("Access-Control-Allow-Origin"));
}
@Test
void shouldChainFallbackWDynamicURL() {
List<Integer> intercepted = new ArrayList<>();
page.route("**/bar", route -> {
intercepted.add(1);
route.fallback(new Route.FallbackOptions().setUrl(server.EMPTY_PAGE));
});
page.route("**/foo", route -> {
intercepted.add(2);
route.fallback(new Route.FallbackOptions().setUrl("http://localhost/bar"));
});
page.route("**/empty.html", route -> {
intercepted.add(3);
route.fallback(new Route.FallbackOptions().setUrl("http://localhost/foo"));
});
page.navigate(server.EMPTY_PAGE);
assertEquals(asList(3, 2, 1), intercepted);
}
}
@@ -22,7 +22,6 @@ import com.microsoft.playwright.options.ScreenshotCaret;
import com.microsoft.playwright.options.ScreenshotScale;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIf;
import org.opentest4j.AssertionFailedError;
import javax.imageio.ImageIO;
@@ -221,7 +220,6 @@ public class TestPageScreenshot extends TestBase {
}
@Test
@DisabledIf(value="com.microsoft.playwright.TestBase#isFirefox", disabledReason="fixme")
void shouldCaptureBlinkingCaretIfExplicitlyAskedFor() {
page.setContent(" <!-- Refer to stylesheet from other origin. Accessing this\n" +
" stylesheet rules will throw.\n" +
@@ -96,50 +96,6 @@ public class TestPageSetInputFiles extends TestBase {
assertEquals(200 * 1024 * 1024, fields.get(0).content.length());
}
@Test
void shouldUploadLargeFileWithRelativePath(@TempDir Path tmpDir) throws IOException, ExecutionException, InterruptedException {
Assumptions.assumeTrue(3 <= (Runtime.getRuntime().maxMemory() >> 30), "Fails if max heap size is < 3Gb");
page.navigate(server.PREFIX + "/input/fileupload.html");
Path uploadFile = tmpDir.resolve("200MB.zip");
String str = String.join("", Collections.nCopies(4 * 1024, "A"));
try (Writer stream = new OutputStreamWriter(Files.newOutputStream(uploadFile))) {
for (int i = 0; i < 50 * 1024; i++) {
stream.write(str);
}
}
Locator input = page.locator("input[type='file']");
JSHandle events = input.evaluateHandle("e => {\n" +
" const events = [];\n" +
" e.addEventListener('input', () => events.push('input'));\n" +
" e.addEventListener('change', () => events.push('change'));\n" +
" return events;\n" +
" }");
Path cwd = Paths.get("").toAbsolutePath();
Path relativeUploadPath = cwd.relativize(uploadFile);
assertFalse(relativeUploadPath.isAbsolute());
input.setInputFiles(relativeUploadPath);
assertEquals("200MB.zip", input.evaluate("e => e.files[0].name"));
assertEquals(asList("input", "change"), events.evaluate("e => e"));
CompletableFuture<MultipartFormData> formData = new CompletableFuture<>();
server.setRoute("/upload", exchange -> {
try {
MultipartFormData multipartFormData = MultipartFormData.parseRequest(exchange);
formData.complete(multipartFormData);
} catch (Exception e) {
e.printStackTrace();
formData.completeExceptionally(e);
}
exchange.sendResponseHeaders(200, -1);
});
page.click("input[type=submit]", new Page.ClickOptions().setTimeout(90_000));
List<MultipartFormData.Field> fields = formData.get().fields;
assertEquals(1, fields.size());
assertEquals("200MB.zip", fields.get(0).filename);
assertEquals(200 * 1024 * 1024, fields.get(0).content.length());
}
@Test
void shouldWork() {
page.setContent("<input type=file>");
@@ -127,17 +127,4 @@ public class TestScreencast extends TestBase {
assertTrue(Files.exists(files.get(0)));
assertTrue(Files.size(files.get(0)) > 0);
}
@Test
void shouldErrorIfPageNotClosedBeforeSaveAs(@TempDir Path tmpDir) {
try (Page page = browser.newPage(new Browser.NewPageOptions().setRecordVideoDir(tmpDir))) {
page.navigate(server.PREFIX + "/grid.html");
Path outPath = tmpDir.resolve("some-video.webm");
Video video = page.video();
PlaywrightException exception = assertThrows(PlaywrightException.class, () -> video.saveAs(outPath));
assertTrue(
exception.getMessage().contains("Page is not yet closed. Close the page prior to calling saveAs"),
exception.getMessage());
}
}
}
@@ -113,7 +113,7 @@ public class TestTracing extends TestBase {
Path trace = tmpDir.resolve("trace1.zip");
context.tracing().stop(new Tracing.StopOptions().setPath(trace));
Map<String, byte[]> entries = Utils.parseZip(trace);
Map<String, byte[]> entries = Utils.parseTrace(trace);
Map<String, byte[]> sources = entries.entrySet().stream().filter(e -> e.getKey().endsWith(".txt")).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
assertEquals(1, sources.size());
@@ -25,7 +25,6 @@ import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.*;
@@ -140,13 +139,9 @@ public class TestWebSocket extends TestBase {
boolean[] socketError = {false};
String[] error = {null};
page.onWebSocket(ws -> {
ws.onSocketError(new Consumer<String>() {
@Override
public void accept(String e) {
ws.offSocketError(this);
error[0] = e;
socketError[0] = true;
}
ws.onSocketError(e -> {
error[0] = e;
socketError[0] = true;
});
});
page.evaluate("port => {\n" +
@@ -108,8 +108,8 @@ public class TestWheel extends TestBase {
"shiftKey", false,
"altKey", false,
"metaKey", false);
page.waitForFunction("window.scrollX === 100");
expectEvent(expected);
page.waitForFunction("window.scrollX === 100");
}
@Test
@@ -121,8 +121,6 @@ public class TestWheel extends TestBase {
" document.querySelector('div').addEventListener('wheel', e => e.preventDefault());\n" +
" }");
page.mouse().wheel(0, 100);
// give the page a chance to scroll
page.waitForFunction("!!window['lastEvent']");
Map<String, Object> expected = mapOf(
"deltaX", 0,
"deltaY", 100,
@@ -134,6 +132,8 @@ public class TestWheel extends TestBase {
"altKey", false,
"metaKey", false);
expectEvent(expected);
// give the page a chacne to scroll
page.waitForTimeout(100);
// ensure that it did not.
assertEquals(0, page.evaluate("window.scrollY"));
}
@@ -22,7 +22,7 @@ import com.google.gson.JsonParser;
import java.io.*;
import java.net.ServerSocket;
import java.nio.file.Files;
import java.net.Socket;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.HashMap;
@@ -89,7 +89,7 @@ class Utils {
}
}
static Map<String, byte[]> parseZip(Path trace) throws IOException {
static Map<String, byte[]> parseTrace(Path trace) throws IOException {
Map<String, byte[]> entries = new HashMap<>();
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(trace.toFile()))) {
for (ZipEntry zipEntry = zis.getNextEntry(); zipEntry != null; zipEntry = zis.getNextEntry()) {
@@ -104,24 +104,6 @@ class Utils {
return entries;
}
static Map<String, byte[]> extractZip(Path zipPath, Path toDir) throws IOException {
Map<String, byte[]> entries = new HashMap<>();
Files.createDirectories(toDir);
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipPath.toFile()))) {
for (ZipEntry zipEntry = zis.getNextEntry(); zipEntry != null; zipEntry = zis.getNextEntry()) {
Path toPath = toDir.resolve(zipEntry.getName());
if (zipEntry.isDirectory()) {
Files.createDirectories(toPath);
} else {
Files.copy(zis, toPath);
}
zis.closeEntry();
}
}
return entries;
}
enum OS { WINDOWS, MAC, LINUX, UNKNOWN }
static OS getOS() {
String name = System.getProperty("os.name").toLowerCase();
@@ -1,366 +0,0 @@
{
"log": {
"version": "1.2",
"creator": {
"name": "Playwright",
"version": "1.23.0-next"
},
"browser": {
"name": "chromium",
"version": "103.0.5060.33"
},
"pages": [
{
"startedDateTime": "2022-06-10T04:27:32.125Z",
"id": "page@b17b177f1c2e66459db3dcbe44636ffd",
"title": "Hey",
"pageTimings": {
"onContentLoad": 70,
"onLoad": 70
}
}
],
"entries": [
{
"_frameref": "frame@c7467fc0f1f86f09fc3b0d727a3862ea",
"_monotonicTime": 270572145.898,
"startedDateTime": "2022-06-10T04:27:32.146Z",
"time": 8.286,
"request": {
"method": "GET",
"url": "http://no.playwright/",
"httpVersion": "HTTP/1.1",
"cookies": [],
"headers": [
{
"name": "Accept",
"value": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
},
{
"name": "Upgrade-Insecure-Requests",
"value": "1"
},
{
"name": "User-Agent",
"value": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.33 Safari/537.36"
}
],
"queryString": [],
"headersSize": 326,
"bodySize": 0
},
"response": {
"status": 200,
"statusText": "OK",
"httpVersion": "HTTP/1.1",
"cookies": [],
"headers": [
{
"name": "content-length",
"value": "111"
},
{
"name": "content-type",
"value": "text/html"
}
],
"content": {
"size": 111,
"mimeType": "text/html",
"compression": 0,
"text": "<title>Hey</title><link rel='stylesheet' href='./style.css'><script src='./script.js'></script><div>hello</div>"
},
"headersSize": 65,
"bodySize": 170,
"redirectURL": "",
"_transferSize": 170
},
"cache": {
"beforeRequest": null,
"afterRequest": null
},
"timings": {
"dns": -1,
"connect": -1,
"ssl": -1,
"send": 0,
"wait": 8.286,
"receive": -1
},
"pageref": "page@b17b177f1c2e66459db3dcbe44636ffd",
"_securityDetails": {}
},
{
"_frameref": "frame@c7467fc0f1f86f09fc3b0d727a3862ea",
"_monotonicTime": 270572174.683,
"startedDateTime": "2022-06-10T04:27:32.172Z",
"time": 7.132,
"request": {
"method": "POST",
"url": "http://no.playwright/style.css",
"httpVersion": "HTTP/1.1",
"cookies": [],
"headers": [
{
"name": "Accept",
"value": "text/css,*/*;q=0.1"
},
{
"name": "Referer",
"value": "http://no.playwright/"
},
{
"name": "User-Agent",
"value": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.33 Safari/537.36"
}
],
"queryString": [],
"headersSize": 220,
"bodySize": 0
},
"response": {
"status": 200,
"statusText": "OK",
"httpVersion": "HTTP/1.1",
"cookies": [],
"headers": [
{
"name": "content-length",
"value": "24"
},
{
"name": "content-type",
"value": "text/css"
}
],
"content": {
"size": 24,
"mimeType": "text/css",
"compression": 0,
"text": "body { background:cyan }"
},
"headersSize": 63,
"bodySize": 81,
"redirectURL": "",
"_transferSize": 81
},
"cache": {
"beforeRequest": null,
"afterRequest": null
},
"timings": {
"dns": -1,
"connect": -1,
"ssl": -1,
"send": 0,
"wait": 8.132,
"receive": -1
},
"pageref": "page@b17b177f1c2e66459db3dcbe44636ffd",
"_securityDetails": {}
},
{
"_frameref": "frame@c7467fc0f1f86f09fc3b0d727a3862ea",
"_monotonicTime": 270572174.683,
"startedDateTime": "2022-06-10T04:27:32.174Z",
"time": 8.132,
"request": {
"method": "GET",
"url": "http://no.playwright/style.css",
"httpVersion": "HTTP/1.1",
"cookies": [],
"headers": [
{
"name": "Accept",
"value": "text/css,*/*;q=0.1"
},
{
"name": "Referer",
"value": "http://no.playwright/"
},
{
"name": "User-Agent",
"value": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.33 Safari/537.36"
}
],
"queryString": [],
"headersSize": 220,
"bodySize": 0
},
"response": {
"status": 200,
"statusText": "OK",
"httpVersion": "HTTP/1.1",
"cookies": [],
"headers": [
{
"name": "content-length",
"value": "24"
},
{
"name": "content-type",
"value": "text/css"
}
],
"content": {
"size": 24,
"mimeType": "text/css",
"compression": 0,
"text": "body { background: red }"
},
"headersSize": 63,
"bodySize": 81,
"redirectURL": "",
"_transferSize": 81
},
"cache": {
"beforeRequest": null,
"afterRequest": null
},
"timings": {
"dns": -1,
"connect": -1,
"ssl": -1,
"send": 0,
"wait": 8.132,
"receive": -1
},
"pageref": "page@b17b177f1c2e66459db3dcbe44636ffd",
"_securityDetails": {}
},
{
"_frameref": "frame@c7467fc0f1f86f09fc3b0d727a3862ea",
"_monotonicTime": 270572175.042,
"startedDateTime": "2022-06-10T04:27:32.175Z",
"time": 15.997,
"request": {
"method": "GET",
"url": "http://no.playwright/script.js",
"httpVersion": "HTTP/1.1",
"cookies": [],
"headers": [
{
"name": "Accept",
"value": "*/*"
},
{
"name": "Referer",
"value": "http://no.playwright/"
},
{
"name": "User-Agent",
"value": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.33 Safari/537.36"
}
],
"queryString": [],
"headersSize": 205,
"bodySize": 0
},
"response": {
"status": 301,
"statusText": "Moved Permanently",
"httpVersion": "HTTP/1.1",
"cookies": [],
"headers": [
{
"name": "location",
"value": "http://no.playwright/script2.js"
}
],
"content": {
"size": -1,
"mimeType": "x-unknown",
"compression": 0
},
"headersSize": 77,
"bodySize": 0,
"redirectURL": "http://no.playwright/script2.js",
"_transferSize": 77
},
"cache": {
"beforeRequest": null,
"afterRequest": null
},
"timings": {
"dns": -1,
"connect": -1,
"ssl": -1,
"send": 0,
"wait": 7.673,
"receive": 8.324
},
"pageref": "page@b17b177f1c2e66459db3dcbe44636ffd",
"_securityDetails": {}
},
{
"_frameref": "frame@c7467fc0f1f86f09fc3b0d727a3862ea",
"_monotonicTime": 270572181.822,
"startedDateTime": "2022-06-10T04:27:32.182Z",
"time": 6.735,
"request": {
"method": "GET",
"url": "http://no.playwright/script2.js",
"httpVersion": "HTTP/1.1",
"cookies": [],
"headers": [
{
"name": "Accept",
"value": "*/*"
},
{
"name": "Referer",
"value": "http://no.playwright/"
},
{
"name": "User-Agent",
"value": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.33 Safari/537.36"
}
],
"queryString": [],
"headersSize": 206,
"bodySize": 0
},
"response": {
"status": 200,
"statusText": "OK",
"httpVersion": "HTTP/1.1",
"cookies": [],
"headers": [
{
"name": "content-length",
"value": "18"
},
{
"name": "content-type",
"value": "text/javascript"
}
],
"content": {
"size": 18,
"mimeType": "text/javascript",
"compression": 0,
"text": "window.value='foo'"
},
"headersSize": 70,
"bodySize": 82,
"redirectURL": "",
"_transferSize": 82
},
"cache": {
"beforeRequest": null,
"afterRequest": null
},
"timings": {
"dns": -1,
"connect": -1,
"ssl": -1,
"send": 0,
"wait": 6.735,
"receive": -1
},
"pageref": "page@b17b177f1c2e66459db3dcbe44636ffd",
"_securityDetails": {}
}
]
}
}
@@ -1,620 +0,0 @@
{
"log": {
"version": "1.2",
"creator": {
"name": "Playwright",
"version": "1.23.0-next"
},
"browser": {
"name": "chromium",
"version": "103.0.5060.42"
},
"pages": [
{
"startedDateTime": "2022-06-16T21:41:23.901Z",
"id": "page@8f314969edc000996eb5c2ab22f0e6b3",
"title": "Microsoft",
"pageTimings": {
"onContentLoad": 8363,
"onLoad": 8896
}
}
],
"entries": [
{
"_frameref": "frame@3767e074ecde4cb8372abba2f6f9bb4f",
"_monotonicTime": 110928357.437,
"startedDateTime": "2022-06-16T21:41:23.951Z",
"time": 93.99,
"request": {
"method": "GET",
"url": "https://theverge.com/",
"httpVersion": "HTTP/2.0",
"cookies": [],
"headers": [
{
"name": ":authority",
"value": "theverge.com"
},
{
"name": ":method",
"value": "GET"
},
{
"name": ":path",
"value": "/"
},
{
"name": ":scheme",
"value": "https"
},
{
"name": "accept",
"value": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
},
{
"name": "accept-encoding",
"value": "gzip, deflate, br"
},
{
"name": "accept-language",
"value": "en-US,en;q=0.9"
},
{
"name": "sec-ch-ua",
"value": "\"Chromium\";v=\"103\", \".Not/A)Brand\";v=\"99\""
},
{
"name": "sec-ch-ua-mobile",
"value": "?0"
},
{
"name": "sec-ch-ua-platform",
"value": "\"Linux\""
},
{
"name": "sec-fetch-dest",
"value": "document"
},
{
"name": "sec-fetch-mode",
"value": "navigate"
},
{
"name": "sec-fetch-site",
"value": "none"
},
{
"name": "sec-fetch-user",
"value": "?1"
},
{
"name": "upgrade-insecure-requests",
"value": "1"
},
{
"name": "user-agent",
"value": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.42 Safari/537.36"
}
],
"queryString": [],
"headersSize": 644,
"bodySize": 0
},
"response": {
"status": 301,
"statusText": "",
"httpVersion": "HTTP/2.0",
"cookies": [
{
"name": "vmidv1",
"value": "9faf31ab-1415-4b90-b367-24b670205f41",
"expires": "2027-06-15T21:41:24.000Z",
"domain": "theverge.com",
"path": "/",
"sameSite": "Lax",
"secure": true
}
],
"headers": [
{
"name": "accept-ranges",
"value": "bytes"
},
{
"name": "content-length",
"value": "0"
},
{
"name": "date",
"value": "Thu, 16 Jun 2022 21:41:24 GMT"
},
{
"name": "location",
"value": "http://www.theverge.com/"
},
{
"name": "retry-after",
"value": "0"
},
{
"name": "server",
"value": "Varnish"
},
{
"name": "set-cookie",
"value": "vmidv1=9faf31ab-1415-4b90-b367-24b670205f41;Expires=Tue, 15 Jun 2027 21:41:24 GMT;Domain=theverge.com;Path=/;SameSite=Lax;Secure"
},
{
"name": "via",
"value": "1.1 varnish"
},
{
"name": "x-cache",
"value": "HIT"
},
{
"name": "x-cache-hits",
"value": "0"
},
{
"name": "x-served-by",
"value": "cache-pao17442-PAO"
},
{
"name": "x-timer",
"value": "S1655415684.005867,VS0,VE0"
}
],
"content": {
"size": -1,
"mimeType": "x-unknown",
"compression": 0
},
"headersSize": 425,
"bodySize": 0,
"redirectURL": "http://www.theverge.com/",
"_transferSize": 425
},
"cache": {
"beforeRequest": null,
"afterRequest": null
},
"timings": {
"dns": 0,
"connect": 34.151,
"ssl": 28.074,
"send": 0,
"wait": 27.549,
"receive": 4.216
},
"pageref": "page@8f314969edc000996eb5c2ab22f0e6b3",
"serverIPAddress": "151.101.65.52",
"_serverPort": 443,
"_securityDetails": {
"protocol": "TLS 1.2",
"subjectName": "*.americanninjawarriornation.com",
"issuer": "GlobalSign Atlas R3 DV TLS CA 2022 Q1",
"validFrom": 1644853133,
"validTo": 1679153932
}
},
{
"_frameref": "frame@3767e074ecde4cb8372abba2f6f9bb4f",
"_monotonicTime": 110928427.603,
"startedDateTime": "2022-06-16T21:41:24.022Z",
"time": 44.39499999999999,
"request": {
"method": "GET",
"url": "http://www.theverge.com/",
"httpVersion": "HTTP/1.1",
"cookies": [],
"headers": [
{
"name": "Accept",
"value": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
},
{
"name": "Accept-Encoding",
"value": "gzip, deflate"
},
{
"name": "Accept-Language",
"value": "en-US,en;q=0.9"
},
{
"name": "Connection",
"value": "keep-alive"
},
{
"name": "Host",
"value": "www.theverge.com"
},
{
"name": "Upgrade-Insecure-Requests",
"value": "1"
},
{
"name": "User-Agent",
"value": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.42 Safari/537.36"
}
],
"queryString": [],
"headersSize": 423,
"bodySize": 0
},
"response": {
"status": 301,
"statusText": "Moved Permanently",
"httpVersion": "HTTP/1.1",
"cookies": [
{
"name": "_chorus_geoip_continent",
"value": "NA"
},
{
"name": "vmidv1",
"value": "4e0c1265-10f8-4cb1-a5de-1c3cf70b531c",
"expires": "2027-06-15T21:41:24.000Z",
"domain": "www.theverge.com",
"path": "/",
"sameSite": "Lax",
"secure": true
}
],
"headers": [
{
"name": "Accept-Ranges",
"value": "bytes"
},
{
"name": "Age",
"value": "2615"
},
{
"name": "Connection",
"value": "keep-alive"
},
{
"name": "Content-Length",
"value": "0"
},
{
"name": "Content-Type",
"value": "text/html"
},
{
"name": "Date",
"value": "Thu, 16 Jun 2022 21:41:24 GMT"
},
{
"name": "Location",
"value": "https://www.theverge.com/"
},
{
"name": "Server",
"value": "nginx"
},
{
"name": "Set-Cookie",
"value": "_chorus_geoip_continent=NA; expires=Fri, 17 Jun 2022 21:41:24 GMT; path=/;"
},
{
"name": "Set-Cookie",
"value": "vmidv1=4e0c1265-10f8-4cb1-a5de-1c3cf70b531c;Expires=Tue, 15 Jun 2027 21:41:24 GMT;Domain=www.theverge.com;Path=/;SameSite=Lax;Secure"
},
{
"name": "Vary",
"value": "X-Forwarded-Proto, Cookie, X-Chorus-Unison-Testing, X-Chorus-Require-Privacy-Consent, X-Chorus-Restrict-In-Privacy-Consent-Region, Accept-Encoding"
},
{
"name": "Via",
"value": "1.1 varnish"
},
{
"name": "X-Cache",
"value": "HIT"
},
{
"name": "X-Cache-Hits",
"value": "2"
},
{
"name": "X-Served-By",
"value": "cache-pao17450-PAO"
},
{
"name": "X-Timer",
"value": "S1655415684.035748,VS0,VE0"
}
],
"content": {
"size": -1,
"mimeType": "text/html",
"compression": 0
},
"headersSize": 731,
"bodySize": 0,
"redirectURL": "https://www.theverge.com/",
"_transferSize": 731
},
"cache": {
"beforeRequest": null,
"afterRequest": null
},
"timings": {
"dns": 2.742,
"connect": 10.03,
"ssl": 14.123,
"send": 0,
"wait": 15.023,
"receive": 2.477
},
"pageref": "page@8f314969edc000996eb5c2ab22f0e6b3",
"serverIPAddress": "151.101.189.52",
"_serverPort": 80,
"_securityDetails": {}
},
{
"_frameref": "frame@3767e074ecde4cb8372abba2f6f9bb4f",
"_monotonicTime": 110928455.901,
"startedDateTime": "2022-06-16T21:41:24.050Z",
"time": 50.29199999999999,
"request": {
"method": "GET",
"url": "https://www.theverge.com/",
"httpVersion": "HTTP/2.0",
"cookies": [
{
"name": "vmidv1",
"value": "9faf31ab-1415-4b90-b367-24b670205f41"
},
{
"name": "_chorus_geoip_continent",
"value": "NA"
}
],
"headers": [
{
"name": ":authority",
"value": "www.theverge.com"
},
{
"name": ":method",
"value": "GET"
},
{
"name": ":path",
"value": "/"
},
{
"name": ":scheme",
"value": "https"
},
{
"name": "accept",
"value": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
},
{
"name": "accept-encoding",
"value": "gzip, deflate, br"
},
{
"name": "accept-language",
"value": "en-US,en;q=0.9"
},
{
"name": "cookie",
"value": "vmidv1=9faf31ab-1415-4b90-b367-24b670205f41; _chorus_geoip_continent=NA"
},
{
"name": "sec-ch-ua",
"value": "\"Chromium\";v=\"103\", \".Not/A)Brand\";v=\"99\""
},
{
"name": "sec-ch-ua-mobile",
"value": "?0"
},
{
"name": "sec-ch-ua-platform",
"value": "\"Linux\""
},
{
"name": "sec-fetch-dest",
"value": "document"
},
{
"name": "sec-fetch-mode",
"value": "navigate"
},
{
"name": "sec-fetch-site",
"value": "none"
},
{
"name": "sec-fetch-user",
"value": "?1"
},
{
"name": "upgrade-insecure-requests",
"value": "1"
},
{
"name": "user-agent",
"value": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.42 Safari/537.36"
}
],
"queryString": [],
"headersSize": 729,
"bodySize": 0
},
"response": {
"status": 200,
"statusText": "",
"httpVersion": "HTTP/2.0",
"cookies": [
{
"name": "_chorus_geoip_continent",
"value": "NA"
},
{
"name": "vmidv1",
"value": "40d8fd14-5ac3-4757-9e9c-efb106e82d3a",
"expires": "2027-06-15T21:41:24.000Z",
"domain": "www.theverge.com",
"path": "/",
"sameSite": "Lax",
"secure": true
}
],
"headers": [
{
"name": "accept-ranges",
"value": "bytes"
},
{
"name": "age",
"value": "263"
},
{
"name": "cache-control",
"value": "max-age=0, public, must-revalidate"
},
{
"name": "content-encoding",
"value": "br"
},
{
"name": "content-length",
"value": "14"
},
{
"name": "content-security-policy",
"value": "default-src https: data: 'unsafe-inline' 'unsafe-eval'; child-src https: data: blob:; connect-src https: data: blob: ; font-src https: data:; img-src https: data: blob:; media-src https: data: blob:; object-src https:; script-src https: data: blob: 'unsafe-inline' 'unsafe-eval'; style-src https: 'unsafe-inline'; block-all-mixed-content; upgrade-insecure-requests"
},
{
"name": "content-type",
"value": "text/html; charset=utf-8"
},
{
"name": "date",
"value": "Thu, 16 Jun 2022 21:41:24 GMT"
},
{
"name": "etag",
"value": "W/\"d498ef668223d015000070a66a181e85\""
},
{
"name": "link",
"value": "<https://concertads-configs.vox-cdn.com/sbn/verge/config.json>; rel=preload; as=fetch; crossorigin"
},
{
"name": "referrer-policy",
"value": "strict-origin-when-cross-origin"
},
{
"name": "server",
"value": "nginx"
},
{
"name": "set-cookie",
"value": "_chorus_geoip_continent=NA; expires=Fri, 17 Jun 2022 21:41:24 GMT; path=/;"
},
{
"name": "set-cookie",
"value": "vmidv1=40d8fd14-5ac3-4757-9e9c-efb106e82d3a;Expires=Tue, 15 Jun 2027 21:41:24 GMT;Domain=www.theverge.com;Path=/;SameSite=Lax;Secure"
},
{
"name": "strict-transport-security",
"value": "max-age=31556952; preload"
},
{
"name": "vary",
"value": "Accept-Encoding, X-Chorus-Unison-Testing, X-Chorus-Require-Privacy-Consent, X-Chorus-Restrict-In-Privacy-Consent-Region, Origin, X-Forwarded-Proto, Cookie, X-Chorus-Unison-Testing, X-Chorus-Require-Privacy-Consent, X-Chorus-Restrict-In-Privacy-Consent-Region"
},
{
"name": "via",
"value": "1.1 varnish"
},
{
"name": "x-cache",
"value": "HIT"
},
{
"name": "x-cache-hits",
"value": "1"
},
{
"name": "x-content-type-options",
"value": "nosniff"
},
{
"name": "x-download-options",
"value": "noopen"
},
{
"name": "x-frame-options",
"value": "SAMEORIGIN"
},
{
"name": "x-permitted-cross-domain-policies",
"value": "none"
},
{
"name": "x-request-id",
"value": "97363ad70e272e63641c0bb784fa06a01b848dfd"
},
{
"name": "x-runtime",
"value": "0.257911"
},
{
"name": "x-served-by",
"value": "cache-pao17436-PAO"
},
{
"name": "x-timer",
"value": "S1655415684.075077,VS0,VE1"
},
{
"name": "x-xss-protection",
"value": "1; mode=block"
}
],
"content": {
"size": 14,
"mimeType": "text/html",
"compression": 0,
"text": "<h1>hello</h1>"
},
"headersSize": 1742,
"bodySize": 48716,
"redirectURL": "",
"_transferSize": 48716
},
"cache": {
"beforeRequest": null,
"afterRequest": null
},
"timings": {
"dns": 0.016,
"connect": 24.487,
"ssl": 17.406,
"send": 0,
"wait": 8.383,
"receive": -1
},
"pageref": "page@8f314969edc000996eb5c2ab22f0e6b3",
"serverIPAddress": "151.101.189.52",
"_serverPort": 443,
"_securityDetails": {
"protocol": "TLS 1.2",
"subjectName": "*.americanninjawarriornation.com",
"issuer": "GlobalSign Atlas R3 DV TLS CA 2022 Q1",
"validFrom": 1644853133,
"validTo": 1679153932
}
}
]
}
}
@@ -1 +0,0 @@
Hello, world
@@ -1,95 +0,0 @@
{
"log": {
"version": "1.2",
"creator": {
"name": "Playwright",
"version": "1.23.0-next"
},
"browser": {
"name": "chromium",
"version": "103.0.5060.33"
},
"pages": [
{
"startedDateTime": "2022-06-10T04:27:32.125Z",
"id": "page@b17b177f1c2e66459db3dcbe44636ffd",
"title": "Hey",
"pageTimings": {
"onContentLoad": 70,
"onLoad": 70
}
}
],
"entries": [
{
"_frameref": "frame@c7467fc0f1f86f09fc3b0d727a3862ea",
"_monotonicTime": 270572145.898,
"startedDateTime": "2022-06-10T04:27:32.146Z",
"time": 8.286,
"request": {
"method": "GET",
"url": "http://no.playwright/",
"httpVersion": "HTTP/1.1",
"cookies": [],
"headers": [
{
"name": "Accept",
"value": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
},
{
"name": "Upgrade-Insecure-Requests",
"value": "1"
},
{
"name": "User-Agent",
"value": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.33 Safari/537.36"
}
],
"queryString": [],
"headersSize": 326,
"bodySize": 0
},
"response": {
"status": 200,
"statusText": "OK",
"httpVersion": "HTTP/1.1",
"cookies": [],
"headers": [
{
"name": "content-length",
"value": "12"
},
{
"name": "content-type",
"value": "text/html"
}
],
"content": {
"size": 12,
"mimeType": "text/html",
"compression": 0,
"_file": "har-sha1-main-response.txt"
},
"headersSize": 64,
"bodySize": 71,
"redirectURL": "",
"_transferSize": 71
},
"cache": {
"beforeRequest": null,
"afterRequest": null
},
"timings": {
"dns": -1,
"connect": -1,
"ssl": -1,
"send": 0,
"wait": 8.286,
"receive": -1
},
"pageref": "page@b17b177f1c2e66459db3dcbe44636ffd",
"_securityDetails": {}
}
]
}
}
-3
View File
@@ -1,3 +0,0 @@
<title>HAR Page</title>
<link rel='stylesheet' href='./one-style.css'>
<div>hello, world!</div>
+2 -2
View File
@@ -6,7 +6,7 @@
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.25.0</version>
<version>1.22.0</version>
<packaging>pom</packaging>
<name>Playwright Parent Project</name>
<description>Java library to automate Chromium, Firefox and WebKit with a single API.
@@ -43,7 +43,7 @@
<properties>
<compiler.version>1.8</compiler.version>
<gson.version>2.8.9</gson.version>
<gson.version>2.8.6</gson.version>
<junit.version>5.7.0</junit.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<websocket.version>1.5.1</websocket.version>
+1 -1
View File
@@ -1 +1 @@
1.25.0
1.22.0

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