Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ae844728de | |||
| 472b523c77 | |||
| 94d1393c88 | |||
| f759222755 | |||
| d87c6b24ca | |||
| 41355bd059 | |||
| e372513fa4 | |||
| b8e1e1d935 | |||
| a0745735d9 | |||
| b90de26d23 | |||
| adfdf92eaa | |||
| 60cb6ea7b3 | |||
| 44cb76d92c | |||
| 9fac877892 | |||
| ec8fb9f191 | |||
| 844d1665b2 | |||
| 484a255ec7 | |||
| 3f60144e0f | |||
| 8004e5d0ff | |||
| 3604aab710 | |||
| 2fdb89c94e | |||
| 4fee61a655 | |||
| efb281e016 | |||
| fdec32c650 | |||
| 7e285ffe44 | |||
| edf0e45fb4 | |||
| c8eb4f9eeb | |||
| e4ec9b8dbe | |||
| ef13ab86b8 | |||
| a48fef6b01 | |||
| 1c1f3d43ac | |||
| 8f59cd73f5 | |||
| e04ef2132c | |||
| 34d23a833e |
+4
-2
@@ -1,3 +1,5 @@
|
||||
# text files must be lf for golden file tests to work
|
||||
*.txt eol=lf
|
||||
*.json eol=lf
|
||||
* text=auto eol=lf
|
||||
|
||||
# make project show as TS on GitHub
|
||||
*.js linguist-detectable=false
|
||||
|
||||
@@ -31,18 +31,14 @@ jobs:
|
||||
- name: Build & Install
|
||||
run: mvn -B install -D skipTests --no-transfer-progress
|
||||
- name: Run tests
|
||||
run: mvn test -DexcludedGroups=isolated --no-transfer-progress --fail-at-end
|
||||
run: mvn test --no-transfer-progress --fail-at-end
|
||||
env:
|
||||
BROWSER: ${{ matrix.browser }}
|
||||
- name: Run tracing tests w/ sources
|
||||
run: mvn test -DexcludedGroups=isolated --no-transfer-progress --fail-at-end -D test=*TestTracing*
|
||||
run: mvn test --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:
|
||||
@@ -83,12 +79,7 @@ jobs:
|
||||
- name: Build & Install
|
||||
run: mvn -B install -D skipTests --no-transfer-progress
|
||||
- name: Run tests
|
||||
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
|
||||
run: mvn test --no-transfer-progress --fail-at-end
|
||||
env:
|
||||
BROWSER: chromium
|
||||
BROWSER_CHANNEL: ${{ matrix.browser-channel }}
|
||||
|
||||
@@ -11,9 +11,9 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom
|
||||
|
||||
| | Linux | macOS | Windows |
|
||||
| :--- | :---: | :---: | :---: |
|
||||
| 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: |
|
||||
| Chromium <!-- GEN:chromium-version -->104.0.5112.48<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| WebKit <!-- GEN:webkit-version -->16.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
|
||||
| Firefox <!-- GEN:firefox-version -->102.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
|
||||
Headless execution is supported for all the browsers on all platforms. Check out [system requirements](https://playwright.dev/java/docs/next/intro/#system-requirements) for details.
|
||||
|
||||
@@ -173,7 +173,7 @@ public class InterceptNetworkRequests {
|
||||
|
||||
## Documentation
|
||||
|
||||
Check out our [new documentation site](https://playwright.dev/java)!.
|
||||
Check out our official [documentation site](https://playwright.dev/java).
|
||||
|
||||
You can also browse [javadoc online](https://www.javadoc.io/doc/com.microsoft.playwright/playwright/latest/index.html).
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>1.22.0-SNAPSHOT</version>
|
||||
<version>1.24.1</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>driver-bundle</artifactId>
|
||||
|
||||
@@ -18,9 +18,13 @@ package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.impl.Driver;
|
||||
import com.microsoft.playwright.impl.DriverJar;
|
||||
import org.junit.jupiter.api.*;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
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;
|
||||
@@ -28,12 +32,26 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
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;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
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.
|
||||
@@ -44,15 +62,29 @@ public class TestInstall {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tags({@Tag("isolated"), @Tag("driverThrowTest")})
|
||||
void shouldThrowWhenBrowserPathIsInvalid(@TempDir Path tmpDir) {
|
||||
void shouldThrowWhenBrowserPathIsInvalid(@TempDir Path tmpDir) throws NoSuchFieldException, IllegalAccessException {
|
||||
Map<String,String> env = new HashMap<>();
|
||||
env.put("PLAYWRIGHT_DOWNLOAD_HOST", "https://127.0.0.127");
|
||||
|
||||
// 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());
|
||||
// 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");
|
||||
|
||||
assertThrows(RuntimeException.class, () -> Driver.ensureDriverInstalled(env, true));
|
||||
assertThrows(RuntimeException.class, () -> Driver.ensureDriverInstalled(env, true));
|
||||
// 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);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -81,12 +113,20 @@ public class TestInstall {
|
||||
}
|
||||
|
||||
@Test
|
||||
void playwrightDriverAlternativeImpl() {
|
||||
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);
|
||||
|
||||
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
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>1.22.0-SNAPSHOT</version>
|
||||
<version>1.24.1</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>driver</artifactId>
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>org.example</groupId>
|
||||
<artifactId>examples</artifactId>
|
||||
<version>1.22.0-SNAPSHOT</version>
|
||||
<version>1.24.1</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.17.0</version>
|
||||
<version>1.22.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.example;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
import com.microsoft.playwright.*;
|
||||
|
||||
public class SelectorsAndKeyboardManipulation {
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>1.22.0-SNAPSHOT</version>
|
||||
<version>1.24.1</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>playwright</artifactId>
|
||||
|
||||
@@ -20,6 +20,7 @@ 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
|
||||
@@ -143,6 +144,17 @@ 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}.
|
||||
*/
|
||||
@@ -153,6 +165,7 @@ 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.
|
||||
@@ -174,6 +187,15 @@ 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()}.
|
||||
@@ -186,7 +208,7 @@ public interface Browser extends AutoCloseable {
|
||||
*/
|
||||
public Path storageStatePath;
|
||||
/**
|
||||
* It specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
|
||||
* If 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.
|
||||
*/
|
||||
@@ -363,6 +385,23 @@ 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}.
|
||||
*/
|
||||
@@ -379,6 +418,14 @@ 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.
|
||||
@@ -427,6 +474,18 @@ 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()}.
|
||||
@@ -445,7 +504,7 @@ public interface Browser extends AutoCloseable {
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* It specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
|
||||
* If 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.
|
||||
*/
|
||||
@@ -571,6 +630,17 @@ 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}.
|
||||
*/
|
||||
@@ -581,6 +651,7 @@ 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.
|
||||
@@ -602,6 +673,15 @@ 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()}.
|
||||
@@ -614,7 +694,7 @@ public interface Browser extends AutoCloseable {
|
||||
*/
|
||||
public Path storageStatePath;
|
||||
/**
|
||||
* It specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
|
||||
* If 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.
|
||||
*/
|
||||
@@ -791,6 +871,23 @@ 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}.
|
||||
*/
|
||||
@@ -807,6 +904,14 @@ 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.
|
||||
@@ -855,6 +960,18 @@ 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()}.
|
||||
@@ -873,7 +990,7 @@ public interface Browser extends AutoCloseable {
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* It specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
|
||||
* If 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.
|
||||
*/
|
||||
@@ -947,6 +1064,10 @@ 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).
|
||||
@@ -954,6 +1075,10 @@ 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();
|
||||
@@ -973,6 +1098,11 @@ 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 explicilty 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.
|
||||
@@ -980,6 +1110,10 @@ public interface Browser extends AutoCloseable {
|
||||
* // Create a new page in a pristine context.
|
||||
* Page page = context.newPage();
|
||||
* page.navigate('https://example.com');
|
||||
*
|
||||
* // Gracefull close up everything
|
||||
* context.close();
|
||||
* browser.close();
|
||||
* }</pre>
|
||||
*/
|
||||
default BrowserContext newContext() {
|
||||
@@ -987,6 +1121,11 @@ 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 explicilty 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.
|
||||
@@ -994,6 +1133,10 @@ public interface Browser extends AutoCloseable {
|
||||
* // Create a new page in a pristine context.
|
||||
* Page page = context.newPage();
|
||||
* page.navigate('https://example.com');
|
||||
*
|
||||
* // Gracefull 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.click("a[target=_blank]");
|
||||
* page.locator("a[target=_blank]").click();
|
||||
* });
|
||||
* System.out.println(newPage.evaluate("location.href"));
|
||||
* }</pre>
|
||||
@@ -174,6 +174,62 @@ 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
|
||||
@@ -348,7 +404,7 @@ public interface BrowserContext extends AutoCloseable {
|
||||
* "</script>\n" +
|
||||
* "<button onclick=\"onClick()\">Click me</button>\n" +
|
||||
* "<div></div>");
|
||||
* page.click("button");
|
||||
* page.locator("button").click();
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
@@ -407,7 +463,7 @@ public interface BrowserContext extends AutoCloseable {
|
||||
* "</script>\n" +
|
||||
* "<button onclick=\"onClick()\">Click me</button>\n" +
|
||||
* "<div></div>");
|
||||
* page.click("button");
|
||||
* page.locator("button").click();
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
@@ -477,7 +533,7 @@ public interface BrowserContext extends AutoCloseable {
|
||||
* "</script>\n" +
|
||||
* "<button onclick=\"onClick()\">Click me</button>\n" +
|
||||
* "<div></div>\n");
|
||||
* page.click("button");
|
||||
* page.locator("button").click();
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
@@ -553,9 +609,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 Page#route Page.route()} will not intercept requests intercepted by Service Worker. See <a
|
||||
* <p> <strong>NOTE:</strong> {@link BrowserContext#route BrowserContext.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. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
|
||||
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
|
||||
*
|
||||
* <p> An example of a naive handler that aborts all image requests:
|
||||
* <pre>{@code
|
||||
@@ -605,9 +661,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 Page#route Page.route()} will not intercept requests intercepted by Service Worker. See <a
|
||||
* <p> <strong>NOTE:</strong> {@link BrowserContext#route BrowserContext.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. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
|
||||
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
|
||||
*
|
||||
* <p> An example of a naive handler that aborts all image requests:
|
||||
* <pre>{@code
|
||||
@@ -655,9 +711,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 Page#route Page.route()} will not intercept requests intercepted by Service Worker. See <a
|
||||
* <p> <strong>NOTE:</strong> {@link BrowserContext#route BrowserContext.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. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
|
||||
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
|
||||
*
|
||||
* <p> An example of a naive handler that aborts all image requests:
|
||||
* <pre>{@code
|
||||
@@ -707,9 +763,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 Page#route Page.route()} will not intercept requests intercepted by Service Worker. See <a
|
||||
* <p> <strong>NOTE:</strong> {@link BrowserContext#route BrowserContext.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. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
|
||||
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
|
||||
*
|
||||
* <p> An example of a naive handler that aborts all image requests:
|
||||
* <pre>{@code
|
||||
@@ -757,9 +813,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 Page#route Page.route()} will not intercept requests intercepted by Service Worker. See <a
|
||||
* <p> <strong>NOTE:</strong> {@link BrowserContext#route BrowserContext.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. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
|
||||
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
|
||||
*
|
||||
* <p> An example of a naive handler that aborts all image requests:
|
||||
* <pre>{@code
|
||||
@@ -809,9 +865,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 Page#route Page.route()} will not intercept requests intercepted by Service Worker. See <a
|
||||
* <p> <strong>NOTE:</strong> {@link BrowserContext#route BrowserContext.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. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
|
||||
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
|
||||
*
|
||||
* <p> An example of a naive handler that aborts all image requests:
|
||||
* <pre>{@code
|
||||
@@ -855,6 +911,32 @@ 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,6 +19,7 @@ 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
|
||||
@@ -515,6 +516,17 @@ 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}.
|
||||
*/
|
||||
@@ -525,6 +537,7 @@ 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.
|
||||
@@ -546,12 +559,21 @@ 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;
|
||||
/**
|
||||
* It specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
|
||||
* If 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.
|
||||
*/
|
||||
@@ -843,6 +865,23 @@ 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}.
|
||||
*/
|
||||
@@ -859,6 +898,14 @@ 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.
|
||||
@@ -907,6 +954,18 @@ 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.
|
||||
*/
|
||||
@@ -915,7 +974,7 @@ public interface BrowserType {
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* It specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
|
||||
* If 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.
|
||||
*/
|
||||
|
||||
@@ -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.click("a"));
|
||||
* Download download = page.waitForDownload(() -> page.locator("a").click());
|
||||
* // wait for download to complete
|
||||
* Path path = download.path();
|
||||
* }</pre>
|
||||
* <pre>{@code
|
||||
* // wait for download to start
|
||||
* Download download = page.waitForDownload(() -> {
|
||||
* page.click("a");
|
||||
* page.locator("a").click();
|
||||
* });
|
||||
* // wait for download to complete
|
||||
* Path path = download.path();
|
||||
|
||||
@@ -608,7 +608,9 @@ 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. Defaults to {@code "device"}.
|
||||
* high-dpi devices will be twice as large or even larger.
|
||||
*
|
||||
* <p> Defaults to {@code "device"}.
|
||||
*/
|
||||
public ScreenshotScale scale;
|
||||
/**
|
||||
@@ -679,7 +681,9 @@ 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. Defaults to {@code "device"}.
|
||||
* high-dpi devices will be twice as large or even larger.
|
||||
*
|
||||
* <p> Defaults to {@code "device"}.
|
||||
*/
|
||||
public ScreenshotOptions setScale(ScreenshotScale scale) {
|
||||
this.scale = scale;
|
||||
@@ -1210,7 +1214,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 bonding box, similarly to <a
|
||||
* <p> Scrolling affects the returned bounding 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.click("upload"));
|
||||
* FileChooser fileChooser = page.waitForFileChooser(() -> page.locator("upload").click());
|
||||
* fileChooser.setFiles(Paths.get("myfile.pdf"));
|
||||
* }</pre>
|
||||
*/
|
||||
|
||||
@@ -2366,9 +2366,25 @@ 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}.
|
||||
|
||||
@@ -1035,7 +1035,9 @@ 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. Defaults to {@code "device"}.
|
||||
* high-dpi devices will be twice as large or even larger.
|
||||
*
|
||||
* <p> Defaults to {@code "device"}.
|
||||
*/
|
||||
public ScreenshotScale scale;
|
||||
/**
|
||||
@@ -1106,7 +1108,9 @@ 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. Defaults to {@code "device"}.
|
||||
* high-dpi devices will be twice as large or even larger.
|
||||
*
|
||||
* <p> Defaults to {@code "device"}.
|
||||
*/
|
||||
public ScreenshotOptions setScale(ScreenshotScale scale) {
|
||||
this.scale = scale;
|
||||
@@ -1632,7 +1636,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 bonding box, similarly to <a
|
||||
* <p> Scrolling affects the returned bounding 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.
|
||||
*
|
||||
@@ -1653,7 +1657,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 bonding box, similarly to <a
|
||||
* <p> Scrolling affects the returned bounding 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.
|
||||
*
|
||||
@@ -2119,13 +2123,35 @@ public interface Locator {
|
||||
*/
|
||||
void fill(String value, FillOptions options);
|
||||
/**
|
||||
* This method narrows existing locator according to the options, for example filters by text.
|
||||
* 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>
|
||||
*/
|
||||
default Locator filter() {
|
||||
return filter(null);
|
||||
}
|
||||
/**
|
||||
* This method narrows existing locator according to the options, for example filters by text.
|
||||
* 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>
|
||||
*/
|
||||
Locator filter(FilterOptions options);
|
||||
/**
|
||||
|
||||
@@ -1997,6 +1997,62 @@ 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
|
||||
@@ -2046,7 +2102,9 @@ 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. Defaults to {@code "device"}.
|
||||
* high-dpi devices will be twice as large or even larger.
|
||||
*
|
||||
* <p> Defaults to {@code "device"}.
|
||||
*/
|
||||
public ScreenshotScale scale;
|
||||
/**
|
||||
@@ -2138,7 +2196,9 @@ 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. Defaults to {@code "device"}.
|
||||
* high-dpi devices will be twice as large or even larger.
|
||||
*
|
||||
* <p> Defaults to {@code "device"}.
|
||||
*/
|
||||
public ScreenshotOptions setScale(ScreenshotScale scale) {
|
||||
this.scale = scale;
|
||||
@@ -3521,9 +3581,25 @@ 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
|
||||
@@ -4764,7 +4840,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. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
|
||||
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
|
||||
*
|
||||
* <p> An example of a naive handler that aborts all image requests:
|
||||
* <pre>{@code
|
||||
@@ -4817,7 +4893,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. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
|
||||
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
|
||||
*
|
||||
* <p> An example of a naive handler that aborts all image requests:
|
||||
* <pre>{@code
|
||||
@@ -4868,7 +4944,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. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
|
||||
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
|
||||
*
|
||||
* <p> An example of a naive handler that aborts all image requests:
|
||||
* <pre>{@code
|
||||
@@ -4921,7 +4997,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. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
|
||||
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
|
||||
*
|
||||
* <p> An example of a naive handler that aborts all image requests:
|
||||
* <pre>{@code
|
||||
@@ -4972,7 +5048,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. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
|
||||
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
|
||||
*
|
||||
* <p> An example of a naive handler that aborts all image requests:
|
||||
* <pre>{@code
|
||||
@@ -5025,7 +5101,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. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
|
||||
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
|
||||
*
|
||||
* <p> An example of a naive handler that aborts all image requests:
|
||||
* <pre>{@code
|
||||
@@ -5067,6 +5143,32 @@ 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,8 +58,9 @@ public interface Request {
|
||||
*/
|
||||
Frame frame();
|
||||
/**
|
||||
* **DEPRECATED** Incomplete list of headers as seen by the rendering engine. Use {@link Request#allHeaders
|
||||
* Request.allHeaders()} instead.
|
||||
* 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.
|
||||
*/
|
||||
Map<String, String> headers();
|
||||
/**
|
||||
|
||||
@@ -40,8 +40,14 @@ public interface Response {
|
||||
*/
|
||||
Frame frame();
|
||||
/**
|
||||
* **DEPRECATED** Incomplete list of headers as seen by the rendering engine. Use {@link Response#allHeaders
|
||||
* Response.allHeaders()} instead.
|
||||
* Indicates whether this Response was fullfilled 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.
|
||||
*/
|
||||
Map<String, String> headers();
|
||||
/**
|
||||
|
||||
@@ -80,6 +80,62 @@ 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.
|
||||
@@ -200,8 +256,8 @@ public interface Route {
|
||||
* page.route("**\/*", route -> {
|
||||
* // Override headers
|
||||
* Map<String, String> headers = new HashMap<>(route.request().headers());
|
||||
* headers.put("foo", "bar"); // set "foo" header
|
||||
* headers.remove("origin"); // remove "origin" header
|
||||
* headers.put("foo", "foo-value"); // set "foo" header
|
||||
* headers.remove("bar"); // remove "bar" header
|
||||
* route.resume(new Route.ResumeOptions().setHeaders(headers));
|
||||
* });
|
||||
* }</pre>
|
||||
@@ -215,13 +271,133 @@ public interface Route {
|
||||
* page.route("**\/*", route -> {
|
||||
* // Override headers
|
||||
* Map<String, String> headers = new HashMap<>(route.request().headers());
|
||||
* headers.put("foo", "bar"); // set "foo" header
|
||||
* headers.remove("origin"); // remove "origin" header
|
||||
* headers.put("foo", "foo-value"); // set "foo" header
|
||||
* headers.remove("bar"); // remove "bar" 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.click("tag=div >> text=\"Click me\"");
|
||||
* page.locator("tag=div >> text=\"Click me\"").click();
|
||||
* // 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.click("tag=div >> text=\"Click me\"");
|
||||
* page.locator("tag=div >> text=\"Click me\"").click();
|
||||
* // 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.click("tag=div >> text=\"Click me\"");
|
||||
* page.locator("tag=div >> text=\"Click me\"").click();
|
||||
* // 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.click("tag=div >> text=\"Click me\"");
|
||||
* page.locator("tag=div >> text=\"Click me\"").click();
|
||||
* // 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.click("text=Get Started");
|
||||
* page.locator("text=Get Started").click();
|
||||
* // 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.click("text=Get Started");
|
||||
* page.locator("text=Get Started").click();
|
||||
* // Everything between startChunk and stopChunk will be recorded in the trace.
|
||||
* context.tracing().stopChunk(new Tracing.StopChunkOptions()
|
||||
* .setPath(Paths.get("trace1.zip")));
|
||||
|
||||
+121
-9
@@ -31,7 +31,7 @@ import java.util.regex.Pattern;
|
||||
* @Test
|
||||
* void statusBecomesSubmitted() {
|
||||
* ...
|
||||
* page.click("#submit-button");
|
||||
* page.locator("#submit-button").click();
|
||||
* assertThat(page.locator(".status")).hasText("Submitted");
|
||||
* }
|
||||
* }
|
||||
@@ -156,6 +156,11 @@ 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.
|
||||
*/
|
||||
@@ -165,6 +170,14 @@ 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.
|
||||
*/
|
||||
@@ -265,6 +278,11 @@ 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.
|
||||
*/
|
||||
@@ -274,6 +292,14 @@ 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.
|
||||
*/
|
||||
@@ -303,6 +329,20 @@ 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"}:
|
||||
@@ -624,9 +664,11 @@ public interface LocatorAssertions {
|
||||
*/
|
||||
void hasAttribute(String name, Pattern value, HasAttributeOptions options);
|
||||
/**
|
||||
* Ensures the {@code Locator} points to an element with given CSS class.
|
||||
* 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.
|
||||
* <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:
|
||||
@@ -640,9 +682,11 @@ public interface LocatorAssertions {
|
||||
hasClass(expected, null);
|
||||
}
|
||||
/**
|
||||
* Ensures the {@code Locator} points to an element with given CSS class.
|
||||
* 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.
|
||||
* <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:
|
||||
@@ -654,9 +698,11 @@ public interface LocatorAssertions {
|
||||
*/
|
||||
void hasClass(String expected, HasClassOptions options);
|
||||
/**
|
||||
* Ensures the {@code Locator} points to an element with given CSS class.
|
||||
* 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.
|
||||
* <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:
|
||||
@@ -670,9 +716,11 @@ public interface LocatorAssertions {
|
||||
hasClass(expected, null);
|
||||
}
|
||||
/**
|
||||
* Ensures the {@code Locator} points to an element with given CSS class.
|
||||
* 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.
|
||||
* <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:
|
||||
@@ -684,9 +732,11 @@ public interface LocatorAssertions {
|
||||
*/
|
||||
void hasClass(Pattern expected, HasClassOptions options);
|
||||
/**
|
||||
* Ensures the {@code Locator} points to an element with given CSS class.
|
||||
* 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.
|
||||
* <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:
|
||||
@@ -700,9 +750,11 @@ public interface LocatorAssertions {
|
||||
hasClass(expected, null);
|
||||
}
|
||||
/**
|
||||
* Ensures the {@code Locator} points to an element with given CSS class.
|
||||
* 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.
|
||||
* <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:
|
||||
@@ -714,9 +766,11 @@ public interface LocatorAssertions {
|
||||
*/
|
||||
void hasClass(String[] expected, HasClassOptions options);
|
||||
/**
|
||||
* Ensures the {@code Locator} points to an element with given CSS class.
|
||||
* 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.
|
||||
* <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:
|
||||
@@ -730,9 +784,11 @@ public interface LocatorAssertions {
|
||||
hasClass(expected, null);
|
||||
}
|
||||
/**
|
||||
* Ensures the {@code Locator} points to an element with given CSS class.
|
||||
* 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.
|
||||
* <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:
|
||||
@@ -1043,5 +1099,61 @@ 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.click("#login");
|
||||
* page.locator("#login").click();
|
||||
* assertThat(page).hasURL(Pattern.compile(".*\/login"));
|
||||
* }
|
||||
* }
|
||||
@@ -120,7 +120,7 @@ public interface PageAssertions {
|
||||
* assertThat(page).hasURL(".com");
|
||||
* }</pre>
|
||||
*
|
||||
* @param urlOrRegExp Expected substring or RegExp.
|
||||
* @param urlOrRegExp Expected URL string 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 substring or RegExp.
|
||||
* @param urlOrRegExp Expected URL string or RegExp.
|
||||
*/
|
||||
void hasURL(String urlOrRegExp, HasURLOptions options);
|
||||
/**
|
||||
@@ -140,7 +140,7 @@ public interface PageAssertions {
|
||||
* assertThat(page).hasURL(".com");
|
||||
* }</pre>
|
||||
*
|
||||
* @param urlOrRegExp Expected substring or RegExp.
|
||||
* @param urlOrRegExp Expected URL string 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 substring or RegExp.
|
||||
* @param urlOrRegExp Expected URL string or RegExp.
|
||||
*/
|
||||
void hasURL(Pattern urlOrRegExp, HasURLOptions options);
|
||||
}
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@ import com.microsoft.playwright.impl.PageAssertionsImpl;
|
||||
* @Test
|
||||
* void statusBecomesSubmitted() {
|
||||
* ...
|
||||
* page.click("#submit-button");
|
||||
* page.locator("#submit-button").click();
|
||||
* assertThat(page.locator(".status")).hasText("Submitted");
|
||||
* }
|
||||
* }
|
||||
|
||||
@@ -20,21 +20,20 @@ import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.*;
|
||||
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 com.microsoft.playwright.options.*;
|
||||
|
||||
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;
|
||||
@@ -54,7 +53,17 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
final TimeoutSettings timeoutSettings = new TimeoutSettings();
|
||||
Path videosDir;
|
||||
URL baseUrl;
|
||||
Path recordHarPath;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
enum EventType {
|
||||
CLOSE,
|
||||
@@ -77,6 +86,12 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
this.request = connection.getExistingObject(initializer.getAsJsonObject("APIRequestContext").get("guid").getAsString());
|
||||
}
|
||||
|
||||
void setRecordHar(Path path, HarContentPolicy policy) {
|
||||
if (path != null) {
|
||||
harRecorders.put("", new HarRecorder(path, policy));
|
||||
}
|
||||
}
|
||||
|
||||
void setBaseUrl(String spec) {
|
||||
try {
|
||||
this.baseUrl = new URL(spec);
|
||||
@@ -181,15 +196,31 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
}
|
||||
isClosedOrClosing = true;
|
||||
try {
|
||||
if (recordHarPath != null) {
|
||||
JsonObject json = sendMessage("harExport").getAsJsonObject();
|
||||
for (Map.Entry<String, HarRecorder> entry : harRecorders.entrySet()) {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("harId", entry.getKey());
|
||||
JsonObject json = sendMessage("harExport", params).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;
|
||||
}
|
||||
artifact.saveAs(recordHarPath);
|
||||
|
||||
// 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.delete();
|
||||
}
|
||||
|
||||
@@ -336,7 +367,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
|
||||
@Override
|
||||
public void route(String url, Consumer<Route> handler, RouteOptions options) {
|
||||
route(new UrlMatcher(this.baseUrl, url), handler, options);
|
||||
route(new UrlMatcher(baseUrl, url), handler, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -349,6 +380,21 @@ 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);
|
||||
@@ -360,6 +406,22 @@ 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", () -> {
|
||||
@@ -474,11 +536,12 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
}
|
||||
}
|
||||
|
||||
void handleRoute(Route route) {
|
||||
boolean handled = routes.handle(route);
|
||||
if (handled) {
|
||||
void handleRoute(RouteImpl route) {
|
||||
Router.HandleResult handled = routes.handle(route);
|
||||
if (handled == Router.HandleResult.FoundMatchingHandler) {
|
||||
maybeDisableNetworkInterception();
|
||||
} else {
|
||||
}
|
||||
if (!route.isHandled()){
|
||||
route.resume();
|
||||
}
|
||||
}
|
||||
@@ -490,7 +553,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
@Override
|
||||
protected void handleEvent(String event, JsonObject params) {
|
||||
if ("route".equals(event)) {
|
||||
Route route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString());
|
||||
RouteImpl 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,20 +19,21 @@ package com.microsoft.playwright.impl;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.Browser;
|
||||
import com.microsoft.playwright.BrowserContext;
|
||||
import com.microsoft.playwright.Page;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import com.microsoft.playwright.*;
|
||||
import com.microsoft.playwright.options.HarContentPolicy;
|
||||
|
||||
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<>();
|
||||
@@ -40,7 +41,7 @@ class BrowserImpl extends ChannelOwner implements Browser {
|
||||
boolean isRemote;
|
||||
boolean isConnectedOverWebSocket;
|
||||
private boolean isConnected = true;
|
||||
LocalUtils localUtils;
|
||||
BrowserTypeImpl browserType;
|
||||
|
||||
enum EventType {
|
||||
DISCONNECTED,
|
||||
@@ -60,6 +61,11 @@ 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());
|
||||
@@ -112,6 +118,9 @@ 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 {
|
||||
@@ -127,21 +136,50 @@ 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 (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");
|
||||
if (recordHar != null) {
|
||||
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();
|
||||
@@ -171,8 +209,7 @@ class BrowserImpl extends ChannelOwner implements Browser {
|
||||
if (options.baseURL != null) {
|
||||
context.setBaseUrl(options.baseURL);
|
||||
}
|
||||
context.recordHarPath = options.recordHarPath;
|
||||
context.tracing().localUtils = localUtils;
|
||||
context.setRecordHar(recordHarPath, harContentPolicy);
|
||||
contexts.add(context);
|
||||
return context;
|
||||
}
|
||||
@@ -193,9 +230,7 @@ class BrowserImpl extends ChannelOwner implements Browser {
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
if (page != null) {
|
||||
JsonObject jsonPage = new JsonObject();
|
||||
jsonPage.addProperty("guid", ((PageImpl) page).guid);
|
||||
params.add("page", jsonPage);
|
||||
params.add("page", ((PageImpl) page).toProtocolRef());
|
||||
}
|
||||
sendMessage("startTracing", params);
|
||||
}
|
||||
|
||||
@@ -22,12 +22,15 @@ 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;
|
||||
@@ -48,7 +51,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.localUtils = localUtils;
|
||||
browser.browserType = this;
|
||||
return browser;
|
||||
}
|
||||
|
||||
@@ -82,7 +85,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);
|
||||
Connection connection = new Connection(pipe, this.connection.env);
|
||||
PlaywrightImpl playwright = connection.initializePlaywright();
|
||||
if (!playwright.initializer.has("preLaunchedBrowser")) {
|
||||
try {
|
||||
@@ -96,7 +99,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.localUtils = localUtils;
|
||||
browser.browserType = this;
|
||||
Consumer<JsonPipe> connectionCloseListener = t -> browser.notifyRemoteClosed();
|
||||
pipe.onClose(connectionCloseListener);
|
||||
browser.onDisconnected(b -> {
|
||||
@@ -130,7 +133,7 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
|
||||
|
||||
BrowserImpl browser = connection.getExistingObject(json.getAsJsonObject("browser").get("guid").getAsString());
|
||||
browser.isRemote = true;
|
||||
browser.localUtils = localUtils;
|
||||
browser.browserType = this;
|
||||
if (json.has("defaultContext")) {
|
||||
String contextId = json.getAsJsonObject("defaultContext").get("guid").getAsString();
|
||||
BrowserContextImpl defaultContext = connection.getExistingObject(contextId);
|
||||
@@ -152,20 +155,52 @@ 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 (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");
|
||||
if (recordHar != null) {
|
||||
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();
|
||||
@@ -195,8 +230,7 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
|
||||
if (options.baseURL != null) {
|
||||
context.setBaseUrl(options.baseURL);
|
||||
}
|
||||
context.recordHarPath = options.recordHarPath;
|
||||
context.tracing().localUtils = localUtils;
|
||||
context.setRecordHar(recordHarPath, harContentPolicy);
|
||||
return context;
|
||||
}
|
||||
|
||||
|
||||
@@ -108,4 +108,10 @@ class ChannelOwner extends LoggingSupport {
|
||||
|
||||
void handleEvent(String event, JsonObject parameters) {
|
||||
}
|
||||
|
||||
JsonObject toProtocolRef() {
|
||||
JsonObject json = new JsonObject();
|
||||
json.addProperty("guid", guid);
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,8 @@ 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) {
|
||||
@@ -78,13 +80,14 @@ public class Connection {
|
||||
}
|
||||
}
|
||||
|
||||
Connection(Transport transport) {
|
||||
Connection(Transport transport, Map<String, String> env) {
|
||||
this.env = env;
|
||||
if (isLogging) {
|
||||
transport = new TransportLogger(transport);
|
||||
}
|
||||
this.transport = transport;
|
||||
root = new Root(this);
|
||||
stackTraceCollector = StackTraceCollector.createFromEnv();
|
||||
stackTraceCollector = StackTraceCollector.createFromEnv(env);
|
||||
}
|
||||
|
||||
boolean isCollectingStacks() {
|
||||
@@ -138,6 +141,10 @@ 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)
|
||||
@@ -270,7 +277,8 @@ public class Connection {
|
||||
result = new JsonPipe(parent, type, guid, initializer);
|
||||
break;
|
||||
case "LocalUtils":
|
||||
result = new LocalUtils(parent, type, guid, initializer);
|
||||
localUtils = new LocalUtils(parent, type, guid, initializer);
|
||||
result = localUtils;
|
||||
break;
|
||||
case "Page":
|
||||
result = new PageImpl(parent, type, guid, initializer);
|
||||
|
||||
@@ -23,7 +23,6 @@ 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;
|
||||
@@ -1048,6 +1047,13 @@ 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");
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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,6 +19,7 @@ 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;
|
||||
@@ -39,6 +40,7 @@ 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));
|
||||
@@ -47,6 +49,7 @@ 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));
|
||||
@@ -58,6 +61,7 @@ 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);
|
||||
@@ -70,6 +74,7 @@ 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);
|
||||
@@ -203,6 +208,7 @@ 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));
|
||||
@@ -211,6 +217,7 @@ 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;
|
||||
@@ -223,6 +230,7 @@ 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);
|
||||
@@ -235,6 +243,7 @@ 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);
|
||||
@@ -255,6 +264,28 @@ 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";
|
||||
@@ -305,5 +336,17 @@ 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,7 +67,8 @@ class LocatorImpl implements Locator {
|
||||
if (options.hasText != null) {
|
||||
if (options.hasText instanceof Pattern) {
|
||||
Pattern pattern = (Pattern) options.hasText;
|
||||
selector += " >> :scope:text-matches(" + escapeWithQuotes(pattern.pattern()) + ", \"" + toJsRegexFlags(pattern) + "\")";
|
||||
String jsRegex = "/" + pattern.pattern() + "/" + toJsRegexFlags(pattern);
|
||||
selector += " >> has=" + gson().toJson("text=" + jsRegex);
|
||||
} else if (options.hasText instanceof String) {
|
||||
String text = (String) options.hasText;
|
||||
selector += " >> :scope:has-text(" + escapeWithQuotes(text) + ")";
|
||||
@@ -510,9 +511,7 @@ class LocatorImpl implements Locator {
|
||||
|
||||
JsonObject toProtocol() {
|
||||
JsonObject result = new JsonObject();
|
||||
JsonObject frameJson = new JsonObject();
|
||||
frameJson.addProperty("guid", frame.guid);
|
||||
result.add("frame", frameJson);
|
||||
result.add("frame", frame.toProtocolRef());
|
||||
result.addProperty("selector", selector);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -60,7 +60,11 @@ class LoggingSupport {
|
||||
System.err.println(timestamp + " " + message);
|
||||
}
|
||||
|
||||
private void logApi(String message) {
|
||||
static boolean isApiLoggingEnabled() {
|
||||
return isEnabled;
|
||||
}
|
||||
|
||||
static void logApi(String message) {
|
||||
// This matches log format produced by the server.
|
||||
logWithTimestamp("pw:api " + message);
|
||||
}
|
||||
|
||||
@@ -175,10 +175,6 @@ 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);
|
||||
@@ -198,11 +194,12 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
}
|
||||
listeners.notify(EventType.FRAMEDETACHED, frame);
|
||||
} else if ("route".equals(event)) {
|
||||
Route route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString());
|
||||
boolean handled = routes.handle(route);
|
||||
if (handled) {
|
||||
RouteImpl route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString());
|
||||
Router.HandleResult handled = routes.handle(route);
|
||||
if (handled == Router.HandleResult.FoundMatchingHandler) {
|
||||
maybeDisableNetworkInterception();
|
||||
} else {
|
||||
}
|
||||
if (!route.isHandled()) {
|
||||
browserContext.handleRoute(route);
|
||||
}
|
||||
} else if ("video".equals(event)) {
|
||||
@@ -972,6 +969,21 @@ 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);
|
||||
|
||||
@@ -43,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()));
|
||||
Connection connection = new Connection(new PipeTransport(p.getInputStream(), p.getOutputStream()), env);
|
||||
PlaywrightImpl result = connection.initializePlaywright();
|
||||
result.driverProcess = p;
|
||||
result.initSharedSelectors(null);
|
||||
|
||||
@@ -31,6 +31,7 @@ class SerializedValue{
|
||||
// Possible values: { 'null, 'undefined, 'NaN, 'Infinity, '-Infinity, '-0 }
|
||||
String v;
|
||||
String d;
|
||||
String u;
|
||||
public static class R {
|
||||
String p;
|
||||
String f;
|
||||
@@ -85,6 +86,7 @@ class ExpectedTextValue {
|
||||
String string;
|
||||
String regexSource;
|
||||
String regexFlags;
|
||||
Boolean ignoreCase;
|
||||
Boolean matchSubstring;
|
||||
Boolean normalizeWhiteSpace;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,14 @@ 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);
|
||||
@@ -75,6 +83,9 @@ 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();
|
||||
}
|
||||
|
||||
@@ -95,19 +106,26 @@ 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() {
|
||||
if (postData == null) {
|
||||
byte[] buffer = postDataBuffer();
|
||||
if (buffer == null) {
|
||||
return null;
|
||||
}
|
||||
return new String(postData, StandardCharsets.UTF_8);
|
||||
return new String(buffer, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] postDataBuffer() {
|
||||
if (fallbackOverrides != null && fallbackOverrides.postData != null) {
|
||||
return fallbackOverrides.postData;
|
||||
}
|
||||
return postData;
|
||||
}
|
||||
|
||||
@@ -156,6 +174,9 @@ 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();
|
||||
}
|
||||
|
||||
@@ -164,6 +185,9 @@ 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;
|
||||
}
|
||||
@@ -176,4 +200,26 @@ 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,6 +83,11 @@ 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,6 +17,7 @@
|
||||
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;
|
||||
|
||||
@@ -27,6 +28,8 @@ 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;
|
||||
|
||||
@@ -44,41 +47,69 @@ public class RouteImpl extends ChannelOwner implements Route {
|
||||
});
|
||||
}
|
||||
|
||||
boolean isHandled() {
|
||||
return handled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resume(ResumeOptions options) {
|
||||
startHandling();
|
||||
withLogging("Route.resume", () -> resumeImpl(options));
|
||||
applyOverrides(convertType(options, FallbackOptions.class));
|
||||
withLogging("Route.resume", () -> resumeImpl(request().fallbackOverridesForResume()));
|
||||
}
|
||||
|
||||
private void resumeImpl(ResumeOptions options) {
|
||||
@Override
|
||||
public void fallback(FallbackOptions options) {
|
||||
if (handled) {
|
||||
throw new PlaywrightException("Route is already handled!");
|
||||
}
|
||||
applyOverrides(options);
|
||||
}
|
||||
|
||||
private void applyOverrides(FallbackOptions options) {
|
||||
if (options == null) {
|
||||
options = new ResumeOptions();
|
||||
}
|
||||
JsonObject params = new JsonObject();
|
||||
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));
|
||||
return;
|
||||
}
|
||||
RequestImpl.FallbackOverrides overrides = new RequestImpl.FallbackOverrides();
|
||||
overrides.url = options.url;
|
||||
overrides.method = options.method;
|
||||
overrides.headers = 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());
|
||||
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);
|
||||
}
|
||||
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();
|
||||
@@ -168,6 +199,14 @@ 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,6 +19,7 @@ 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;
|
||||
@@ -37,10 +38,7 @@ class Router {
|
||||
this.times = times;
|
||||
}
|
||||
|
||||
boolean handle(Route route) {
|
||||
if (times != null && times <= 0) {
|
||||
return false;
|
||||
}
|
||||
boolean handle(RouteImpl route) {
|
||||
if (!matcher.test(route.request().url())) {
|
||||
return false;
|
||||
}
|
||||
@@ -70,15 +68,21 @@ class Router {
|
||||
return routes.size();
|
||||
}
|
||||
|
||||
boolean handle(Route route) {
|
||||
for (RouteInfo info : routes) {
|
||||
enum HandleResult { NoMatchingHandler, FoundMatchingHandler}
|
||||
HandleResult handle(RouteImpl route) {
|
||||
HandleResult result = HandleResult.NoMatchingHandler;
|
||||
for (Iterator<RouteInfo> it = routes.iterator(); it.hasNext();) {
|
||||
RouteInfo info = it.next();
|
||||
if (info.handle(route)) {
|
||||
result = HandleResult.FoundMatchingHandler;
|
||||
if (info.isDone()) {
|
||||
routes.remove(info);
|
||||
it.remove();
|
||||
}
|
||||
if (route.isHandled()) {
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,12 +28,19 @@ 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()
|
||||
private static final Gson gson = new GsonBuilder().disableHtmlEscaping()
|
||||
.registerTypeAdapter(SameSiteAttribute.class, new SameSiteAdapter().nullSafe())
|
||||
.registerTypeAdapter(BrowserChannel.class, new ToLowerCaseAndDashSerializer<BrowserChannel>())
|
||||
.registerTypeAdapter(ColorScheme.class, new ToLowerCaseAndDashSerializer<ColorScheme>())
|
||||
@@ -44,6 +51,7 @@ 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>())
|
||||
@@ -140,6 +148,14 @@ 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);
|
||||
@@ -203,6 +219,17 @@ 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":
|
||||
@@ -287,9 +314,7 @@ class Serialization {
|
||||
static JsonArray toProtocol(ElementHandle[] handles) {
|
||||
JsonArray jsonElements = new JsonArray();
|
||||
for (ElementHandle handle : handles) {
|
||||
JsonObject jsonHandle = new JsonObject();
|
||||
jsonHandle.addProperty("guid", ((ElementHandleImpl) handle).guid);
|
||||
jsonElements.add(jsonHandle);
|
||||
jsonElements.add(((ElementHandleImpl) handle).toProtocolRef());
|
||||
}
|
||||
return jsonElements;
|
||||
}
|
||||
@@ -298,6 +323,16 @@ 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()) {
|
||||
@@ -309,6 +344,15 @@ 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) {
|
||||
@@ -339,9 +383,7 @@ class Serialization {
|
||||
private static class HandleSerializer implements JsonSerializer<JSHandleImpl> {
|
||||
@Override
|
||||
public JsonElement serialize(JSHandleImpl src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
JsonObject json = new JsonObject();
|
||||
json.addProperty("guid", src.guid);
|
||||
return json;
|
||||
return src.toProtocolRef();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,18 +28,25 @@ 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() {
|
||||
String srcRoots = System.getenv("PLAYWRIGHT_JAVA_SRC");
|
||||
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);
|
||||
}
|
||||
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,7 +26,6 @@ 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) {
|
||||
@@ -60,7 +59,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");
|
||||
localUtils.zip(path, entries);
|
||||
connection.localUtils.zip(path, entries);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -176,11 +176,18 @@ class Utils {
|
||||
} catch (IOException e) {
|
||||
throw new PlaywrightException("Failed to copy file to remote server.", e);
|
||||
}
|
||||
jsonStreams.add(temp.toProtocol());
|
||||
jsonStreams.add(temp.toProtocolRef());
|
||||
}
|
||||
params.add("streams", jsonStreams);
|
||||
} else {
|
||||
params.add("localPaths", toJsonArray(files));
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,6 +277,17 @@ 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) {
|
||||
@@ -289,4 +307,18 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,10 +30,4 @@ class WritableStream extends ChannelOwner {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
JsonObject toProtocol() {
|
||||
JsonObject json = new JsonObject();
|
||||
json.addProperty("guid", guid);
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.options;
|
||||
|
||||
public enum HarMode {
|
||||
FULL,
|
||||
MINIMAL
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.options;
|
||||
|
||||
public enum HarNotFound {
|
||||
ABORT,
|
||||
FALLBACK
|
||||
}
|
||||
@@ -19,7 +19,8 @@ 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}.
|
||||
* The {@code RequestOptions} allows to create form data to be sent via {@code APIRequestContext}. Playwright will automatically
|
||||
* determine content type of the request.
|
||||
* <pre>{@code
|
||||
* context.request().post(
|
||||
* "https://example.com/submit",
|
||||
@@ -27,6 +28,33 @@ 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 {
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.options;
|
||||
|
||||
public enum ServiceWorkerPolicy {
|
||||
ALLOW,
|
||||
BLOCK
|
||||
}
|
||||
@@ -72,8 +72,12 @@ public class TestBase {
|
||||
return options;
|
||||
}
|
||||
|
||||
Playwright.CreateOptions playwrightOptions() {
|
||||
return null;
|
||||
}
|
||||
|
||||
void initBrowserType() {
|
||||
playwright = Playwright.create();
|
||||
playwright = Playwright.create(playwrightOptions());
|
||||
browserType = Utils.getBrowserTypeFromEnv(playwright);
|
||||
}
|
||||
|
||||
|
||||
@@ -97,4 +97,9 @@ public class TestBrowser extends TestBase {
|
||||
assertNotNull(browser);
|
||||
browser.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnBrowserType() {
|
||||
assertEquals(browserType, browser.browserType());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,13 +65,19 @@ 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);
|
||||
assertEquals(timestamp, cookie.expires);
|
||||
// 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(false, cookie.httpOnly);
|
||||
assertEquals(false, cookie.secure);
|
||||
if (isChromium()) {
|
||||
|
||||
@@ -0,0 +1,457 @@
|
||||
/*
|
||||
* 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-CH"));
|
||||
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("fr-FR"));
|
||||
Page page = context.newPage();
|
||||
Future<Server.Request> request = server.futureRequest("/empty.html");
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
assertEquals("fr-CH", request.get().headers.get("accept-language").get(0).substring(0, 5));
|
||||
assertEquals("fr-FR", 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-CH"));
|
||||
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("fr-FR"));
|
||||
Page page = context.newPage();
|
||||
assertEquals("fr-CH", page.evaluate("() => navigator.language"));
|
||||
assertEquals("fr-FR", 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-CH"));
|
||||
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("fr-FR"));
|
||||
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-CH"));
|
||||
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("fr-FR"));
|
||||
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-CH"));
|
||||
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("fr-FR"));
|
||||
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-CH", result);
|
||||
assertEquals("fr-FR", result);
|
||||
context.close();
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ public class TestBrowserContextLocale extends TestBase {
|
||||
defaultLocale = getContextLocale.apply(context);
|
||||
context.close();
|
||||
}
|
||||
String localeOverride = "ru-RU".equals(defaultLocale) ? "de-DE" : "ru-RU";
|
||||
String localeOverride = "es-MX".equals(defaultLocale) ? "de-DE" : "es-MX";
|
||||
{
|
||||
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale(localeOverride));
|
||||
assertEquals(localeOverride, getContextLocale.apply(context));
|
||||
|
||||
@@ -19,6 +19,7 @@ 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;
|
||||
@@ -62,28 +63,28 @@ public class TestBrowserContextRoute extends TestBase {
|
||||
List<Integer> intercepted = new ArrayList<>();
|
||||
context.route("**/*", route -> {
|
||||
intercepted.add(1);
|
||||
route.resume();
|
||||
route.fallback();
|
||||
});
|
||||
context.route("**/empty.html", route -> {
|
||||
intercepted.add(2);
|
||||
route.resume();
|
||||
route.fallback();
|
||||
});
|
||||
context.route("**/empty.html", route -> {
|
||||
intercepted.add(3);
|
||||
route.resume();
|
||||
route.fallback();
|
||||
});
|
||||
Consumer<Route> handler4 = route -> {
|
||||
intercepted.add(4);
|
||||
route.resume();
|
||||
route.fallback();
|
||||
};
|
||||
context.route("**/empty.html", handler4);
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
assertEquals(asList(4), intercepted);
|
||||
assertEquals(asList(4, 3, 2, 1), intercepted);
|
||||
|
||||
intercepted.clear();
|
||||
context.unroute("**/empty.html", handler4);
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
assertEquals(asList(3), intercepted);
|
||||
assertEquals(asList(3, 2, 1), intercepted);
|
||||
|
||||
intercepted.clear();
|
||||
context.unroute("**/empty.html");
|
||||
@@ -207,4 +208,118 @@ 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,7 +17,6 @@
|
||||
package com.microsoft.playwright;
|
||||
|
||||
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;
|
||||
@@ -499,7 +498,7 @@ public class TestBrowserTypeConnect extends TestBase {
|
||||
Path trace = tmpDir.resolve("trace1.zip");
|
||||
context.tracing().stop(new Tracing.StopOptions().setPath(trace));
|
||||
|
||||
Map<String, byte[]> entries = parseTrace(trace);
|
||||
Map<String, byte[]> entries = 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());
|
||||
|
||||
|
||||
@@ -82,8 +82,8 @@ public class TestDefaultBrowserContext2 extends TestBase {
|
||||
@Test
|
||||
void shouldSupportLocaleOption() {
|
||||
Page page = launchPersistent(new BrowserType.LaunchPersistentContextOptions()
|
||||
.setLocale("fr-CH"));
|
||||
assertEquals("fr-CH", page.evaluate("navigator.language"));
|
||||
.setLocale("fr-FR"));
|
||||
assertEquals("fr-FR", page.evaluate("navigator.language"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -16,47 +16,56 @@
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.*;
|
||||
import com.microsoft.playwright.options.HarContentPolicy;
|
||||
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.condition.DisabledIf;
|
||||
import org.junit.jupiter.api.condition.EnabledIf;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
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 {
|
||||
harFile = Files.createTempFile("test-", ".har");
|
||||
context = browser.newContext(new Browser.NewContextOptions()
|
||||
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
|
||||
.setRecordHarPath(harFile).setIgnoreHTTPSErrors(true));
|
||||
page = context.newPage();
|
||||
}
|
||||
|
||||
JsonObject log() throws IOException {
|
||||
context.close();
|
||||
try (FileReader json = new FileReader(harFile.toFile())) {
|
||||
return new Gson().fromJson(json, JsonObject.class).getAsJsonObject("log");
|
||||
}
|
||||
return parseHar(harFile);
|
||||
}
|
||||
|
||||
Map<String, byte[]> parseZip() throws IOException {
|
||||
context.close();
|
||||
return Utils.parseZip(harFile);
|
||||
}
|
||||
|
||||
void dispose() throws IOException {
|
||||
@@ -176,4 +185,144 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -72,6 +72,28 @@ 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>");
|
||||
@@ -79,6 +101,10 @@ 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
|
||||
@@ -101,6 +127,10 @@ 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
|
||||
@@ -131,6 +161,8 @@ 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
|
||||
@@ -549,6 +581,109 @@ 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>");
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import 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);
|
||||
}
|
||||
}
|
||||
@@ -164,6 +164,24 @@ 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,6 +20,11 @@ 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;
|
||||
@@ -574,20 +579,56 @@ public class TestPageEvaluate extends TestBase {
|
||||
assertTrue(((String) error).contains("Error: error message"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldEvaluateDate() {
|
||||
// TODO: Date values are not supported in java.
|
||||
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);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRoundtripDate() {
|
||||
// TODO: Date values are not supported in java.
|
||||
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());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRoundtripRegex() {
|
||||
// Not applicable
|
||||
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());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldJsonValueDate() {
|
||||
// TODO: Date values are not supported in java.
|
||||
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());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
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,6 +119,33 @@ 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,7 +20,10 @@ 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;
|
||||
|
||||
@@ -93,4 +96,21 @@ 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,10 +18,52 @@ package com.microsoft.playwright;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
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.*;
|
||||
|
||||
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};
|
||||
|
||||
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
* 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 -> {
|
||||
System.out.println("before fulfill");
|
||||
route.fulfill(new Route.FulfillOptions().setStatus(200).setBody("fulfilled one"));
|
||||
System.out.println("after fulfill");
|
||||
}, new Page.RouteOptions().setTimes(1));
|
||||
page.route("**/empty.html", route -> {
|
||||
System.out.println("before fallback");
|
||||
route.fallback();
|
||||
System.out.println("after 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,6 +21,7 @@ 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;
|
||||
@@ -66,29 +67,29 @@ public class TestPageRoute extends TestBase {
|
||||
List<Integer> intercepted = new ArrayList<>();
|
||||
page.route("**/*", route -> {
|
||||
intercepted.add(1);
|
||||
route.resume();
|
||||
route.fallback();
|
||||
});
|
||||
|
||||
page.route("**/empty.html", route -> {
|
||||
intercepted.add(2);
|
||||
route.resume();
|
||||
route.fallback();
|
||||
});
|
||||
page.route("**/empty.html", route -> {
|
||||
intercepted.add(3);
|
||||
route.resume();
|
||||
route.fallback();
|
||||
});
|
||||
Consumer<Route> handler4 = route -> {
|
||||
intercepted.add(4);
|
||||
route.resume();
|
||||
route.fallback();
|
||||
};
|
||||
page.route("**/empty.html", handler4);
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
assertEquals(asList(4), intercepted);
|
||||
assertEquals(asList(4, 3, 2, 1), intercepted);
|
||||
|
||||
intercepted.clear();
|
||||
page.unroute("**/empty.html", handler4);
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
assertEquals(asList(3), intercepted);
|
||||
assertEquals(asList(3, 2, 1), intercepted);
|
||||
|
||||
intercepted.clear();
|
||||
page.unroute("**/empty.html");
|
||||
@@ -102,25 +103,25 @@ public class TestPageRoute extends TestBase {
|
||||
Predicate<String> predicate = r -> true;
|
||||
page.route(predicate, route -> {
|
||||
intercepted.add(1);
|
||||
route.resume();
|
||||
route.fallback();
|
||||
});
|
||||
page.route(predicate, route -> {
|
||||
intercepted.add(2);
|
||||
route.resume();
|
||||
route.fallback();
|
||||
});
|
||||
Consumer<Route> handler3 = route -> {
|
||||
intercepted.add(3);
|
||||
route.resume();
|
||||
route.fallback();
|
||||
};
|
||||
page.route(predicate, handler3);
|
||||
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
assertEquals(asList(3), intercepted);
|
||||
assertEquals(asList(3, 2, 1), intercepted);
|
||||
|
||||
intercepted.clear();
|
||||
page.unroute(predicate, handler3);
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
assertEquals(asList(2), intercepted);
|
||||
assertEquals(asList(2, 1), intercepted);
|
||||
|
||||
intercepted.clear();
|
||||
page.unroute(predicate);
|
||||
@@ -475,7 +476,7 @@ public class TestPageRoute extends TestBase {
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotThrowInvalidInterceptionIdIfTheRequestWasCancelled() {
|
||||
void shouldThrowIfResumeIsCalledAfterRouteHandlerFinished() {
|
||||
page.setContent("<iframe></iframe>");
|
||||
Route[] route = {null};
|
||||
page.route("**/*", r -> route[0] = r);
|
||||
@@ -485,8 +486,9 @@ public class TestPageRoute extends TestBase {
|
||||
page.evalOnSelector("iframe", "frame => frame.remove()");
|
||||
try {
|
||||
route[0].resume();
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
fail("Should not throw");
|
||||
assertTrue(e.getMessage().contains("Route is already handled!"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -723,4 +725,25 @@ 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,6 +22,7 @@ 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;
|
||||
@@ -220,6 +221,7 @@ 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,6 +96,50 @@ 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>");
|
||||
|
||||
@@ -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.parseTrace(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());
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ 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.*;
|
||||
@@ -139,9 +140,13 @@ public class TestWebSocket extends TestBase {
|
||||
boolean[] socketError = {false};
|
||||
String[] error = {null};
|
||||
page.onWebSocket(ws -> {
|
||||
ws.onSocketError(e -> {
|
||||
error[0] = e;
|
||||
socketError[0] = true;
|
||||
ws.onSocketError(new Consumer<String>() {
|
||||
@Override
|
||||
public void accept(String e) {
|
||||
ws.offSocketError(this);
|
||||
error[0] = e;
|
||||
socketError[0] = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
page.evaluate("port => {\n" +
|
||||
|
||||
@@ -22,7 +22,7 @@ import com.google.gson.JsonParser;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
@@ -89,7 +89,7 @@ class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
static Map<String, byte[]> parseTrace(Path trace) throws IOException {
|
||||
static Map<String, byte[]> parseZip(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,6 +104,24 @@ 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();
|
||||
|
||||
@@ -0,0 +1,366 @@
|
||||
{
|
||||
"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": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,620 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Hello, world
|
||||
@@ -0,0 +1,95 @@
|
||||
{
|
||||
"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": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<title>HAR Page</title>
|
||||
<link rel='stylesheet' href='./one-style.css'>
|
||||
<div>hello, world!</div>
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>1.22.0-SNAPSHOT</version>
|
||||
<version>1.24.1</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.6</gson.version>
|
||||
<gson.version>2.8.9</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
@@ -1 +1 @@
|
||||
1.22.0-beta-1652379237000
|
||||
1.24.1
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>api-generator</artifactId>
|
||||
<version>1.22.0-SNAPSHOT</version>
|
||||
<version>1.24.1</version>
|
||||
<name>Playwright - API Generator</name>
|
||||
<description>
|
||||
This is an internal module used to generate Java API from the upstream Playwright
|
||||
@@ -14,7 +14,7 @@
|
||||
</description>
|
||||
<properties>
|
||||
<compiler.version>1.8</compiler.version>
|
||||
<gson.version>2.8.6</gson.version>
|
||||
<gson.version>2.8.9</gson.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
|
||||
@@ -944,7 +944,7 @@ class Interface extends TypeDefinition {
|
||||
if (asList("Page", "Frame", "BrowserContext", "WebSocket").contains(jsonName)) {
|
||||
output.add("import java.util.function.Predicate;");
|
||||
}
|
||||
if (asList("Page", "Frame", "FrameLocator", "Locator", "BrowserContext", "PageAssertions", "LocatorAssertions").contains(jsonName)) {
|
||||
if (asList("Page", "Frame", "FrameLocator", "Locator", "Browser", "BrowserType", "BrowserContext", "PageAssertions", "LocatorAssertions").contains(jsonName)) {
|
||||
output.add("import java.util.regex.Pattern;");
|
||||
}
|
||||
if ("PlaywrightAssertions".equals(jsonName)) {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>test-cli-version</artifactId>
|
||||
<version>1.22.0-SNAPSHOT</version>
|
||||
<version>1.24.1</version>
|
||||
<name>Test Playwright Command Line Version</name>
|
||||
<properties>
|
||||
<compiler.version>1.8</compiler.version>
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>test-local-installation</artifactId>
|
||||
<version>1.22.0-SNAPSHOT</version>
|
||||
<version>1.24.1</version>
|
||||
<name>Test local installation</name>
|
||||
<description>Runs Playwright test suite (copied from playwright module) against locally cached Playwright</description>
|
||||
<properties>
|
||||
<compiler.version>1.8</compiler.version>
|
||||
<gson.version>2.8.6</gson.version>
|
||||
<gson.version>2.8.9</gson.version>
|
||||
<junit.version>5.7.0</junit.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<websocket.version>1.5.1</websocket.version>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>test-spring-boot-starter</artifactId>
|
||||
<version>1.22.0-SNAPSHOT</version>
|
||||
<version>1.24.1</version>
|
||||
<name>Test Playwright With Spring Boot</name>
|
||||
<properties>
|
||||
<spring.version>2.4.3</spring.version>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>update-version</artifactId>
|
||||
<version>1.22.0-SNAPSHOT</version>
|
||||
<version>1.24.1</version>
|
||||
<name>Playwright - Update Version in Documentation</name>
|
||||
<description>
|
||||
This is an internal module used to update versions in the documentation based on
|
||||
|
||||
@@ -10,7 +10,9 @@ RUN apt-get update && \
|
||||
# Install utilities required for downloading browsers
|
||||
curl \
|
||||
# Install utilities required for downloading driver
|
||||
unzip && \
|
||||
unzip \
|
||||
# For the MSEdge install script
|
||||
gpg && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
# Create the pwuser
|
||||
adduser pwuser
|
||||
|
||||
Reference in New Issue
Block a user