Quarkus Playwright
Playwright is an open-source automation library designed for browser testing and web scraping. This extension supports two primary use cases:
-
Testing: Perform end-to-end tests for your Quarkus web application.
-
Runtime: Leverage Playwright for screen scraping or other browser tasks in your runtime application, including support for GraalVM native compilation.
Test Usage
The primary use case for Playwright is integration with @QuarkusTest for end-to-end testing of your application. You can easily create effective cross-browser end-to-end tests for your Quarkus web application using Playwright with frameworks such as Qute, Quinoa, Renarde, Web-Bundler, and MyFaces. Playwright Test was specifically designed to meet the requirements of end-to-end testing. It supports all modern rendering engines, including Chromium, WebKit, and Firefox. You can run tests on Windows, Linux, and macOS—either locally or in CI—both in headless and headed modes, with native mobile emulation for Google Chrome on Android and Mobile Safari.
Just add the dependency as <scope>test</scope> to your pom.xml:
<dependency>
<groupId>io.quarkiverse.playwright</groupId>
<artifactId>quarkus-playwright</artifactId>
<version>2.3.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
Write your tests:
@QuarkusTest
@WithPlaywright
public class WithDefaultPlaywrightTest {
@InjectPlaywright
BrowserContext context;
@TestHTTPResource("/")
URL index;
@Test
public void testIndex() {
final Page page = context.newPage();
Response response = page.navigate(index.toString());
Assertions.assertEquals("OK", response.statusText());
page.waitForLoadState();
String title = page.title();
Assertions.assertEquals("My Awesome App", title);
// Make sure the web app is loaded and hits the backend
final ElementHandle quinoaEl = page.waitForSelector(".toast-body.received");
String greeting = quinoaEl.innerText();
Assertions.assertEquals("Hello from RESTEasy Reactive", greeting);
}
}
Use the annotation @WithPlaywright() to change the browser (Chromium, Firefox, Webkit), headless, enable debug, logs, and other options.
Debug your tests with the Playwright inspector @WithPlaywright(debug=true).
Custom Test Selectors
Selectors can be used to install custom selector engines. An example of registering selector engine that queries elements based on a tag name:
@QuarkusTest
@WithPlaywright(selectors = {
@PlaywrightSelector(name = "tag", script = "{\n" +
" // Returns the first element matching given selector in the root's subtree.\n" +
" query(root, selector) {\n" +
" return root.querySelector(selector);\n" +
" },\n" +
" // Returns all elements matching given selector in the root's subtree.\n" +
" queryAll(root, selector) {\n" +
" return Array.from(root.querySelectorAll(selector));\n" +
" }\n" +
"}")
})
public class WithSelectorsPlaywrightTest {
@InjectPlaywright
BrowserContext context;
@Test
public void testIndex() {
final Page page = context.newPage();
// add a button to the page
page.setContent("<div><button>Click me</button></div>");
// Use the selector prefixed with its name.
Locator button = page.locator("tag=button");
// Combine it with built-in locators.
page.locator("tag=div").getByText("Click me").click();
// Can use it in any methods supporting selectors.
int buttonCount = (int) page.locator("tag=button").count();
Assertions.assertEquals(1, buttonCount);
}
}
Runtime Usage
Leverage Playwright for screen scraping or other browser tasks in your runtime application, including support for GraalVM native compilation.
Just add the runtime dependency to pom.xml:
<dependency>
<groupId>io.quarkiverse.playwright</groupId>
<artifactId>quarkus-playwright</artifactId>
<version>2.3.1</version>
</dependency>
In runtime mode, Playwright is used directly through its Java SDK without CDI injection. Follow the standard Microsoft Playwright documentation for usage patterns. Below is an example REST service that demonstrates screen scraping:
@Path("/playwright")
@ApplicationScoped
public class PlaywrightResource {
private static final Logger log = Logger.getLogger(PlaywrightResource.class);
/**
* Navigates to Google homepage and retrieves the page title using Playwright.
*
* <p>This endpoint demonstrates basic Playwright functionality by:
* <ul>
* <li>Launching a headless Chromium browser</li>
* <li>Creating a new page and navigating to google.com</li>
* <li>Retrieving and returning the page title</li>
* </ul>
* </p>
*
* @return The title of the Google homepage
*/
@GET
public String google() {
String pageTitle;
final BrowserType.LaunchOptions launchOptions = new BrowserType.LaunchOptions()
.setHeadless(true)
.setChromiumSandbox(false)
.setChannel("")
.setArgs(List.of("--disable-gpu"));
final Map<String, String> env = new HashMap<>(System.getenv());
env.put("DEBUG", "pw:api");
try (Playwright playwright = Playwright.create(new Playwright.CreateOptions().setEnv(env))) {
try (Browser browser = playwright.chromium().launch(launchOptions)) {
Page page = browser.newPage();
page.navigate("https://www.google.com/");
pageTitle = page.title();
log.infof("Page title: %s", pageTitle);
}
}
return pageTitle;
}
}
Setting Up CI
When running Playwright tests in CI, you need to ensure that the required browser dependencies are installed. In the case of GitHub Actions, add the following step to your workflow:
- name: Ensure browsers are installed
run: mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install-deps chromium"
For more information about browser installation and configuration, see the Playwright Browsers documentation.
Native
If you plan on running in a Docker image we highly recommend you use a pre-built image from Microsoft mcr.microsoft.com/playwright:v1.48.1 which is based on Ubuntu and already has all libraries and tools necessary for PlayWright.
FROM mcr.microsoft.com/playwright:v1.48.1-noble
WORKDIR /work/
RUN chown 1001:root /work \
&& chmod g+rwX /work \
&& chown 1001:root /work
COPY --chown=1001:root target/*.properties target/*.so /work/
COPY --chown=1001:root target/*-runner /work/application
# Make application executable for all users
RUN chmod ugo+x /work/application
EXPOSE 8080
USER 1001
CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]
Additional Configuration
There is additional configuration options available on the @BrowserContextConfig and @WithPlaywright annotations:
/**
* These settings all correspond to settings on {@link com.microsoft.playwright.Browser.NewContextOptions NewContextOptions}
* and {@link com.microsoft.playwright.BrowserContext BrowserContext}.
*/
@Target({})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface BrowserContextConfig {
/**
* Default maximum navigation time in milliseconds
* <p>
* Parsed using {@link java.time.Duration#parse(CharSequence)}
* </p>
*
* @see com.microsoft.playwright.BrowserContext#setDefaultNavigationTimeout(double)
*/
String defaultNavigationTimeout() default "";
/**
* Default maximum time for all methods accepting a timeout option, in milliseconds
* <p>
* Parsed using {@link java.time.Duration#parse(CharSequence)}
* </p>
*
* @see com.microsoft.playwright.BrowserContext#setDefaultTimeout(double)
*/
String defaultTimeout() default "";
/**
* Specific user agent to use in all contexts
*
* @see com.microsoft.playwright.Browser.NewContextOptions#setUserAgent(String)
*/
String userAgent() default "";
/**
* Specify user local, for example {@code en-GB}, {@code de-DE}, etc.
* Locale will affect {@code navigator.language}, {@code Accept-Language} request header value,
* as well as number and date formatting rules.
*
* @see com.microsoft.playwright.Browser.NewContextOptions#setLocale(String)
*/
String locale() default "";
/**
* Whether to emulate the network being offline for the browser context
*
* @see com.microsoft.playwright.Browser.NewContextOptions#setOffline(boolean)
*/
boolean offline() default false;
}
package io.quarkiverse.playwright;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import io.quarkus.test.common.QuarkusTestResource;
/**
* Annotation to configure and enable Playwright for Quarkus tests.
* <p>
* This annotation sets up a Playwright testing environment with options for
* browser selection, debugging, and configuration of other Playwright features.
* It is applied at the class level and managed by {@link QuarkusPlaywrightManager}.
* </p>
*
* <p>
* Usage example:
*
* <pre>
* {@code
* @WithPlaywright(browser = Browser.FIREFOX, headless = false, verbose = true)
* public class PlaywrightTest {
* // Test code here
* }
* }
* </pre>
* </p>
*
* @see io.quarkus.test.common.QuarkusTestResource
* @see QuarkusPlaywrightManager
* @since 1.0
*/
@QuarkusTestResource(QuarkusPlaywrightManager.class)
@Retention(RetentionPolicy.RUNTIME) // Annotation is retained at runtime for test setup.
@Target(ElementType.TYPE) // Applied only at the class level.
public @interface WithPlaywright {
/**
* Specifies the browser to use for Playwright tests.
* <p>
* Defaults to {@link Browser#CHROMIUM}.
* </p>
*/
Browser browser() default Browser.CHROMIUM;
/**
* Enables Playwright verbose logging.
* <p>
* Set to {@code true} to enable detailed logging, useful for debugging.
* </p>
*/
boolean verbose() default false;
/**
* Enables the Playwright Debug Inspector for debugging tests.
* <p>
* Use {@code true} to launch the inspector, which pauses tests for interactive debugging.
* </p>
*/
boolean debug() default false;
/**
* Specifies the distribution channel of the browser to use, such as "chrome" or "msedge".
* <p>
* Supported values include "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge",
* "msedge-beta", "msedge-dev", and "msedge-canary".
* </p>
* <p>
* Refer to the <a href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">
* Playwright documentation</a> for additional details on using these channels.
* </p>
*/
String channel() default "";
/**
* Defines custom attribute name to be used in Page.getByTestId(). "data-testid" is used by default.
* <p>
* Defaults to "data-testid".
* </p>
*/
String testId() default "data-testid";
/**
* Enables sandboxing for Chromium-based browsers.
* <p>
* Defaults to {@code false} for compatibility. Set to {@code true} to enable sandboxing if supported.
* </p>
*/
boolean chromiumSandbox() default false;
/**
* Runs the browser in headless mode, which is suitable for CI environments.
* <p>
* Defaults to {@code true} unless the {@code devtools} option is enabled.
* </p>
* <p>
* See more about headless mode in <a href="https://developers.google.com/web/updates/2017/04/headless-chrome">
* Chromium</a> and <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode">Firefox</a>.
* </p>
*/
boolean headless() default true;
/**
* Slows down Playwright operations by the specified number of milliseconds.
* <p>
* This is useful for observing browser interactions more clearly during tests.
* </p>
*/
double slowMo() default 0;
/**
* Specifies the directory to store video recordings of all pages.
* <p>
* If not set, video recording is disabled.
* </p>
*/
String recordVideoDir() default "";
/**
* Specifies command-line arguments to use when launching the browser.
* <p>
* Defaults to disabling GPU with {@code "--disable-gpu"}.
* </p>
*/
String[] args() default { "--disable-gpu" };
/**
* Specifies Playwright selectors to be used for locating elements in tests.
* <p>
* Multiple selectors can be defined and optionally named for reference.
* These selectors will be available for use in test methods annotated with this annotation.
* </p>
*
* @return Array of {@link PlaywrightSelector} annotations defining the selectors
*/
PlaywrightSelector[] selectors() default {};
/**
* Enum representing the supported browsers for Playwright testing.
*/
enum Browser {
CHROMIUM, // Google Chrome and other Chromium-based browsers.
FIREFOX, // Mozilla Firefox browser.
WEBKIT // WebKit browser, primarily for Safari compatibility.
}
/**
* Configuration for creation of the {@link com.microsoft.playwright.BrowserContext BrowserContext}
*/
BrowserContextConfig browserContext() default @BrowserContextConfig;
}