Compare commits

..

1 Commits

Author SHA1 Message Date
Yury Semikhatsky
f89f6422c4
chore: set version to 1.54.0 (#1823) 2025-07-21 11:43:08 -07:00
48 changed files with 313 additions and 489 deletions

View File

@ -13,7 +13,7 @@ jobs:
environment: Docker
if: github.repository == 'microsoft/playwright-java'
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v4
- name: Azure login
uses: azure/login@v2
with:
@ -26,5 +26,5 @@ jobs:
uses: docker/setup-qemu-action@v3
with:
platforms: arm64
- uses: actions/checkout@v5
- uses: actions/checkout@v4
- run: ./utils/docker/publish_docker.sh stable

View File

@ -20,9 +20,9 @@ jobs:
browser: [chromium, firefox, webkit]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v4
- name: Set up JDK 1.8
uses: actions/setup-java@v5
uses: actions/setup-java@v4
with:
distribution: zulu
java-version: 8
@ -65,13 +65,13 @@ jobs:
browser-channel: msedge
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v4
- name: Install Media Pack
if: matrix.os == 'windows-latest'
shell: powershell
run: Install-WindowsFeature Server-Media-Foundation
- name: Set up JDK 1.8
uses: actions/setup-java@v5
uses: actions/setup-java@v4
with:
distribution: zulu
java-version: 8
@ -100,9 +100,9 @@ jobs:
browser: [chromium, firefox, webkit]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v4
- name: Set up JDK 21
uses: actions/setup-java@v5
uses: actions/setup-java@v4
with:
distribution: adopt
java-version: 21

View File

@ -13,7 +13,7 @@ jobs:
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v4
- name: Cache Maven packages
uses: actions/cache@v4
with:

View File

@ -21,32 +21,18 @@ jobs:
name: Test
timeout-minutes: 120
runs-on: ${{ matrix.runs-on }}
env:
PW_MAX_RETRIES: 3
strategy:
fail-fast: false
matrix:
flavor: [jammy, noble]
runs-on: [ubuntu-24.04, ubuntu-24.04-arm]
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v4
- name: Build Docker image
run: |
ARCH="${{ matrix.runs-on == 'ubuntu-24.04-arm' && 'arm64' || 'amd64' }}"
bash utils/docker/build.sh --$ARCH ${{ matrix.flavor }} playwright-java:localbuild-${{ matrix.flavor }}
- name: Start container
- name: Test
run: |
CONTAINER_ID=$(docker run --rm -e CI -e PW_MAX_RETRIES --ipc=host -v "$(pwd)":/root/playwright --name playwright-docker-test -d -t playwright-java:localbuild-${{ matrix.flavor }} /bin/bash)
echo "CONTAINER_ID=$CONTAINER_ID" >> $GITHUB_ENV
- name: Run test in container
run: |
docker exec "$CONTAINER_ID" /root/playwright/tools/test-local-installation/create_project_and_run_tests.sh
- name: Test ClassLoader
run: |
docker exec "${CONTAINER_ID}" /root/playwright/tools/test-spring-boot-starter/package_and_run_async_test.sh
- name: Stop container
run: |
docker stop "$CONTAINER_ID"
CONTAINER_ID="$(docker run --rm -e CI --ipc=host -v $(pwd):/root/playwright --name playwright-docker-test -d -t playwright-java:localbuild-${{ matrix.flavor }} /bin/bash)"
docker exec "${CONTAINER_ID}" /root/playwright/tools/test-local-installation/create_project_and_run_tests.sh

View File

@ -19,7 +19,7 @@ jobs:
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v4
- name: Download drivers
run: scripts/download_driver.sh
- name: Regenerate APIs

View File

@ -32,9 +32,9 @@ scripts/download_driver.sh
mvn compile
mvn test
# Executing a single test
BROWSER=chromium mvn test -Dtest=TestPageNetworkSizes#shouldHaveTheCorrectResponseBodySize
BROWSER=chromium mvn test --projects=playwright -Dtest=TestPageNetworkSizes#shouldHaveTheCorrectResponseBodySize
# Executing a single test class
BROWSER=chromium mvn test -Dtest=TestPageNetworkSizes
BROWSER=chromium mvn test --projects=playwright -Dtest=TestPageNetworkSizes
```
### Generating API

View File

@ -10,9 +10,9 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom
| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->140.0.7339.16<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Chromium <!-- GEN:chromium-version -->139.0.7258.5<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->26.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->141.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Firefox <!-- GEN:firefox-version -->140.0.2<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
## Documentation

View File

@ -6,7 +6,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.50.0-SNAPSHOT</version>
<version>1.54.0</version>
</parent>
<artifactId>driver-bundle</artifactId>

View File

@ -114,7 +114,7 @@ public class DriverJar extends Driver {
}
public static URI getDriverResourceURI() throws URISyntaxException {
ClassLoader classloader = DriverJar.class.getClassLoader();
ClassLoader classloader = Thread.currentThread().getContextClassLoader();
return classloader.getResource("driver/" + platformDir()).toURI();
}

View File

@ -6,7 +6,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.50.0-SNAPSHOT</version>
<version>1.54.0</version>
</parent>
<artifactId>driver</artifactId>

View File

@ -6,11 +6,11 @@
<groupId>org.example</groupId>
<artifactId>examples</artifactId>
<version>1.50.0-SNAPSHOT</version>
<version>1.54.0</version>
<name>Playwright Client Examples</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<playwright.version>1.55.0</playwright.version>
<playwright.version>1.54.0</playwright.version>
</properties>
<dependencies>
<dependency>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.50.0-SNAPSHOT</version>
<version>1.54.0</version>
</parent>
<artifactId>playwright</artifactId>

View File

@ -51,10 +51,6 @@ public interface APIRequest {
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
*
* <p> Client certificate authentication is only active when at least one client certificate is provided. If you want to reject
* all client certificates sent by the server, you need to provide a client certificate with an {@code origin} that does
* not match any of the domains you plan to visit.
*
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
* replacing {@code localhost} with {@code local.playwright}.
*/
@ -138,10 +134,6 @@ public interface APIRequest {
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
*
* <p> Client certificate authentication is only active when at least one client certificate is provided. If you want to reject
* all client certificates sent by the server, you need to provide a client certificate with an {@code origin} that does
* not match any of the domains you plan to visit.
*
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
* replacing {@code localhost} with {@code local.playwright}.
*/

View File

@ -106,10 +106,6 @@ public interface Browser extends AutoCloseable {
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
*
* <p> Client certificate authentication is only active when at least one client certificate is provided. If you want to reject
* all client certificates sent by the server, you need to provide a client certificate with an {@code origin} that does
* not match any of the domains you plan to visit.
*
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
* replacing {@code localhost} with {@code local.playwright}.
*/
@ -327,10 +323,6 @@ public interface Browser extends AutoCloseable {
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
*
* <p> Client certificate authentication is only active when at least one client certificate is provided. If you want to reject
* all client certificates sent by the server, you need to provide a client certificate with an {@code origin} that does
* not match any of the domains you plan to visit.
*
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
* replacing {@code localhost} with {@code local.playwright}.
*/
@ -682,10 +674,6 @@ public interface Browser extends AutoCloseable {
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
*
* <p> Client certificate authentication is only active when at least one client certificate is provided. If you want to reject
* all client certificates sent by the server, you need to provide a client certificate with an {@code origin} that does
* not match any of the domains you plan to visit.
*
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
* replacing {@code localhost} with {@code local.playwright}.
*/
@ -903,10 +891,6 @@ public interface Browser extends AutoCloseable {
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
*
* <p> Client certificate authentication is only active when at least one client certificate is provided. If you want to reject
* all client certificates sent by the server, you need to provide a client certificate with an {@code origin} that does
* not match any of the domains you plan to visit.
*
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
* replacing {@code localhost} with {@code local.playwright}.
*/

View File

@ -491,10 +491,6 @@ public interface BrowserType {
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
*
* <p> Client certificate authentication is only active when at least one client certificate is provided. If you want to reject
* all client certificates sent by the server, you need to provide a client certificate with an {@code origin} that does
* not match any of the domains you plan to visit.
*
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
* replacing {@code localhost} with {@code local.playwright}.
*/
@ -817,10 +813,6 @@ public interface BrowserType {
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
*
* <p> Client certificate authentication is only active when at least one client certificate is provided. If you want to reject
* all client certificates sent by the server, you need to provide a client certificate with an {@code origin} that does
* not match any of the domains you plan to visit.
*
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
* replacing {@code localhost} with {@code local.playwright}.
*/
@ -1394,11 +1386,6 @@ public interface BrowserType {
* the **parent** directory of the "Profile Path" seen at {@code chrome://version}.
*
* <p> Note that browsers do not allow launching multiple instances with the same User Data Directory.
*
* <p> <strong>NOTE:</strong> Chromium/Chrome: Due to recent Chrome policy changes, automating the default Chrome user profile is not supported.
* Pointing {@code userDataDir} to Chrome's main "User Data" directory (the profile used for your regular browsing) may
* result in pages not loading or the browser exiting. Create and use a separate directory (for example, an empty folder)
* as your automation profile instead. See https://developer.chrome.com/blog/remote-debugging-port for details.
* @since v1.8
*/
default BrowserContext launchPersistentContext(Path userDataDir) {
@ -1419,11 +1406,6 @@ public interface BrowserType {
* the **parent** directory of the "Profile Path" seen at {@code chrome://version}.
*
* <p> Note that browsers do not allow launching multiple instances with the same User Data Directory.
*
* <p> <strong>NOTE:</strong> Chromium/Chrome: Due to recent Chrome policy changes, automating the default Chrome user profile is not supported.
* Pointing {@code userDataDir} to Chrome's main "User Data" directory (the profile used for your regular browsing) may
* result in pages not loading or the browser exiting. Create and use a separate directory (for example, an empty folder)
* as your automation profile instead. See https://developer.chrome.com/blog/remote-debugging-port for details.
* @since v1.8
*/
BrowserContext launchPersistentContext(Path userDataDir, LaunchPersistentContextOptions options);

View File

@ -5812,8 +5812,8 @@ public interface Page extends AutoCloseable {
*/
Page opener();
/**
* Pauses script execution. Playwright will stop executing the script and wait for the user to either press the 'Resume'
* button in the page overlay or to call {@code playwright.resume()} in the DevTools console.
* Pauses script execution. Playwright will stop executing the script and wait for the user to either press 'Resume' button
* in the page overlay or to call {@code playwright.resume()} in the DevTools console.
*
* <p> User can inspect selectors or perform manual steps while paused. Resume will continue running the original script from
* the place it was paused.

View File

@ -1008,16 +1008,15 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void pause() {
TimeoutSettings settings = browserContext.timeoutSettings;
Double defaultNavigationTimeout = settings.defaultNavigationTimeout();
Double defaultTimeout = settings.defaultTimeout();
settings.setDefaultNavigationTimeout(0.0);
settings.setDefaultTimeout(0.0);
Double defaultNavigationTimeout = browserContext.timeoutSettings.defaultNavigationTimeout();
Double defaultTimeout = browserContext.timeoutSettings.defaultTimeout();
browserContext.setDefaultNavigationTimeout(0.0);
browserContext.setDefaultTimeout(0.0);
try {
runUntil(() -> {}, new WaitableRace<>(asList(context().pause(), (Waitable<JsonElement>) waitableClosedOrCrashed)));
} finally {
settings.setDefaultNavigationTimeout(defaultNavigationTimeout);
settings.setDefaultTimeout(defaultTimeout);
browserContext.setDefaultNavigationTimeout(defaultNavigationTimeout);
browserContext.setDefaultTimeout(defaultTimeout);
}
}
@ -1167,8 +1166,18 @@ public class PageImpl extends ChannelOwner implements Page {
}
}
}
List<Locator> mask = options.mask;
options.mask = null;
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
options.mask = mask;
params.remove("path");
if (mask != null) {
JsonArray maskArray = new JsonArray();
for (Locator locator: mask) {
maskArray.add(((LocatorImpl) locator).toProtocol());
}
params.add("mask", maskArray);
}
JsonObject json = sendMessage("screenshot", params, timeoutSettings.timeout(options.timeout)).getAsJsonObject();
byte[] buffer = Base64.getDecoder().decode(json.get("binary").getAsString());
@ -1358,13 +1367,10 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void waitForLoadState(LoadState state, WaitForLoadStateOptions options) {
final LoadState loadState = state == null ? LoadState.LOAD : state;
withTitle("Wait for load state \"" + loadState.toString().toLowerCase() + "\"", () -> {
withWaitLogging("Page.waitForLoadState", logger -> {
mainFrame.waitForLoadStateImpl(loadState, convertType(options, Frame.WaitForLoadStateOptions.class), logger);
mainFrame.waitForLoadStateImpl(state, convertType(options, Frame.WaitForLoadStateOptions.class), logger);
return null;
});
});
}
@Override

View File

@ -66,7 +66,6 @@ class Serialization {
.registerTypeHierarchyAdapter(JSHandleImpl.class, new HandleSerializer())
.registerTypeAdapter((new TypeToken<Map<String, String>>(){}).getType(), new StringMapSerializer())
.registerTypeAdapter((new TypeToken<Map<String, Object>>(){}).getType(), new FirefoxUserPrefsSerializer())
.registerTypeAdapter(LocatorImpl.class, new LocatorImplSerializer())
.registerTypeHierarchyAdapter(Path.class, new PathSerializer()).create();
static Gson gson() {
@ -491,13 +490,6 @@ class Serialization {
}
}
private static class LocatorImplSerializer implements JsonSerializer<LocatorImpl> {
@Override
public JsonElement serialize(LocatorImpl src, Type typeOfSrc, JsonSerializationContext context) {
return src.toProtocol();
}
}
private static class SameSiteAdapter extends TypeAdapter<SameSiteAttribute> {
@Override
public void write(JsonWriter out, SameSiteAttribute value) throws IOException {

View File

@ -23,7 +23,6 @@ import java.net.InetSocketAddress;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.function.Function;
import java.util.zip.GZIPOutputStream;
import static com.microsoft.playwright.Utils.copy;
@ -41,7 +40,6 @@ public class Server implements HttpHandler {
private final Map<String, String> csp = Collections.synchronizedMap(new HashMap<>());
private final Map<String, HttpHandler> routes = Collections.synchronizedMap(new HashMap<>());
private final Set<String> gzipRoutes = Collections.synchronizedSet(new HashSet<>());
private Function<String, InputStream> resourceProvider;
private static class Auth {
public final String user;
@ -77,8 +75,6 @@ public class Server implements HttpHandler {
server.createContext("/", this);
server.setExecutor(null); // creates a default executor
server.start();
// Resources from "src/test/resources/" are copied to "resources/" directory in the jar.
resourceProvider = path -> Server.class.getClassLoader().getResourceAsStream("resources" + path);
}
public void stop() {
@ -97,10 +93,6 @@ public class Server implements HttpHandler {
gzipRoutes.add(path);
}
void setResourceProvider(Function<String, InputStream> resourceProvider) {
this.resourceProvider = resourceProvider;
}
static class Request {
public final String url;
public final String method;
@ -195,16 +187,18 @@ public class Server implements HttpHandler {
path = "/index.html";
}
InputStream resource = resourceProvider.apply(path);
// Resources from "src/test/resources/" are copied to "resources/" directory in the jar.
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: " + path);
writer.write("File not found: " + resourcePath);
}
return;
}
exchange.getResponseHeaders().add("Content-Type", mimeType(new File(path)));
exchange.getResponseHeaders().add("Content-Type", mimeType(new File(resourcePath)));
ByteArrayOutputStream body = new ByteArrayOutputStream();
OutputStream output = body;
if (gzipRoutes.contains(path)) {

View File

@ -45,12 +45,12 @@ public class TestBase {
static final boolean isMac = Utils.getOS() == Utils.OS.MAC;
static final boolean isLinux = Utils.getOS() == Utils.OS.LINUX;
static final boolean isWindows = Utils.getOS() == Utils.OS.WINDOWS;
static final boolean headed;
static final boolean headful;
static final SameSiteAttribute defaultSameSiteCookieValue;
static {
String headedEnv = System.getenv("HEADED");
headed = headedEnv != null && !"0".equals(headedEnv) && !"false".equals(headedEnv);
String headfulEnv = System.getenv("HEADFUL");
headful = headfulEnv != null && !"0".equals(headfulEnv) && !"false".equals(headfulEnv);
defaultSameSiteCookieValue = initSameSiteAttribute();
}
@ -58,8 +58,8 @@ public class TestBase {
Page page;
BrowserContext context;
static boolean isHeaded() {
return headed;
static boolean isHeadful() {
return headful;
}
static boolean isChromium() {
@ -81,7 +81,7 @@ public class TestBase {
static BrowserType.LaunchOptions createLaunchOptions() {
BrowserType.LaunchOptions options;
options = new BrowserType.LaunchOptions();
options.headless = !headed;
options.headless = !headful;
options.channel = getBrowserChannelFromEnv();
return options;
}

View File

@ -27,7 +27,7 @@ public class TestBrowserContextCredentials extends TestBase {
static boolean isChromiumHeadedLike() {
// --headless=new, the default in all Chromium channels, is like headless.
return isChromium() && (isHeaded() || getBrowserChannelFromEnv() != null);
return isChromium() && (isHeadful() || getBrowserChannelFromEnv() != null);
}
@Test

View File

@ -129,12 +129,12 @@ public class TestBrowserContextProxy extends TestBase {
context.close();
}
static boolean isChromiumHeaded() {
return isChromium() && isHeaded();
static boolean isChromiumHeadful() {
return isChromium() && isHeadful();
}
@Test
@DisabledIf(value="isChromiumHeaded", disabledReason="fixme")
@DisabledIf(value="isChromiumHeadful", disabledReason="fixme")
void shouldExcludePatterns() {
server.setRoute("/target.html", exchange -> {
exchange.sendResponseHeaders(200, 0);

View File

@ -128,7 +128,7 @@ public class TestDefaultBrowserContext2 extends TestBase {
@Test
void shouldSupportExtraHTTPHeadersOption() throws ExecutionException, InterruptedException {
// TODO: test.flaky(browserName === "firefox" && headed && platform === "linux", "Intermittent timeout on bots");
// TODO: test.flaky(browserName === "firefox" && headful && platform === "linux", "Intermittent timeout on bots");
Page page = launchPersistent(new BrowserType.LaunchPersistentContextOptions().setExtraHTTPHeaders(mapOf("foo", "bar")));
Future<Server.Request> request = server.futureRequest("/empty.html");
page.navigate(server.EMPTY_PAGE);

View File

@ -93,12 +93,18 @@ public class TestDownload extends TestBase {
assertTrue(Files.exists(path));
byte[] bytes = readAllBytes(path);
assertEquals("Hello world", new String(bytes, UTF_8));
if (isChromium()) {
assertNotNull(error[0]);
assertTrue(error[0].getMessage().contains("net::ERR_ABORTED"));
assertEquals("about:blank", page.url());
} else if (isWebKit()) {
assertNotNull(error[0]);
assertTrue(error[0].getMessage().contains("Download is starting"));
assertEquals("about:blank", page.url());
} else {
assertNotNull(error[0]);
if (!chromiumVersionLessThan(browser.version(), "140.0.0.0")) {
assertTrue(error[0].getMessage().contains("Download is starting"));
}
if (!isFirefox())
assertEquals("about:blank", page.url());
page.close();
}
@ -341,14 +347,14 @@ public class TestDownload extends TestBase {
}
static boolean isChromiumHeaded() {
return isChromium() && isHeaded();
static boolean isChromiumHeadful() {
return isChromium() && isHeadful();
}
@Test
@DisabledIf(value="isChromiumHeaded", disabledReason="fixme")
@DisabledIf(value="isChromiumHeadful", disabledReason="fixme")
void shouldReportNewWindowDownloads() throws IOException {
// TODO: - the test fails in headed Chromium as the popup page gets closed along
// TODO: - the test fails in headful Chromium as the popup page gets closed along
// with the session before download completed event arrives.
// - WebKit doesn't close the popup page
Page page = browser.newPage(new Browser.NewPageOptions().setAcceptDownloads(true));

View File

@ -26,12 +26,12 @@ import static org.junit.jupiter.api.Assertions.*;
public class TestElementHandleBoundingBox extends TestBase {
static boolean isFirefoxHeaded() {
return isFirefox() && isHeaded();
static boolean isFirefoxHeadful() {
return isFirefox() && isHeadful();
}
@Test
@DisabledIf(value="isFirefoxHeaded", disabledReason="fail")
@DisabledIf(value="isFirefoxHeadful", disabledReason="fail")
void shouldWork() {
page.setViewportSize(500, 500);
page.navigate(server.PREFIX + "/grid.html");

View File

@ -0,0 +1,88 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIf;
import org.junit.jupiter.api.io.TempDir;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import static com.microsoft.playwright.Utils.mapOf;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.*;
public class TestLaunch extends TestBase {
@Override
@BeforeAll
// Hide base class method to not launch browser.
void launchBrowser() {
}
@Override
void createContextAndPage() {
// Do nothing
}
@Test
void passEnvVar() {
BrowserType.LaunchOptions options = new BrowserType.LaunchOptions();
options.setEnv(mapOf("DEBUG", "pw:protocol"));
launchBrowser(options);
}
public static boolean canRunHeaded() {
// On linux headed browser requires xvfb.
return isHeadful() || isMac || isWindows;
}
public static boolean canRunExtensionTest() {
return canRunHeaded() && isChromium();
}
@Test
@EnabledIf(value="com.microsoft.playwright.TestLaunch#canRunExtensionTest", disabledReason="Only Chromium Headed")
void shouldReturnBackgroundPages(@TempDir Path tmpDir) throws IOException {
Path profileDir = tmpDir.resolve("profile");
Files.createDirectories(profileDir);
String extensionPath = Paths.get("src/test/resources/simple-extension").toAbsolutePath().toString();
initBrowserType();
BrowserContext context = browserType.launchPersistentContext(profileDir, new BrowserType.LaunchPersistentContextOptions()
.setHeadless(false)
.setArgs(asList(
"--disable-extensions-except=" + extensionPath,
"--load-extension=" + extensionPath
)));
List<Page> backgroundPages = context.backgroundPages();
context.onBackgroundPage(page1 -> backgroundPages.add(page1));
context.waitForCondition(() -> !backgroundPages.isEmpty(),
new BrowserContext.WaitForConditionOptions().setTimeout(10_000));
Page backgroundPage = backgroundPages.get(0);
assertNotNull(backgroundPage);
assertTrue(context.backgroundPages().contains(backgroundPage));
assertFalse(context.pages().contains(backgroundPage));
context.close();
assertEquals(0, context.pages().size());
assertEquals(0, context.backgroundPages().size());
}
}

View File

@ -35,13 +35,13 @@ public class TestOptionsFactories {
public static BrowserType.LaunchOptions createLaunchOptions() {
BrowserType.LaunchOptions options;
options = new BrowserType.LaunchOptions();
options.headless = !getHeaded();
options.headless = !getHeadful();
return options;
}
private static boolean getHeaded() {
String headedEnv = System.getenv("HEADED");
return headedEnv != null && !"0".equals(headedEnv) && !"false".equals(headedEnv);
private static boolean getHeadful() {
String headfulEnv = System.getenv("HEADFUL");
return headfulEnv != null && !"0".equals(headfulEnv) && !"false".equals(headfulEnv);
}
public static String getBrowserName() {

View File

@ -356,10 +356,4 @@ public class TestPageBasic extends TestBase {
assertTrue(e.getMessage().contains("Can't add a null listener"));
}
@Test
void pagePauseShouldNotThrow() {
page.pause();
}
}

View File

@ -186,13 +186,6 @@ public class TestPageInterception extends TestBase {
assertTrue(urlMatches("http://playwright.dev", "http://playwright.dev/?x=y", "?x=y"));
assertTrue(urlMatches("http://playwright.dev/foo/", "http://playwright.dev/foo/bar?x=y", "./bar?x=y"));
// Case insensitive matching
assertTrue(urlMatches(null, "https://playwright.dev/fooBAR", "HtTpS://pLaYwRiGhT.dEv/fooBAR"));
assertTrue(urlMatches("http://ignored", "https://playwright.dev/fooBAR", "HtTpS://pLaYwRiGhT.dEv/fooBAR"));
// Path and search query are case-sensitive
assertFalse(urlMatches(null, "https://playwright.dev/foobar", "https://playwright.dev/fooBAR"));
assertFalse(urlMatches(null, "https://playwright.dev/foobar?a=b", "https://playwright.dev/foobar?A=B"));
// This is not supported, we treat ? as a query separator.
assertFalse(urlMatches(null, "http://localhost:8080/Simple/path.js", "http://localhost:8080/?imple/path.js"));
assertFalse(urlMatches(null, "http://playwright.dev/", "http://playwright.?ev"));

View File

@ -40,7 +40,7 @@ import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
// TODO: suite.skip(browserName === "firefox" && headed");
// TODO: suite.skip(browserName === "firefox" && headful");
public class TestPageScreenshot extends TestBase {
@Test
void shouldWork() throws IOException {
@ -136,7 +136,7 @@ public class TestPageScreenshot extends TestBase {
}
@Test
void maskShouldWorkForPage() {
void maskShouldWork() {
page.setViewportSize(500, 500);
page.navigate(server.PREFIX + "/grid.html");
byte[] screenshot = page.screenshot(new Page.ScreenshotOptions()
@ -146,17 +146,6 @@ public class TestPageScreenshot extends TestBase {
assertThrows(AssertionFailedError.class, () -> assertArrayEquals(screenshot, originalScreenshot));
}
@Test
void maskShouldWorkForLocator() {
page.navigate(server.PREFIX + "/grid.html");
Locator locatorToScreenshot = page.locator("div").first();
byte[] screenshot = locatorToScreenshot.screenshot(new Locator.ScreenshotOptions()
.setMask(asList(page.locator("img"))));
// TODO: toMatchSnapshot is not present in java, so we only checks that masked screenshot is different.
byte[] originalScreenshot = locatorToScreenshot.screenshot();
assertThrows(AssertionFailedError.class, () -> assertArrayEquals(screenshot, originalScreenshot));
}
@Test
void shouldWorkWithDeviceScaleFactorAndClip() {
try (BrowserContext context = browser.newContext(new Browser.NewContextOptions()

View File

@ -451,7 +451,7 @@ public class TestPageSetInputFiles extends TestBase {
List<String> relativePathsSorted = new ArrayList<>(webkitRelativePaths);
relativePathsSorted.sort(String::compareTo);
// https://issues.chromium.org/issues/345393164
if (isChromium() && !isHeaded() && chromiumVersionLessThan(browser.version(), "127.0.6533.0")) {
if (isChromium() && !isHeadful() && chromiumVersionLessThan(browser.version(), "127.0.6533.0")) {
assertEquals(asList("file-upload-test/file1.txt", "file-upload-test/file2"), relativePathsSorted);
} else {
assertEquals(asList("file-upload-test/file1.txt", "file-upload-test/file2", "file-upload-test/sub-dir/really.txt"), relativePathsSorted);

View File

@ -55,12 +55,12 @@ public class TestRequestFulfill extends TestBase {
assertEquals("Yo, page!", page.evaluate("document.body.textContent"));
}
static boolean isFirefoxHeaded() {
return isFirefox() && isHeaded();
static boolean isFirefoxHeadful() {
return isFirefox() && isHeadful();
}
@Test
@DisabledIf(value="isFirefoxHeaded", disabledReason="skip")
@DisabledIf(value="isFirefoxHeadful", disabledReason="skip")
void shouldAllowMockingBinaryResponses() {
page.route("**/*", route -> {
byte[] imageBuffer;
@ -85,9 +85,9 @@ public class TestRequestFulfill extends TestBase {
}
@Test
@DisabledIf(value="isFirefoxHeaded", disabledReason="skip")
@DisabledIf(value="isFirefoxHeadful", disabledReason="skip")
void shouldAllowMockingSvgWithCharset() {
// Firefox headed produces a different image.
// Firefox headful produces a different image.
page.route("**/*", route -> {
route.fulfill(new Route.FulfillOptions()
.setContentType("image/svg+xml ; charset=utf-8")

View File

@ -16,6 +16,8 @@
package com.microsoft.playwright;
import com.google.gson.Gson;
import com.google.gson.annotations.SerializedName;
import com.microsoft.playwright.options.AriaRole;
import com.microsoft.playwright.options.Location;
import com.microsoft.playwright.options.MouseButton;
@ -25,12 +27,18 @@ import org.junit.jupiter.api.BeforeAll;
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.util.regex.Pattern;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.*;
public class TestTracing extends TestBase {
@ -49,7 +57,7 @@ public class TestTracing extends TestBase {
}
@Test
void shouldCollectTrace1(@TempDir Path tempDir) throws Exception {
void shouldCollectTrace1(@TempDir Path tempDir) {
context.tracing().start(new Tracing.StartOptions().setName("test")
.setScreenshots(true).setSnapshots(true));
page.navigate(server.EMPTY_PAGE);
@ -60,18 +68,10 @@ public class TestTracing extends TestBase {
context.tracing().stop(new Tracing.StopOptions().setPath(traceFile));
assertTrue(Files.exists(traceFile));
TraceViewerPage.showTraceViewer(this.browserType, traceFile, traceViewer -> {
assertThat(traceViewer.actionTitles()).hasText(new Pattern[] {
Pattern.compile("Navigate to \"/empty.html\""),
Pattern.compile("Set content"),
Pattern.compile("Click"),
Pattern.compile("Close")
});
});
}
@Test
void shouldCollectTwoTraces(@TempDir Path tempDir) throws Exception {
void shouldCollectTwoTraces(@TempDir Path tempDir) {
context.tracing().start(new Tracing.StartOptions().setName("test1")
.setScreenshots(true).setSnapshots(true));
page.navigate(server.EMPTY_PAGE);
@ -89,25 +89,10 @@ public class TestTracing extends TestBase {
assertTrue(Files.exists(traceFile1));
assertTrue(Files.exists(traceFile2));
TraceViewerPage.showTraceViewer(this.browserType, traceFile1, traceViewer -> {
assertThat(traceViewer.actionTitles()).hasText(new Pattern[] {
Pattern.compile("Navigate to \"/empty.html\""),
Pattern.compile("Set content"),
Pattern.compile("Click")
});
});
TraceViewerPage.showTraceViewer(this.browserType, traceFile2, traceViewer -> {
assertThat(traceViewer.actionTitles()).hasText(new Pattern[] {
Pattern.compile("Double click"),
Pattern.compile("Close")
});
});
}
@Test
void shouldWorkWithMultipleChunks(@TempDir Path tempDir) throws Exception {
void shouldWorkWithMultipleChunks(@TempDir Path tempDir) {
context.tracing().start(new Tracing.StartOptions().setScreenshots(true).setSnapshots(true));
page.navigate(server.PREFIX + "/frames/frame.html");
@ -124,60 +109,28 @@ public class TestTracing extends TestBase {
assertTrue(Files.exists(traceFile1));
assertTrue(Files.exists(traceFile2));
TraceViewerPage.showTraceViewer(this.browserType, traceFile1, traceViewer -> {
assertThat(traceViewer.actionTitles()).hasText(new Pattern[] {
Pattern.compile("Set content"),
Pattern.compile("Click")
});
traceViewer.selectSnapshot("After");
FrameLocator frame = traceViewer.snapshotFrame("Set content", 0, false);
assertThat(frame.locator("button")).hasText("Click");
});
TraceViewerPage.showTraceViewer(this.browserType, traceFile2, traceViewer -> {
assertThat(traceViewer.actionTitles()).containsText(new String[] {"Hover"});
FrameLocator frame = traceViewer.snapshotFrame("Hover", 0, false);
assertThat(frame.locator("button")).hasText("Click");
});
}
@Test
void shouldCollectSources(@TempDir Path tmpDir) throws Exception {
void shouldCollectSources(@TempDir Path tmpDir) throws IOException {
Assumptions.assumeTrue(System.getenv("PLAYWRIGHT_JAVA_SRC") != null, "PLAYWRIGHT_JAVA_SRC must point to the directory containing this test source.");
context.tracing().start(new Tracing.StartOptions().setSources(true));
page.navigate(server.EMPTY_PAGE);
page.setContent("<button>Click</button>");
myMethodOuter();
page.click("'Click'");
Path trace = tmpDir.resolve("trace1.zip");
context.tracing().stop(new Tracing.StopOptions().setPath(trace));
TraceViewerPage.showTraceViewer(this.browserType, trace, traceViewer -> {
assertThat(traceViewer.actionTitles()).hasText(new Pattern[] {
Pattern.compile("Navigate to \"/empty.html\""),
Pattern.compile("Set content"),
Pattern.compile("Click")
});
traceViewer.showSourceTab();
assertThat(traceViewer.stackFrames()).containsText(new Pattern[] {
Pattern.compile("myMethodInner"),
Pattern.compile("myMethodOuter"),
Pattern.compile("shouldCollectSources")
});
traceViewer.selectAction("Set content");
assertThat(traceViewer.page().locator(".source-tab-file-name"))
.hasAttribute("title", Pattern.compile(".*TestTracing\\.java"));
assertThat(traceViewer.page().locator(".source-line-running"))
.containsText("page.setContent(\"<button>Click</button>\");");
});
}
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());
private void myMethodOuter() {
myMethodInner();
}
private void myMethodInner() {
page.getByText("Click").click();
String path = getClass().getName().replace('.', File.separatorChar);
String[] srcRoots = System.getenv("PLAYWRIGHT_JAVA_SRC").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));
}
@Test
@ -187,7 +140,7 @@ public class TestTracing extends TestBase {
}
@Test
void shouldRespectTracesDirAndName(@TempDir Path tempDir) throws Exception {
void shouldRespectTracesDirAndName(@TempDir Path tempDir) {
Path tracesDir = tempDir.resolve("trace-dir");
BrowserType.LaunchOptions options = createLaunchOptions();
options.setTracesDir(tracesDir);
@ -206,24 +159,6 @@ public class TestTracing extends TestBase {
context.tracing().stop(new Tracing.StopOptions().setPath(tempDir.resolve("trace2.zip")));
assertTrue(Files.exists(tracesDir.resolve("name2.trace")));
assertTrue(Files.exists(tracesDir.resolve("name2.network")));
TraceViewerPage.showTraceViewer(this.browserType, tempDir.resolve("trace1.zip"), traceViewer -> {
assertThat(traceViewer.actionTitles()).hasText(new Pattern[] {
Pattern.compile("Navigate to \"/one-style.html\"")
});
FrameLocator frame = traceViewer.snapshotFrame("Navigate", 0, false);
assertThat(frame.locator("body")).hasCSS("background-color", "rgb(255, 192, 203)");
assertThat(frame.locator("body")).hasText("hello, world!");
});
TraceViewerPage.showTraceViewer(this.browserType, tempDir.resolve("trace2.zip"), traceViewer -> {
assertThat(traceViewer.actionTitles()).hasText(new Pattern[] {
Pattern.compile("Navigate to \"/har.html\"")
});
FrameLocator frame = traceViewer.snapshotFrame("Navigate", 0, false);
assertThat(frame.locator("body")).hasCSS("background-color", "rgb(255, 192, 203)");
assertThat(frame.locator("body")).hasText("hello, world!");
});
}
}
@ -244,9 +179,11 @@ public class TestTracing extends TestBase {
context.tracing().groupEnd();
context.tracing().groupEnd();
TraceViewerPage.showTraceViewer(this.browserType, traceFile1, traceViewer -> {
assertThat(traceViewer.actionTitles()).containsText(new String[] {"actual", "Navigate to \"/empty.html\""});
});
List<TraceEvent> events = parseTraceEvents(traceFile1);
List<TraceEvent> groups = events.stream().filter(e -> "tracingGroup".equals(e.method)).collect(Collectors.toList());
assertEquals(1, groups.size());
assertEquals("actual", groups.get(0).title);
}
@Test
@ -265,16 +202,9 @@ public class TestTracing extends TestBase {
Path traceFile1 = tempDir.resolve("trace1.zip");
context.tracing().stop(new Tracing.StopOptions().setPath(traceFile1));
TraceViewerPage.showTraceViewer(this.browserType, traceFile1, traceViewer -> {
traceViewer.expandAction("inner group 1");
assertThat(traceViewer.actionTitles()).hasText(new Pattern[] {
Pattern.compile("outer group"),
Pattern.compile("Navigate to \"data:"),
Pattern.compile("inner group 1"),
Pattern.compile("Click"),
Pattern.compile("inner group 2"),
});
});
List<TraceEvent> events = parseTraceEvents(traceFile1);
List<String> calls = events.stream().filter(e -> e.renderedTitle() != null).map(e -> e.renderedTitle()).collect(Collectors.toList());
assertEquals(asList("outer group", "Frame.goto", "inner group 1", "Frame.click", "inner group 2", "Frame.isVisible"), calls);
}
@Test
@ -310,36 +240,37 @@ public class TestTracing extends TestBase {
Path traceFile1 = tempDir.resolve("trace1.zip");
context.tracing().stop(new Tracing.StopOptions().setPath(traceFile1));
TraceViewerPage.showTraceViewer(this.browserType, traceFile1, traceViewer -> {
assertThat(traceViewer.actionTitles()).hasText(new Pattern[] {
Pattern.compile("Install clock"),
Pattern.compile("Set content"),
Pattern.compile("Click"),
Pattern.compile("Click"),
Pattern.compile("Type"),
Pattern.compile("Press"),
Pattern.compile("Key down"),
Pattern.compile("Insert"),
Pattern.compile("Key up"),
Pattern.compile("Mouse move"),
Pattern.compile("Mouse down"),
Pattern.compile("Mouse move"),
Pattern.compile("Mouse wheel"),
Pattern.compile("Mouse up"),
Pattern.compile("Fast forward clock"),
Pattern.compile("Fast forward clock"),
Pattern.compile("Pause clock"),
Pattern.compile("Run clock"),
Pattern.compile("Set fixed time"),
Pattern.compile("Set system time"),
Pattern.compile("Resume clock"),
Pattern.compile("Click")
});
});
List<TraceEvent> events = parseTraceEvents(traceFile1);
List<String> calls = events.stream().filter(e -> e.renderedTitle() != null).map(e -> e.renderedTitle())
.collect(Collectors.toList());
assertEquals(asList(
"BrowserContext.clockInstall",
"Frame.setContent",
"Frame.click",
"Frame.click",
"Page.keyboardType",
"Page.keyboardPress",
"Page.keyboardDown",
"Page.keyboardInsertText",
"Page.keyboardUp",
"Page.mouseMove",
"Page.mouseDown",
"Page.mouseMove",
"Page.mouseWheel",
"Page.mouseUp",
"BrowserContext.clockFastForward",
"BrowserContext.clockFastForward",
"BrowserContext.clockPauseAt",
"BrowserContext.clockRunFor",
"BrowserContext.clockSetFixedTime",
"BrowserContext.clockSetSystemTime",
"BrowserContext.clockResume",
"Frame.click"),
calls);
}
@Test
public void shouldNotRecordNetworkActions(@TempDir Path tempDir) throws Exception {
public void shouldNotRecordNetworkActions(@TempDir Path tempDir) throws IOException {
context.tracing().start(new Tracing.StartOptions());
page.onRequest(request -> {
@ -353,30 +284,41 @@ public class TestTracing extends TestBase {
Path traceFile1 = tempDir.resolve("trace1.zip");
context.tracing().stop(new Tracing.StopOptions().setPath(traceFile1));
TraceViewerPage.showTraceViewer(this.browserType, traceFile1, traceViewer -> {
assertThat(traceViewer.actionTitles()).hasText(new Pattern[] {
Pattern.compile("Navigate to \"/empty.html\"")
});
});
List<TraceEvent> events = parseTraceEvents(traceFile1);
List<String> calls = events.stream().filter(e -> e.renderedTitle() != null).map(e -> e.renderedTitle())
.collect(Collectors.toList());
assertEquals(asList("Frame.goto"), calls);
}
@Test
public void shouldShowWaitForLoadState(@TempDir Path tempDir) throws Exception {
// https://github.com/microsoft/playwright/issues/37297
private static class TraceEvent {
String type;
String name;
String title;
@SerializedName("class")
String clazz;
String method;
Double startTime;
Double endTime;
String callId;
context.tracing().start(new Tracing.StartOptions());
String renderedTitle() {
if (title != null) {
return title;
}
if (clazz != null && method != null) {
return clazz + "." + method;
}
return null;
}
}
page.navigate(server.EMPTY_PAGE);
page.waitForLoadState();
Path traceFile1 = tempDir.resolve("trace1.zip");
context.tracing().stop(new Tracing.StopOptions().setPath(traceFile1));
TraceViewerPage.showTraceViewer(this.browserType, traceFile1, traceViewer -> {
assertThat(traceViewer.actionTitles()).hasText(new Pattern[] {
Pattern.compile("Navigate to \"/empty.html\""),
Pattern.compile("Wait for load state \"load\""),
});
});
private static List<TraceEvent> parseTraceEvents(Path traceFile) throws IOException {
Map<String, byte[]> files = Utils.parseZip(traceFile);
Map<String, byte[]> traces = files.entrySet().stream().filter(e -> e.getKey().endsWith(".trace")).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
assertNotNull(traces.get("trace.trace"));
return Arrays.stream(new String(traces.get("trace.trace"), UTF_8)
.split("\n"))
.map(s -> new Gson().fromJson(s, TraceEvent.class))
.collect(Collectors.toList());
}
}

View File

@ -1,119 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import com.microsoft.playwright.impl.driver.Driver;
import com.microsoft.playwright.options.AriaRole;
class TraceViewerPage {
private final Page page;
TraceViewerPage(Page page) {
this.page = page;
}
Page page() {
return page;
}
Locator actionsTree() {
return page.getByTestId("actions-tree");
}
Locator actionTitles() {
return page.locator(".action-title");
}
Locator stackFrames() {
return this.page.getByRole(AriaRole.LIST, new Page.GetByRoleOptions().setName("stack trace")).getByRole(AriaRole.LISTITEM);
}
void selectAction(String title, int ordinal) {
this.actionsTree().getByTitle(title).nth(ordinal).click();
}
void selectAction(String title) {
selectAction(title, 0);
}
void selectSnapshot(String name) {
this.page.getByRole(AriaRole.TAB, new Page.GetByRoleOptions().setName(name)).click();
}
FrameLocator snapshotFrame(String actionName, int ordinal, boolean hasSubframe) {
selectAction(actionName, ordinal);
while (page.frames().size() < (hasSubframe ? 4 : 3)) {
page.waitForTimeout(200);
}
return page.frameLocator("iframe.snapshot-visible[name=snapshot]");
}
FrameLocator snapshotFrame(String actionName, int ordinal) {
return snapshotFrame(actionName, ordinal, false);
}
void showSourceTab() {
page.getByRole(AriaRole.TAB, new Page.GetByRoleOptions().setName("Source")).click();
}
void expandAction(String title) {
this.actionsTree().getByRole(AriaRole.TREEITEM, new Locator.GetByRoleOptions().setName(title)).locator(".codicon-chevron-right").click();
}
static void showTraceViewer(BrowserType browserType, Path tracePath, TraceViewerConsumer callback) throws Exception {
Path driverDir = Driver.ensureDriverInstalled(java.util.Collections.emptyMap(), true).driverDir();
Path traceViewerPath = driverDir.resolve("package").resolve("lib").resolve("vite").resolve("traceViewer");
Server traceServer = Server.createHttp(Utils.nextFreePort());
traceServer.setResourceProvider(path -> {
Path filePath = traceViewerPath.resolve(path.substring(1));
if (Files.exists(filePath) && !Files.isDirectory(filePath)) {
try {
return Files.newInputStream(filePath);
} catch (IOException e) {
return null;
}
}
return null;
});
traceServer.setRoute("/trace.zip", exchange -> {
exchange.getResponseHeaders().add("Content-Type", "application/zip");
exchange.sendResponseHeaders(200, Files.size(tracePath));
Files.copy(tracePath, exchange.getResponseBody());
exchange.getResponseBody().close();
});
try (Browser browser = browserType.launch(TestBase.createLaunchOptions());
BrowserContext context = browser.newContext()) {
Page page = context.newPage();
page.navigate(traceServer.PREFIX + "/index.html?trace=" + traceServer.PREFIX + "/trace.zip");
TraceViewerPage traceViewer = new TraceViewerPage(page);
callback.accept(traceViewer);
} finally {
traceServer.stop();
}
}
@FunctionalInterface
interface TraceViewerConsumer {
void accept(TraceViewerPage traceViewer) throws Exception;
}
}

View File

@ -0,0 +1,9 @@
package com.microsoft.playwright.impl;
import com.microsoft.playwright.Browser;
public class ImplUtils {
public static boolean isRemoteBrowser(Browser browser) {
return ((BrowserImpl) browser).isConnectedOverWebSocket;
}
}

View File

@ -0,0 +1,3 @@
console.log('hey from the content-script');
self.thisIsTheContentScript = true;

View File

@ -0,0 +1,2 @@
// Mock script for background extension
window.MAGIC = 42;

View File

@ -0,0 +1,14 @@
{
"name": "Simple extension",
"version": "0.1",
"background": {
"scripts": ["index.js"]
},
"content_scripts": [{
"matches": ["<all_urls>"],
"css": [],
"js": ["content-script.js"]
}],
"permissions": ["background", "activeTab"],
"manifest_version": 2
}

13
pom.xml
View File

@ -6,7 +6,7 @@
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.50.0-SNAPSHOT</version>
<version>1.54.0</version>
<packaging>pom</packaging>
<name>Playwright Parent Project</name>
<description>Java library to automate Chromium, Firefox and WebKit with a single API.
@ -44,8 +44,8 @@
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<maven.compiler.parameters>true</maven.compiler.parameters>
<gson.version>2.13.1</gson.version>
<junit.version>5.13.4</junit.version>
<gson.version>2.12.1</gson.version>
<junit.version>5.12.1</junit.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<websocket.version>1.6.0</websocket.version>
<slf4j.version>2.0.17</slf4j.version>
@ -118,7 +118,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>3.5.0</version>
<version>3.4.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
@ -148,7 +148,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.11.3</version>
<version>3.11.2</version>
<configuration>
<additionalOptions>--allow-script-in-comments</additionalOptions>
<failOnError>false</failOnError>
@ -170,7 +170,6 @@
junit.jupiter.execution.parallel.config.dynamic.factor=0.5
</configurationParameters>
</properties>
<failIfNoSpecifiedTests>false</failIfNoSpecifiedTests>
<failIfNoTests>false</failIfNoTests>
<rerunFailingTestsCount>${env.PW_MAX_RETRIES}</rerunFailingTestsCount>
<!-- Activate the use of TCP to transmit events to the plugin and avoid
@ -181,7 +180,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>3.2.8</version>
<version>3.2.7</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>

View File

@ -1 +1 @@
1.55.0
1.54.1

View File

@ -6,7 +6,7 @@
<groupId>com.microsoft.playwright</groupId>
<artifactId>api-generator</artifactId>
<version>1.50.0-SNAPSHOT</version>
<version>1.54.0</version>
<name>Playwright - API Generator</name>
<description>
This is an internal module used to generate Java API from the upstream Playwright

View File

@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.microsoft.playwright</groupId>
<artifactId>test-cli-fatjar</artifactId>
<version>1.50.0-SNAPSHOT</version>
<version>1.54.0</version>
<name>Test Playwright Command Line FatJar</name>
<properties>
<compiler.version>1.8</compiler.version>

View File

@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.microsoft.playwright</groupId>
<artifactId>test-cli-version</artifactId>
<version>1.50.0-SNAPSHOT</version>
<version>1.54.0</version>
<name>Test Playwright Command Line Version</name>
<properties>
<compiler.version>1.8</compiler.version>

View File

@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.microsoft.playwright</groupId>
<artifactId>test-local-installation</artifactId>
<version>1.50.0-SNAPSHOT</version>
<version>1.54.0</version>
<name>Test local installation</name>
<description>Runs Playwright test suite (copied from playwright module) against locally cached Playwright</description>
<properties>
@ -64,15 +64,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.3</version>
<configuration>
<failIfNoSpecifiedTests>false</failIfNoSpecifiedTests>
<failIfNoTests>false</failIfNoTests>
<rerunFailingTestsCount>${env.PW_MAX_RETRIES}</rerunFailingTestsCount>
<!-- Activate the use of TCP to transmit events to the plugin and avoid
[WARNING] Corrupted STDOUT by directly writing to native stream in forked JVM -->
<forkNode implementation="org.apache.maven.plugin.surefire.extensions.SurefireForkNodeFactory"/>
</configuration>
<version>3.2.5</version>
</plugin>
</plugins>
</build>

View File

@ -1,8 +0,0 @@
#!/bin/bash
set -e
set +x
cd "$(dirname "$0")"
mvn package -D skipTests --no-transfer-progress
java -jar target/test-spring-boot-starter*.jar --async

View File

@ -9,7 +9,7 @@
</parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>test-spring-boot-starter</artifactId>
<version>1.50.0-SNAPSHOT</version>
<version>1.54.0</version>
<name>Test Playwright With Spring Boot</name>
<properties>
<spring.version>2.4.3</spring.version>

View File

@ -5,9 +5,6 @@ import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
@SpringBootApplication
public class TestApp implements CommandLineRunner {
@ -17,19 +14,6 @@ public class TestApp implements CommandLineRunner {
@Override
public void run(String... args) {
if (Arrays.asList(args).contains("--async")) {
runAsync();
} else {
runSync();
}
}
private void runAsync() {
CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(this::runSync);
voidCompletableFuture.join();
}
private void runSync() {
try (Playwright playwright = Playwright.create()) {
BrowserType browserType = getBrowserTypeFromEnv(playwright);
System.out.println("Running test with " + browserType.name());

View File

@ -6,7 +6,7 @@
<groupId>com.microsoft.playwright</groupId>
<artifactId>update-version</artifactId>
<version>1.50.0-SNAPSHOT</version>
<version>1.54.0</version>
<name>Playwright - Update Version in Documentation</name>
<description>
This is an internal module used to update versions in the documentation based on