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

Compare commits

...

25 Commits

Author SHA1 Message Date
Yury Semikhatsky 8e5074954b chore: mark 1.25.0 (#1027) 2022-08-11 14:00:32 -07:00
Yury Semikhatsky 385131e51b chore: roll driver to 1.25.0 (#1026) 2022-08-11 13:40:57 -07:00
Yury Semikhatsky 6cb9f60988 chore: roll driver to 1.25.0-alpha-1659998098000 (#1025) 2022-08-09 14:07:53 -07:00
Yury Semikhatsky aef9badd64 feat: assertions default timeout (#1023) 2022-08-04 17:29:02 -07:00
Yury Semikhatsky 64f7a059af fix: prevent video.saveAs() from hanging (#1020) 2022-08-01 15:01:43 -07:00
Yury Semikhatsky 436fc12609 feat: roll driver to 1.25.0-alpha-jul-28-2022 (#1015) 2022-07-28 14:23:59 -07:00
Yury Semikhatsky 560575a9b5 test: unflake wheel test (#1014) 2022-07-27 16:39:47 -07:00
Yury Semikhatsky 0afaf6c561 fix: no split packages for compatibility with modules (#1013) 2022-07-27 13:32:25 -07:00
Yury Semikhatsky 202371b5d7 test: locator has with unicode symbols (#1012) 2022-07-26 14:12:47 -07:00
Yury Semikhatsky 2093bba554 fix: do not download browsers if selenium url is set (#1008) 2022-07-25 16:20:38 -07:00
Yury Semikhatsky 77538dcf7f chore: bump dev version to 1.25 (#1007) 2022-07-25 15:56:22 -07:00
Yury Semikhatsky f759222755 fix: do not escape html symbols when serializing strings to json (#1005) 2022-07-25 15:19:48 -07:00
Max Schmitt d87c6b24ca chore(roll): roll 1.24.0 add regex, date, url serializers (#1003) 2022-07-25 22:53:53 +02:00
Max Schmitt 41355bd059 chore: add 'gpg' package to Docker images (#1004) 2022-07-25 12:49:19 +02:00
Yury Semikhatsky e372513fa4 test: unflake playwrightDriverAlternativeImpl (#986) 2022-07-08 16:22:48 -07:00
Yury Semikhatsky b8e1e1d935 chore: remove isolated tests (#984) 2022-06-30 14:09:09 -07:00
Yury Semikhatsky a0745735d9 feat: roll driver to 1.23.1-beta, implement routeFromHar.update (#982) 2022-06-30 12:04:22 -07:00
Yury Semikhatsky b90de26d23 feat: accept PLAYWRIGHT_JAVA_SRC in Playwright.create (#980) 2022-06-29 15:27:11 -07:00
Yury Semikhatsky adfdf92eaa test: use only 127.0.0.1 for loopback (#978) 2022-06-28 17:26:33 -07:00
Yury Semikhatsky 60cb6ea7b3 chore: remove extractZip logs from tests (#979) 2022-06-28 17:26:22 -07:00
Yury Semikhatsky 44cb76d92c chore: only log har when pw:api is enabled (#977) 2022-06-28 16:23:04 -07:00
Yury Semikhatsky 9fac877892 test: unflake TestWebSocket.shouldEmitError (#976) 2022-06-28 16:09:17 -07:00
Yury Semikhatsky ec8fb9f191 fix: use same eol setting in gitattributes as upstream (#973) 2022-06-28 12:29:27 -07:00
Yury Semikhatsky 844d1665b2 chore: set dev version to 1.24.0-SNAPSHOT (#972) 2022-06-28 09:14:57 -07:00
Yury Semikhatsky 484a255ec7 feat: support ignoreCase option (#969) 2022-06-28 08:43:20 -07:00
76 changed files with 1316 additions and 254 deletions
+4 -2
View File
@@ -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
+3 -12
View File
@@ -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 }}
+3 -3
View File
@@ -11,9 +11,9 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom
| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->104.0.5112.20<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->15.4<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->100.0.2<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Chromium <!-- GEN:chromium-version -->105.0.5195.19<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->16.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->103.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
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.
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.23.0-SNAPSHOT</version>
<version>1.25.0</version>
</parent>
<artifactId>driver-bundle</artifactId>
@@ -14,7 +14,9 @@
* limitations under the License.
*/
package com.microsoft.playwright.impl;
package com.microsoft.playwright.impl.driver.jar;
import com.microsoft.playwright.impl.driver.Driver;
import java.io.IOException;
import java.net.URI;
@@ -26,6 +28,7 @@ import java.util.concurrent.TimeUnit;
public class DriverJar extends Driver {
private static final String PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD = "PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD";
private static final String SELENIUM_REMOTE_URL = "SELENIUM_REMOTE_URL";
private final Path driverTempDir;
public DriverJar() throws IOException {
@@ -57,6 +60,10 @@ public class DriverJar extends Driver {
System.out.println("Skipping browsers download because `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD` env variable is set");
return;
}
if (env.get(SELENIUM_REMOTE_URL) != null || System.getenv(SELENIUM_REMOTE_URL) != null) {
logMessage("Skipping browsers download because `SELENIUM_REMOTE_URL` env variable is set");
return;
}
Path driver = driverPath();
if (!Files.exists(driver)) {
throw new RuntimeException("Failed to find driver: " + driver);
@@ -158,7 +165,7 @@ public class DriverJar extends Driver {
}
@Override
Path driverDir() {
protected Path driverDir() {
return driverTempDir;
}
}
@@ -16,11 +16,15 @@
package com.microsoft.playwright;
import com.microsoft.playwright.impl.Driver;
import com.microsoft.playwright.impl.DriverJar;
import org.junit.jupiter.api.*;
import com.microsoft.playwright.impl.driver.Driver;
import com.microsoft.playwright.impl.driver.jar.DriverJar;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import 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,16 +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
@@ -82,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
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.23.0-SNAPSHOT</version>
<version>1.25.0</version>
</parent>
<artifactId>driver</artifactId>
@@ -14,14 +14,13 @@
* limitations under the License.
*/
package com.microsoft.playwright.impl;
package com.microsoft.playwright.impl.driver;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.ZonedDateTime;
import java.util.Map;
import static com.microsoft.playwright.impl.DriverLogging.logWithTimestamp;
import static com.microsoft.playwright.impl.driver.DriverLogging.logWithTimestamp;
/**
* This class provides access to playwright-cli. It can be either preinstalled
@@ -44,7 +43,7 @@ public abstract class Driver {
}
@Override
Path driverDir() {
protected Path driverDir() {
return driverDir;
}
}
@@ -100,12 +99,12 @@ public abstract class Driver {
}
String driverImpl =
System.getProperty("playwright.driver.impl", "com.microsoft.playwright.impl.DriverJar");
System.getProperty("playwright.driver.impl", "com.microsoft.playwright.impl.driver.jar.DriverJar");
Class<?> jarDriver = Class.forName(driverImpl);
return (Driver) jarDriver.getDeclaredConstructor().newInstance();
}
abstract Path driverDir();
protected abstract Path driverDir();
protected static void logMessage(String message) {
// This matches log format produced by the server.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.microsoft.playwright.impl;
package com.microsoft.playwright.impl.driver;
import java.time.ZoneId;
import java.time.ZonedDateTime;
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>org.example</groupId>
<artifactId>examples</artifactId>
<version>1.23.0-SNAPSHOT</version>
<version>1.25.0</version>
<name>Playwright Client Examples</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.23.0-SNAPSHOT</version>
<version>1.25.0</version>
</parent>
<artifactId>playwright</artifactId>
@@ -30,15 +30,15 @@ import java.nio.file.Path;
*
* <p> **Cookie management**
*
* <p> {@code APIRequestContext} retuned by {@link BrowserContext#request BrowserContext.request()} and {@link Page#request
* <p> {@code APIRequestContext} returned by {@link BrowserContext#request BrowserContext.request()} and {@link Page#request
* Page.request()} shares cookie storage with the corresponding {@code BrowserContext}. Each API request will have {@code Cookie}
* header populated with the values from the browser context. If the API response contains {@code Set-Cookie} header it will
* automatically update {@code BrowserContext} cookies and requests made from the page will pick them up. This means that if you
* log in using this API, your e2e test will be logged in and vice versa.
*
* <p> If you want API requests to not interfere with the browser cookies you shoud create a new {@code APIRequestContext} by calling
* {@link APIRequest#newContext APIRequest.newContext()}. Such {@code APIRequestContext} object will have its own isolated cookie
* storage.
* <p> If you want API requests to not interfere with the browser cookies you should create a new {@code APIRequestContext} by
* calling {@link APIRequest#newContext APIRequest.newContext()}. Such {@code APIRequestContext} object will have its own
* isolated cookie storage.
*/
public interface APIRequestContext {
class StorageStateOptions {
@@ -1075,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();
@@ -1094,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 explicitly close the returned context
* via {@link BrowserContext#close BrowserContext.close()} when your code is done with the {@code BrowserContext}, and before
* calling {@link Browser#close Browser.close()}. This will ensure the {@code context} is closed gracefully and any
* artifacts—like HARs and videos—are fully flushed and saved.
* <pre>{@code
* Browser browser = playwright.firefox().launch(); // Or 'chromium' or 'webkit'.
* // Create a new incognito browser context.
@@ -1101,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');
*
* // Graceful close up everything
* context.close();
* browser.close();
* }</pre>
*/
default BrowserContext newContext() {
@@ -1108,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 explicitly close the returned context
* via {@link BrowserContext#close BrowserContext.close()} when your code is done with the {@code BrowserContext}, and before
* calling {@link Browser#close Browser.close()}. This will ensure the {@code context} is closed gracefully and any
* artifacts—like HARs and videos—are fully flushed and saved.
* <pre>{@code
* Browser browser = playwright.firefox().launch(); // Or 'chromium' or 'webkit'.
* // Create a new incognito browser context.
@@ -1115,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');
*
* // Graceful close up everything
* context.close();
* browser.close();
* }</pre>
*/
BrowserContext newContext(NewContextOptions options);
@@ -67,7 +67,7 @@ public interface BrowserContext extends AutoCloseable {
* done and its response has started loading in the popup.
* <pre>{@code
* Page newPage = context.waitForPage(() -> {
* page.click("a[target=_blank]");
* page.locator("a[target=_blank]").click();
* });
* System.out.println(newPage.evaluate("location.href"));
* }</pre>
@@ -184,9 +184,13 @@ public interface BrowserContext extends AutoCloseable {
* <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 surved from the HAR file. If not specified, all requests are served from the HAR file.
* will be served from the HAR file. If not specified, all requests are served from the HAR file.
*/
public Object url;
@@ -202,9 +206,16 @@ public interface BrowserContext extends AutoCloseable {
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 surved from the HAR file. If not specified, all requests are served from the HAR file.
* 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;
@@ -212,20 +223,12 @@ public interface BrowserContext extends AutoCloseable {
}
/**
* A glob pattern, regular expression or predicate to match the request URL. Only requests with URL matching the pattern
* will be surved from the HAR file. If not specified, all requests are served from the HAR file.
* 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;
}
/**
* A glob pattern, regular expression or predicate to match the request URL. Only requests with URL matching the pattern
* will be surved from the HAR file. If not specified, all requests are served from the HAR file.
*/
public RouteFromHAROptions setUrl(Predicate<String> url) {
this.url = url;
return this;
}
}
class StorageStateOptions {
/**
@@ -401,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();
* }
* }
* }
@@ -460,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();
* }
* }
* }
@@ -530,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();
* }
* }
* }
@@ -1028,7 +1028,9 @@ public interface BrowserType {
}
}
/**
* This method attaches Playwright to an existing browser instance.
* This method attaches Playwright to an existing browser instance. When connecting to another browser launched via
* {@code BrowserType.launchServer} in Node.js, the major and minor version needs to match the client version (1.2.3 → is
* compatible with 1.2.x).
*
* @param wsEndpoint A browser websocket endpoint to connect to.
*/
@@ -1036,7 +1038,9 @@ public interface BrowserType {
return connect(wsEndpoint, null);
}
/**
* This method attaches Playwright to an existing browser instance.
* This method attaches Playwright to an existing browser instance. When connecting to another browser launched via
* {@code BrowserType.launchServer} in Node.js, the major and minor version needs to match the client version (1.2.3 → is
* compatible with 1.2.x).
*
* @param wsEndpoint A browser websocket endpoint to connect to.
*/
@@ -16,7 +16,7 @@
package com.microsoft.playwright;
import com.microsoft.playwright.impl.Driver;
import com.microsoft.playwright.impl.driver.Driver;
import java.io.IOException;
import java.nio.file.Path;
@@ -27,14 +27,14 @@ import java.nio.file.Path;
* <p> Download event is emitted once the download starts. Download path becomes available once download completes:
* <pre>{@code
* // wait for download to start
* Download download = page.waitForDownload(() -> page.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();
@@ -586,7 +586,7 @@ public interface ElementHandle extends JSHandle {
*/
public ScreenshotCaret caret;
/**
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlayed with a pink box
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box
* {@code #FF00FF} that completely covers its bounding box.
*/
public List<Locator> mask;
@@ -647,7 +647,7 @@ public interface ElementHandle extends JSHandle {
return this;
}
/**
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlayed with a pink box
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box
* {@code #FF00FF} that completely covers its bounding box.
*/
public ScreenshotOptions setMask(List<Locator> mask) {
@@ -1214,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}.
@@ -1013,7 +1013,7 @@ public interface Locator {
*/
public ScreenshotCaret caret;
/**
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlayed with a pink box
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box
* {@code #FF00FF} that completely covers its bounding box.
*/
public List<Locator> mask;
@@ -1074,7 +1074,7 @@ public interface Locator {
return this;
}
/**
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlayed with a pink box
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box
* {@code #FF00FF} that completely covers its bounding box.
*/
public ScreenshotOptions setMask(List<Locator> mask) {
@@ -1636,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.
*
@@ -1657,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.
*
@@ -2007,9 +2007,13 @@ public interface Page extends AutoCloseable {
* <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 surved from the HAR file. If not specified, all requests are served from the HAR file.
* will be served from the HAR file. If not specified, all requests are served from the HAR file.
*/
public Object url;
@@ -2025,9 +2029,16 @@ public interface Page extends AutoCloseable {
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 surved from the HAR file. If not specified, all requests are served from the HAR file.
* 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;
@@ -2035,20 +2046,12 @@ public interface Page extends AutoCloseable {
}
/**
* A glob pattern, regular expression or predicate to match the request URL. Only requests with URL matching the pattern
* will be surved from the HAR file. If not specified, all requests are served from the HAR file.
* 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;
}
/**
* A glob pattern, regular expression or predicate to match the request URL. Only requests with URL matching the pattern
* will be surved from the HAR file. If not specified, all requests are served from the HAR file.
*/
public RouteFromHAROptions setUrl(Predicate<String> url) {
this.url = url;
return this;
}
}
class ScreenshotOptions {
/**
@@ -2077,7 +2080,7 @@ public interface Page extends AutoCloseable {
*/
public Boolean fullPage;
/**
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlayed with a pink box
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box
* {@code #FF00FF} that completely covers its bounding box.
*/
public List<Locator> mask;
@@ -2159,7 +2162,7 @@ public interface Page extends AutoCloseable {
return this;
}
/**
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlayed with a pink box
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box
* {@code #FF00FF} that completely covers its bounding box.
*/
public ScreenshotOptions setMask(List<Locator> mask) {
@@ -3578,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
@@ -40,7 +40,7 @@ public interface Response {
*/
Frame frame();
/**
* Indicates whether this Response was fullfilled by a Service Worker's Fetch Handler (i.e. via <a
* Indicates whether this Response was fulfilled by a Service Worker's Fetch Handler (i.e. via <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent/respondWith">FetchEvent.respondWith</a>).
*/
boolean fromServiceWorker();
@@ -280,7 +280,7 @@ public interface Route {
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 previos ones. In the example below, request will be handled by the
* 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
@@ -341,7 +341,7 @@ public interface Route {
}
/**
* 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 previos ones. In the example below, request will be handled by the
* 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
@@ -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")));
@@ -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");
* }
* }
@@ -498,9 +498,29 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).containsText("substring");
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> Elements from a **subset** of this list contain text from the expected array, respectively.</li>
* <li> The matching subset of elements has the same order as the expected array.</li>
* <li> Each text value from the expected array is matched by some element from the list.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <pre>{@code
* assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
* // ✓ Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
*
* // ✖ No item contains this text
* assertThat(page.locator("ul > li")).containsText(new String[] {"Some 33"});
*
* // ✖ Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).containsText(new String[] {"Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -515,9 +535,29 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).containsText("substring");
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> Elements from a **subset** of this list contain text from the expected array, respectively.</li>
* <li> The matching subset of elements has the same order as the expected array.</li>
* <li> Each text value from the expected array is matched by some element from the list.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <pre>{@code
* assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
* // ✓ Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
*
* // ✖ No item contains this text
* assertThat(page.locator("ul > li")).containsText(new String[] {"Some 33"});
*
* // ✖ Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).containsText(new String[] {"Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -530,9 +570,29 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).containsText("substring");
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> Elements from a **subset** of this list contain text from the expected array, respectively.</li>
* <li> The matching subset of elements has the same order as the expected array.</li>
* <li> Each text value from the expected array is matched by some element from the list.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <pre>{@code
* assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
* // ✓ Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
*
* // ✖ No item contains this text
* assertThat(page.locator("ul > li")).containsText(new String[] {"Some 33"});
*
* // ✖ Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).containsText(new String[] {"Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -547,9 +607,29 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).containsText("substring");
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> Elements from a **subset** of this list contain text from the expected array, respectively.</li>
* <li> The matching subset of elements has the same order as the expected array.</li>
* <li> Each text value from the expected array is matched by some element from the list.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <pre>{@code
* assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
* // ✓ Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
*
* // ✖ No item contains this text
* assertThat(page.locator("ul > li")).containsText(new String[] {"Some 33"});
*
* // ✖ Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).containsText(new String[] {"Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -562,9 +642,29 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).containsText("substring");
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> Elements from a **subset** of this list contain text from the expected array, respectively.</li>
* <li> The matching subset of elements has the same order as the expected array.</li>
* <li> Each text value from the expected array is matched by some element from the list.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <pre>{@code
* assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
* // ✓ Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
*
* // ✖ No item contains this text
* assertThat(page.locator("ul > li")).containsText(new String[] {"Some 33"});
*
* // ✖ Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).containsText(new String[] {"Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -579,9 +679,29 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).containsText("substring");
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> Elements from a **subset** of this list contain text from the expected array, respectively.</li>
* <li> The matching subset of elements has the same order as the expected array.</li>
* <li> Each text value from the expected array is matched by some element from the list.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <pre>{@code
* assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
* // ✓ Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
*
* // ✖ No item contains this text
* assertThat(page.locator("ul > li")).containsText(new String[] {"Some 33"});
*
* // ✖ Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).containsText(new String[] {"Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -594,9 +714,29 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).containsText("substring");
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> Elements from a **subset** of this list contain text from the expected array, respectively.</li>
* <li> The matching subset of elements has the same order as the expected array.</li>
* <li> Each text value from the expected array is matched by some element from the list.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <pre>{@code
* assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
* // ✓ Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
*
* // ✖ No item contains this text
* assertThat(page.locator("ul > li")).containsText(new String[] {"Some 33"});
*
* // ✖ Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).containsText(new String[] {"Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -611,9 +751,29 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).containsText("substring");
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> Elements from a **subset** of this list contain text from the expected array, respectively.</li>
* <li> The matching subset of elements has the same order as the expected array.</li>
* <li> Each text value from the expected array is matched by some element from the list.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <pre>{@code
* assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
* // ✓ Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
*
* // ✖ No item contains this text
* assertThat(page.locator("ul > li")).containsText(new String[] {"Some 33"});
*
* // ✖ Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).containsText(new String[] {"Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -664,9 +824,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:
@@ -680,9 +842,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:
@@ -694,9 +858,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:
@@ -710,9 +876,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:
@@ -724,9 +892,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:
@@ -740,9 +910,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:
@@ -754,9 +926,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:
@@ -770,9 +944,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:
@@ -918,9 +1094,28 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> The number of elements equals the number of expected values in the array.</li>
* <li> Elements from the list have text matching expected array values, one by one, in order.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <pre>{@code
* assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* // ✓ Has the right items in the right order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 3", "Text 2", "Text 1"});
*
* // ✖ Last item does not match
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text"});
*
* // ✖ Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -935,9 +1130,28 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> The number of elements equals the number of expected values in the array.</li>
* <li> Elements from the list have text matching expected array values, one by one, in order.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <pre>{@code
* assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* // ✓ Has the right items in the right order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 3", "Text 2", "Text 1"});
*
* // ✖ Last item does not match
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text"});
*
* // ✖ Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -950,9 +1164,28 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> The number of elements equals the number of expected values in the array.</li>
* <li> Elements from the list have text matching expected array values, one by one, in order.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <pre>{@code
* assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* // ✓ Has the right items in the right order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 3", "Text 2", "Text 1"});
*
* // ✖ Last item does not match
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text"});
*
* // ✖ Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -967,9 +1200,28 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> The number of elements equals the number of expected values in the array.</li>
* <li> Elements from the list have text matching expected array values, one by one, in order.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <pre>{@code
* assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* // ✓ Has the right items in the right order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 3", "Text 2", "Text 1"});
*
* // ✖ Last item does not match
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text"});
*
* // ✖ Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -982,9 +1234,28 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> The number of elements equals the number of expected values in the array.</li>
* <li> Elements from the list have text matching expected array values, one by one, in order.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <pre>{@code
* assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* // ✓ Has the right items in the right order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 3", "Text 2", "Text 1"});
*
* // ✖ Last item does not match
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text"});
*
* // ✖ Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -999,9 +1270,28 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> The number of elements equals the number of expected values in the array.</li>
* <li> Elements from the list have text matching expected array values, one by one, in order.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <pre>{@code
* assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* // ✓ Has the right items in the right order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 3", "Text 2", "Text 1"});
*
* // ✖ Last item does not match
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text"});
*
* // ✖ Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -1014,9 +1304,28 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> The number of elements equals the number of expected values in the array.</li>
* <li> Elements from the list have text matching expected array values, one by one, in order.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <pre>{@code
* assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* // ✓ Has the right items in the right order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 3", "Text 2", "Text 1"});
*
* // ✖ Last item does not match
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text"});
*
* // ✖ Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -1031,9 +1340,28 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> The number of elements equals the number of expected values in the array.</li>
* <li> Elements from the list have text matching expected array values, one by one, in order.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <pre>{@code
* assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* // ✓ Has the right items in the right order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 3", "Text 2", "Text 1"});
*
* // ✖ Last item does not match
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text"});
*
* // ✖ Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -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);
}
@@ -20,6 +20,7 @@ import com.microsoft.playwright.APIResponse;
import com.microsoft.playwright.Locator;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.impl.APIResponseAssertionsImpl;
import com.microsoft.playwright.impl.AssertionsTimeout;
import com.microsoft.playwright.impl.LocatorAssertionsImpl;
import com.microsoft.playwright.impl.PageAssertionsImpl;
@@ -37,7 +38,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");
* }
* }
@@ -86,5 +87,17 @@ public interface PlaywrightAssertions {
return new PageAssertionsImpl(page);
}
/**
* Changes default timeout for Playwright assertions from 5 seconds to the specified value.
* <pre>{@code
* PlaywrightAssertions.setDefaultAssertionTimeout(30_000);
* }</pre>
*
* @param timeout Timeout in milliseconds.
*/
static void setDefaultAssertionTimeout(double milliseconds) {
AssertionsTimeout.setDefaultTimeout(milliseconds);
}
}
@@ -21,6 +21,7 @@ import com.microsoft.playwright.assertions.APIResponseAssertions;
import org.opentest4j.AssertionFailedError;
import java.util.List;
import java.util.regex.Pattern;
public class APIResponseAssertionsImpl implements APIResponseAssertions {
private final APIResponse actual;
@@ -54,6 +55,20 @@ public class APIResponseAssertionsImpl implements APIResponseAssertions {
if (!log.isEmpty()) {
log = "\nCall log:\n" + log;
}
throw new AssertionFailedError(message + log);
String contentType = actual.headers().get("content-type");
boolean isTextEncoding = contentType == null ? false : isTextualMimeType(contentType);
String responseText = "";
if (isTextEncoding) {
String text = actual.text();
if (text != null) {
responseText = "\nResponse text:\n" + (text.length() > 1000 ? text.substring(0, 1000) : text);
}
}
throw new AssertionFailedError(message + log + responseText);
}
static boolean isTextualMimeType(String mimeType) {
return Pattern.matches("^(text/.*?|application/(json|(x-)?javascript|xml.*?|ecmascript|graphql|x-www-form-urlencoded)|image/svg(\\+xml)?|application/.*?(\\+json|\\+xml))(;\\s*charset=.*)?$", mimeType);
}
}
@@ -52,7 +52,7 @@ class AssertionsBase {
void expectImpl(String expression, FrameExpectOptions expectOptions, Object expected, String message) {
if (expectOptions.timeout == null) {
expectOptions.timeout = 5_000.0;
expectOptions.timeout = AssertionsTimeout.defaultTimeout;
}
if (expectOptions.isNot) {
message = message.replace("expected to", "expected not to");
@@ -0,0 +1,30 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.microsoft.playwright.PlaywrightException;
public class AssertionsTimeout {
static double defaultTimeout = 5_000;
public static void setDefaultTimeout(double ms) {
if (ms < 0) {
throw new PlaywrightException("Timeout cannot be negative");
}
defaultTimeout = ms;
}
}
@@ -27,11 +27,13 @@ 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;
@@ -51,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,
@@ -71,7 +83,13 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
this.tracing = connection.getExistingObject(initializer.getAsJsonObject("tracing").get("guid").getAsString());
tracing.isRemote = browser != null && browser.isRemote;
this.request = connection.getExistingObject(initializer.getAsJsonObject("APIRequestContext").get("guid").getAsString());
this.request = connection.getExistingObject(initializer.getAsJsonObject("requestContext").get("guid").getAsString());
}
void setRecordHar(Path path, HarContentPolicy policy) {
if (path != null) {
harRecorders.put("", new HarRecorder(path, policy));
}
}
void setBaseUrl(String spec) {
@@ -178,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();
}
@@ -351,6 +385,10 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
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());
@@ -368,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", () -> {
@@ -30,6 +30,7 @@ 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;
@@ -137,24 +138,22 @@ class BrowserImpl extends ChannelOwner implements Browser {
}
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) {
recordHar.addProperty("content", options.recordHarContent.toString().toLowerCase());
harContentPolicy = options.recordHarContent;
} else if (options.recordHarOmitContent != null && options.recordHarOmitContent) {
recordHar.addProperty("content", HarContentPolicy.OMIT.toString().toLowerCase());
harContentPolicy = HarContentPolicy.OMIT;
}
if (harContentPolicy != null) {
recordHar.addProperty("content", harContentPolicy.name().toLowerCase());
}
if (options.recordHarMode != null) {
recordHar.addProperty("mode", options.recordHarMode.toString().toLowerCase());
}
if (options.recordHarUrlFilter instanceof String) {
recordHar.addProperty("urlGlob", (String) options.recordHarUrlFilter);
} else if (options.recordHarUrlFilter instanceof Pattern) {
Pattern pattern = (Pattern) options.recordHarUrlFilter;
recordHar.addProperty("urlRegexSource", pattern.pattern());
recordHar.addProperty("urlRegexFlags", toJsRegexFlags(pattern));
recordHar.addProperty("mode", options.recordHarMode.name().toLowerCase());
}
addHarUrlFilter(recordHar, options.recordHarUrlFilter);
options.recordHarPath = null;
options.recordHarMode = null;
options.recordHarOmitContent = null;
@@ -210,7 +209,7 @@ class BrowserImpl extends ChannelOwner implements Browser {
if (options.baseURL != null) {
context.setBaseUrl(options.baseURL);
}
context.recordHarPath = recordHarPath;
context.setRecordHar(recordHarPath, harContentPolicy);
contexts.add(context);
return context;
}
@@ -231,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;
@@ -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 {
@@ -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,7 +230,7 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
if (options.baseURL != null) {
context.setBaseUrl(options.baseURL);
}
context.recordHarPath = options.recordHarPath;
context.setRecordHar(recordHarPath, harContentPolicy);
return context;
}
@@ -26,7 +26,7 @@ import java.util.function.Supplier;
class ChannelOwner extends LoggingSupport {
final Connection connection;
private final ChannelOwner parent;
private ChannelOwner parent;
private final Map<String, ChannelOwner> objects = new HashMap<>();
final String type;
@@ -68,6 +68,12 @@ class ChannelOwner extends LoggingSupport {
objects.clear();
}
void adopt(ChannelOwner child) {
child.parent.objects.remove(child.guid);
objects.put(child.guid, child);
child.parent = this;
}
<T> T withWaitLogging(String apiName, Supplier<T> code) {
return new WaitForEventLogger<>(this, apiName, code).get();
}
@@ -108,4 +114,10 @@ class ChannelOwner extends LoggingSupport {
void handleEvent(String event, JsonObject parameters) {
}
JsonObject toProtocolRef() {
JsonObject json = new JsonObject();
json.addProperty("guid", guid);
return json;
}
}
@@ -65,6 +65,7 @@ public class Connection {
isLogging = (debug != null) && debug.contains("pw:channel");
}
LocalUtils localUtils;
final Map<String, String> env;
class Root extends ChannelOwner {
Root(Connection connection) {
@@ -79,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() {
@@ -199,18 +201,24 @@ public class Connection {
createRemoteObject(message.guid, message.params);
return;
}
if (message.method.equals("__dispose__")) {
ChannelOwner object = objects.get(message.guid);
if (object == null) {
throw new PlaywrightException("Cannot find object to dispose: " + message.guid);
}
object.disconnect();
return;
}
ChannelOwner object = objects.get(message.guid);
if (object == null) {
throw new PlaywrightException("Cannot find object to call " + message.method + ": " + message.guid);
}
if (message.method.equals("__adopt__")) {
String childGuid = message.params.get("guid").getAsString();
ChannelOwner child = objects.get(childGuid);
if (child == null) {
throw new PlaywrightException("Unknown new child: " + childGuid);
}
object.adopt(child);
return;
}
if (message.method.equals("__dispose__")) {
object.disconnect();
return;
}
object.handleEvent(message.method, message.params);
}
@@ -26,6 +26,7 @@ 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;
@@ -66,7 +67,9 @@ public class HARRouter {
String action = response.get("action").getAsString();
if ("redirect".equals(action)) {
String redirectURL = response.get("redirectURL").getAsString();
logApi("HAR: " + route.request().url() + " redirected to " + redirectURL);
if (isApiLoggingEnabled()) {
logApi("HAR: " + route.request().url() + " redirected to " + redirectURL);
}
((RouteImpl) route).redirectNavigationRequest(redirectURL);
return;
}
@@ -83,7 +86,9 @@ public class HARRouter {
}
if ("error".equals(action)) {
logApi("HAR: " + response.get("message").getAsString());
if (isApiLoggingEnabled()) {
logApi("HAR: " + response.get("message").getAsString());
}
// Report the error, but fall through to the default handler.
}
@@ -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);
@@ -327,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,6 +60,10 @@ class LoggingSupport {
System.err.println(timestamp + " " + message);
}
static boolean isApiLoggingEnabled() {
return isEnabled;
}
static void logApi(String message) {
// This matches log format produced by the server.
logWithTimestamp("pw:api " + message);
@@ -974,6 +974,10 @@ public class PageImpl extends ChannelOwner implements Page {
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());
@@ -21,6 +21,7 @@ import com.microsoft.playwright.APIRequest;
import com.microsoft.playwright.Playwright;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.Selectors;
import com.microsoft.playwright.impl.driver.Driver;
import java.io.IOException;
import java.nio.file.Path;
@@ -43,7 +44,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;
}
@@ -39,9 +39,6 @@ class Router {
}
boolean handle(RouteImpl route) {
if (times != null && times <= 0) {
return false;
}
if (!matcher.test(route.request().url())) {
return false;
}
@@ -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>())
@@ -141,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);
@@ -204,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":
@@ -288,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;
}
@@ -299,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()) {
@@ -349,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);
@@ -176,7 +176,7 @@ 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 {
@@ -307,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;
}
}
@@ -72,6 +72,9 @@ class VideoImpl implements Video {
@Override
public void saveAs(Path path) {
page.withLogging("Video.saveAs", () -> {
if (!page.isClosed()) {
throw new PlaywrightException("Page is not yet closed. Close the page prior to calling saveAs");
}
try {
waitForArtifact().saveAs(path);
} catch (PlaywrightException e) {
@@ -30,10 +30,4 @@ class WritableStream extends ChannelOwner {
}
};
}
JsonObject toProtocol() {
JsonObject json = new JsonObject();
json.addProperty("guid", guid);
return json;
}
}
@@ -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 {
/**
@@ -195,6 +195,7 @@ public class Server implements HttpHandler {
String resourcePath = "resources" + path;
InputStream resource = getClass().getClassLoader().getResourceAsStream(resourcePath);
if (resource == null) {
exchange.getResponseHeaders().add("Content-Type", "text/plain");
exchange.sendResponseHeaders(404, 0);
try (Writer writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("File not found: " + resourcePath);
@@ -19,8 +19,11 @@ package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import org.opentest4j.AssertionFailedError;
import java.io.OutputStreamWriter;
import java.io.Writer;
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.*;
public class TestAPIResponseAssertions extends TestBase {
@Test
@@ -38,14 +41,62 @@ public class TestAPIResponseAssertions extends TestBase {
@Test
void fail() {
APIResponse res = page.request().get(server.PREFIX + "/unknown");
boolean didThrow = false;
try {
assertThat(res).isOK();
} catch (AssertionFailedError e) {
didThrow = true;
assertTrue(e.getMessage().contains("→ GET " + server.PREFIX + "/unknown"), "Actual error: " + e.toString());
assertTrue(e.getMessage().contains("← 404 Not Found"), "Actual error: " + e.toString());
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> assertThat(res).isOK());
assertTrue(e.getMessage().contains("→ GET " + server.PREFIX + "/unknown"), "Actual error: " + e.toString());
assertTrue(e.getMessage().contains("← 404 Not Found"), "Actual error: " + e.toString());
}
@Test
void shouldPrintResponseTextIfIdOkFails() {
APIResponse res = page.request().get(server.PREFIX + "/unknown");
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> assertThat(res).isOK());
assertTrue(e.getMessage().contains("File not found"), "Actual error: " + e.toString());
}
@Test
void shouldOnlyPrintResponseWithTextContentTypeIfIsOkFails() {
{
server.setRoute("/text-content-type", exchange -> {
exchange.getResponseHeaders().set("Content-type", "text/plain");
exchange.sendResponseHeaders(404, 0);
try (Writer writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("Text error");
}
});
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> assertThat(page.request().get(server.PREFIX + "/text-content-type")).isOK());
assertTrue(e.getMessage().contains("Text error"), "Actual error: " + e);
}
{
server.setRoute("/svg-xml-content-type", exchange -> {
exchange.getResponseHeaders().set("Content-type", "image/svg+xml");
exchange.sendResponseHeaders(404, 0);
try (Writer writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("Json error");
}
});
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> assertThat(page.request().get(server.PREFIX + "/svg-xml-content-type")).isOK());
assertTrue(e.getMessage().contains("Json error"), "Actual error: " + e);
}
{
server.setRoute("/no-content-type", exchange -> {
exchange.sendResponseHeaders(404, 0);
try (Writer writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("No content type error");
}
});
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> assertThat(page.request().get(server.PREFIX + "/no-content-type")).isOK());
assertFalse(e.getMessage().contains("No content type error"), "Actual error: " + e);
}
{
server.setRoute("/image-content-type", exchange -> {
exchange.getResponseHeaders().set("Content-type", "image/bmp");
exchange.sendResponseHeaders(404, 0);
try (Writer writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("Image type error");
}
});
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> assertThat(page.request().get(server.PREFIX + "/image-content-type")).isOK());
assertFalse(e.getMessage().contains("Image type error"), "Actual error: " + e);
}
assertTrue(didThrow);
}
}
@@ -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);
}
@@ -16,6 +16,7 @@
package com.microsoft.playwright;
import com.microsoft.playwright.options.HarContentPolicy;
import com.microsoft.playwright.options.HarMode;
import com.microsoft.playwright.options.HarNotFound;
import org.junit.jupiter.api.Test;
@@ -26,6 +27,7 @@ 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;
@@ -35,6 +37,7 @@ 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 {
@@ -264,7 +267,29 @@ public class TestBrowserContextHar extends TestBase {
}
try (BrowserContext context2 = browser.newContext()) {
context2.routeFromHAR(harPath, new BrowserContext.RouteFromHAROptions().setNotFound(HarNotFound.ABORT));
Page page2 = context.newPage();
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)");
@@ -286,7 +311,7 @@ public class TestBrowserContextHar extends TestBase {
try (BrowserContext context2 = browser.newContext()) {
context2.routeFromHAR(harDir.resolve("har.har"));
Page page2 = context.newPage();
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)");
@@ -326,8 +351,7 @@ public class TestBrowserContextHar extends TestBase {
assertEquals("3", page2.evaluate(fetchFunction, "3"));
assertEquals("3", page2.evaluate(fetchFunction, "3"));
try {
Object result = page2.evaluate(fetchFunction, "4");
System.out.println(result);
page2.evaluate(fetchFunction, "4");
fail("did not throw");
} catch (PlaywrightException e) {
}
@@ -380,4 +404,54 @@ public class TestBrowserContextHar extends TestBase {
}
}
@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)");
}
}
}
@@ -16,7 +16,7 @@
package com.microsoft.playwright;
import com.microsoft.playwright.impl.Driver;
import com.microsoft.playwright.impl.driver.Driver;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
@@ -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));
}
}
@@ -17,6 +17,7 @@
package com.microsoft.playwright;
import com.microsoft.playwright.assertions.LocatorAssertions;
import com.microsoft.playwright.assertions.PlaywrightAssertions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.opentest4j.AssertionFailedError;
@@ -72,6 +73,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 +102,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 +128,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 +162,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
@@ -948,4 +981,35 @@ public class TestLocatorAssertions extends TestBase {
page.locator("#searchResultTableDiv .x-grid3-row").count();
assertThat(page.locator("#searchResultTableDiv .x-grid3-row")).hasCount(0);
}
@Test
void defaultTimeoutHasTextFail() {
page.setContent("<div></div>");
Locator locator = page.locator("div");
PlaywrightAssertions.setDefaultAssertionTimeout(1000);
AssertionFailedError exception = assertThrows(AssertionFailedError.class, () -> assertThat(locator).hasText("foo"));
assertTrue(exception.getMessage().contains("Locator.expect with timeout 1000ms"), exception.getMessage());
// Restore default.
PlaywrightAssertions.setDefaultAssertionTimeout(5_000);
}
@Test
void defaultTimeoutHasTextPass() {
page.setContent("<div>foo</div>");
Locator locator = page.locator("div");
PlaywrightAssertions.setDefaultAssertionTimeout(1000);
assertThat(locator).hasText("foo");
// Restore default.
PlaywrightAssertions.setDefaultAssertionTimeout(5_000);
}
@Test
void defaultTimeoutZeroHasTextPass() {
page.setContent("<div>foo</div>");
Locator locator = page.locator("div");
PlaywrightAssertions.setDefaultAssertionTimeout(0);
assertThat(locator).hasText("foo");
// Restore default.
PlaywrightAssertions.setDefaultAssertionTimeout(5_000);
}
}
@@ -19,8 +19,9 @@ package com.microsoft.playwright;
import com.microsoft.playwright.options.WaitForSelectorState;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.regex.Pattern;
import static org.junit.jupiter.api.Assertions.*;
public class TestLocatorMisc extends TestBase{
@Test
@@ -49,4 +50,22 @@ public class TestLocatorMisc extends TestBase{
page.evalOnSelector("div", "div => setTimeout(() => div.innerHTML = '', 500)");
locator.waitFor(new Locator.WaitForOptions().setState(WaitForSelectorState.HIDDEN));
}
@Test
void locatorsHasDoesNotEncodeUnicode() {
page.navigate(server.EMPTY_PAGE);
Locator[] locators = new Locator[]{
page.locator("button", new Page.LocatorOptions().setHasText("Драматург")),
page.locator("button", new Page.LocatorOptions().setHasText(Pattern.compile("Драматург"))),
page.locator("button", new Page.LocatorOptions().setHas(page.locator("text=Драматург")))
};
for (Locator locator: locators) {
try {
locator.click(new Locator.ClickOptions().setTimeout(100));
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Драматург"), e.getMessage());
}
}
}
}
@@ -39,7 +39,7 @@ public class TestPageBasic extends TestBase {
newPage.evaluate("() => new Promise(r => {})");
fail("evaluate should throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Target closed"), e.getMessage());
assertTrue(e.getMessage().contains("Target page, context or browser has been closed"), e.getMessage());
}
}
@@ -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
@@ -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>");
@@ -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" +
@@ -127,4 +127,17 @@ public class TestScreencast extends TestBase {
assertTrue(Files.exists(files.get(0)));
assertTrue(Files.size(files.get(0)) > 0);
}
@Test
void shouldErrorIfPageNotClosedBeforeSaveAs(@TempDir Path tmpDir) {
try (Page page = browser.newPage(new Browser.NewPageOptions().setRecordVideoDir(tmpDir))) {
page.navigate(server.PREFIX + "/grid.html");
Path outPath = tmpDir.resolve("some-video.webm");
Video video = page.video();
PlaywrightException exception = assertThrows(PlaywrightException.class, () -> video.saveAs(outPath));
assertTrue(
exception.getMessage().contains("Page is not yet closed. Close the page prior to calling saveAs"),
exception.getMessage());
}
}
}
@@ -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" +
@@ -108,8 +108,8 @@ public class TestWheel extends TestBase {
"shiftKey", false,
"altKey", false,
"metaKey", false);
expectEvent(expected);
page.waitForFunction("window.scrollX === 100");
expectEvent(expected);
}
@Test
@@ -121,6 +121,8 @@ public class TestWheel extends TestBase {
" document.querySelector('div').addEventListener('wheel', e => e.preventDefault());\n" +
" }");
page.mouse().wheel(0, 100);
// give the page a chance to scroll
page.waitForFunction("!!window['lastEvent']");
Map<String, Object> expected = mapOf(
"deltaX", 0,
"deltaY", 100,
@@ -132,8 +134,6 @@ public class TestWheel extends TestBase {
"altKey", false,
"metaKey", false);
expectEvent(expected);
// give the page a chacne to scroll
page.waitForTimeout(100);
// ensure that it did not.
assertEquals(0, page.evaluate("window.scrollY"));
}
@@ -109,7 +109,6 @@ class Utils {
Files.createDirectories(toDir);
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipPath.toFile()))) {
for (ZipEntry zipEntry = zis.getNextEntry(); zipEntry != null; zipEntry = zis.getNextEntry()) {
System.out.println(zipEntry.getName());
Path toPath = toDir.resolve(zipEntry.getName());
if (zipEntry.isDirectory()) {
Files.createDirectories(toPath);
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.23.0-SNAPSHOT</version>
<version>1.25.0</version>
<packaging>pom</packaging>
<name>Playwright Parent Project</name>
<description>Java library to automate Chromium, Firefox and WebKit with a single API.
+1 -1
View File
@@ -1 +1 @@
1.23.0
1.25.0
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>com.microsoft.playwright</groupId>
<artifactId>api-generator</artifactId>
<version>1.23.0-SNAPSHOT</version>
<version>1.25.0</version>
<name>Playwright - API Generator</name>
<description>
This is an internal module used to generate Java API from the upstream Playwright
@@ -675,6 +675,14 @@ class Method extends Element {
}
return;
}
if ("PlaywrightAssertions.setDefaultAssertionTimeout".equals(jsonPath)) {
writeJavadoc(params, output, offset);
output.add(offset + "static void setDefaultAssertionTimeout(double milliseconds) {");
output.add(offset + " AssertionsTimeout.setDefaultTimeout(milliseconds);");
output.add(offset + "}");
output.add("");
return;
}
int numOverloads = 1;
for (int i = 0; i < params.size(); i++) {
if (params.get(i).type.isTypeUnion()) {
@@ -952,6 +960,7 @@ class Interface extends TypeDefinition {
output.add("import com.microsoft.playwright.Locator;");
output.add("import com.microsoft.playwright.Page;");
output.add("import com.microsoft.playwright.impl.APIResponseAssertionsImpl;");
output.add("import com.microsoft.playwright.impl.AssertionsTimeout;");
output.add("import com.microsoft.playwright.impl.LocatorAssertionsImpl;");
output.add("import com.microsoft.playwright.impl.PageAssertionsImpl;");
}
+1 -1
View File
@@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.microsoft.playwright</groupId>
<artifactId>test-cli-version</artifactId>
<version>1.23.0-SNAPSHOT</version>
<version>1.25.0</version>
<name>Test Playwright Command Line Version</name>
<properties>
<compiler.version>1.8</compiler.version>
+1 -1
View File
@@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.microsoft.playwright</groupId>
<artifactId>test-local-installation</artifactId>
<version>1.23.0-SNAPSHOT</version>
<version>1.25.0</version>
<name>Test local installation</name>
<description>Runs Playwright test suite (copied from playwright module) against locally cached Playwright</description>
<properties>
+1 -1
View File
@@ -9,7 +9,7 @@
</parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>test-spring-boot-starter</artifactId>
<version>1.23.0-SNAPSHOT</version>
<version>1.25.0</version>
<name>Test Playwright With Spring Boot</name>
<properties>
<spring.version>2.4.3</spring.version>
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>com.microsoft.playwright</groupId>
<artifactId>update-version</artifactId>
<version>1.23.0-SNAPSHOT</version>
<version>1.25.0</version>
<name>Playwright - Update Version in Documentation</name>
<description>
This is an internal module used to update versions in the documentation based on
+3 -1
View File
@@ -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