blob: 02c9ccc2e66714184f2c4f6e9d270d8ec3814a03 [file]
// Copyright (c) 2026, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.microsoft.playwright.Browser;
import com.microsoft.playwright.BrowserType;
import com.microsoft.playwright.ConsoleMessage;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.Playwright;
import com.microsoft.playwright.Request;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Rule;
import org.junit.rules.TemporaryFolder;
public class PlaywrightTestBase extends TestBase {
@Rule public TemporaryFolder tempFolder = new TemporaryFolder();
private static Playwright playwright;
private static Browser browser;
protected Page page;
private final List<ConsoleMessage> consoleMessages = new ArrayList<>();
private final List<String> pageErrors = new ArrayList<>();
private final List<Request> failedRequests = new ArrayList<>();
public Page getPage(boolean headful) {
if (page == null) {
initPage(headful);
}
return page;
}
private static synchronized void initBrowser(boolean headful) {
if (browser != null) {
return;
}
boolean headless = !headful;
Path chromeExecutable = headless ? resolveChromeExecutable() : null;
Map<String, String> env = new HashMap<>();
if (headless) {
env.put("PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD", "1");
} else {
// Allow downloading when headful. We set it to 0 to override any inherited env var.
env.put("PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD", "0");
}
Playwright.CreateOptions createOptions = new Playwright.CreateOptions().setEnv(env);
playwright = Playwright.create(createOptions);
BrowserType.LaunchOptions launchOptions = new BrowserType.LaunchOptions();
if (headless) {
if (chromeExecutable == null) {
throw new RuntimeException("chrome-headless-shell binary is missing");
}
launchOptions.setExecutablePath(chromeExecutable);
launchOptions.setHeadless(true);
} else {
launchOptions.setHeadless(false);
launchOptions.setSlowMo(50);
}
browser = playwright.chromium().launch(launchOptions);
}
private void initPage(boolean headful) {
initBrowser(headful);
page = browser.newPage();
consoleMessages.clear();
pageErrors.clear();
failedRequests.clear();
page.onConsoleMessage(consoleMessages::add);
page.onPageError(pageErrors::add);
page.onRequestFailed(failedRequests::add);
}
@After
public void tearDownPage() {
if (page != null) {
try {
verifyNoConsoleOrNetworkErrors();
} finally {
page.close();
page = null;
}
}
}
private void verifyNoConsoleOrNetworkErrors() {
// 1. Assert no console messages
for (ConsoleMessage message : consoleMessages) {
System.err.println("Unexpected console message: [" + message.type() + "] " + message.text());
fail("Unexpected console message: [" + message.type() + "] " + message.text());
}
// 2. Assert no page errors (unhandled exceptions)
for (String error : pageErrors) {
System.err.println("Page error: " + error);
}
assertTrue(pageErrors.isEmpty());
// 3. Assert no failed requests (missing assets etc)
for (Request request : failedRequests) {
System.err.println(
"Failed request: "
+ request.url()
+ " - "
+ (request.failure() != null ? request.failure() : "unknown"));
}
assertTrue(failedRequests.isEmpty());
}
@AfterClass
public static void tearDownBrowser() {
if (browser != null) {
browser.close();
browser = null;
}
if (playwright != null) {
playwright.close();
playwright = null;
}
}
private static Path resolveChromeExecutable() {
Path chromeDir = Paths.get(ToolHelper.THIRD_PARTY_DIR, "chrome_headless");
if (!Files.exists(chromeDir)) {
return null;
}
boolean isMac = ToolHelper.isMac();
boolean isWindows = ToolHelper.isWindows();
boolean isLinux = ToolHelper.isLinux();
if (isMac) {
chromeDir = chromeDir.resolve("mac");
} else if (isLinux) {
chromeDir = chromeDir.resolve("linux");
} else if (isWindows) {
chromeDir = chromeDir.resolve("windows");
}
if (!Files.exists(chromeDir)) {
return null;
}
String[] executables = {
"chrome-headless-shell", "chrome", "chromium", "chrome-headless-shell.exe", "chrome.exe"
};
String macArch = "";
if (isMac) {
String arch = System.getProperty("os.arch").toLowerCase();
if (arch.contains("aarch64") || arch.contains("arm64")) {
macArch = "mac-arm64";
} else {
macArch = "mac-x64";
}
}
final String targetMacArch = macArch;
try {
return Files.walk(chromeDir, 3)
.filter(
p -> {
String name = p.getFileName().toString();
if (isMac && !p.toString().contains(targetMacArch)) {
return false;
}
for (String exec : executables) {
if (name.equalsIgnoreCase(exec) && Files.isExecutable(p)) {
return true;
}
}
return false;
})
.findFirst()
.orElse(null);
} catch (IOException e) {
return null;
}
}
}