How to Use Playwright with Java: A Practical Tutorial
Install Playwright Java with Maven, automate Chromium, Firefox and WebKit, write reliable locators and assertions, record tests, and capture screenshots.
Playwright Java lets you automate Chromium, Firefox and WebKit from a Maven project. Add the com.microsoft.playwright dependency, install the browser binaries that match your Playwright version, create a Playwright instance, launch a browser, create an isolated context and page, then use locators and web-first assertions instead of fixed sleeps.
This tutorial builds a working Java project, explains reliable test structure, shows screenshots and PDFs, covers recording with Codegen, and documents the failures you are most likely to encounter.
1. Requirements and project setup
- Java 8 or newer.
- Maven.
- A Playwright Java dependency. The official installation guide currently shows version
1.63.0; keep the version in your project and browser installation aligned. Playwright Java installation guide
Create a Maven project
Create this directory layout:
playwright-java-demo/
├── pom.xml
└── src/main/java/org/example/App.java
Use this pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>playwright-java-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<playwright.version>1.63.0</playwright.version>
</properties>
<dependencies>
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>${playwright.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.5.0</version>
</plugin>
</plugins>
</build>
</project>
Install browser binaries
Playwright downloads browser revisions that are coupled to the library release. After Maven resolves the dependency, install the default browsers:
mvn exec:java -e \
-Dexec.mainClass=com.microsoft.playwright.CLI \
-Dexec.args="install"
Install only one engine when that is all your job needs:
mvn exec:java -e \
-Dexec.mainClass=com.microsoft.playwright.CLI \
-Dexec.args="install chromium"
mvn exec:java -e \
-Dexec.mainClass=com.microsoft.playwright.CLI \
-Dexec.args="install firefox"
mvn exec:java -e \
-Dexec.mainClass=com.microsoft.playwright.CLI \
-Dexec.args="install webkit"
Linux and CI runners may also need operating-system libraries:
mvn exec:java -e \
-Dexec.mainClass=com.microsoft.playwright.CLI \
-Dexec.args="install --with-deps chromium"
Run the install command again when upgrading Playwright because a new release can require a different browser revision.
2. Your first Playwright Java script
The essential flow is Playwright.create(), choose a browser type, launch(), create a page, navigate, and close resources with try-with-resources.
package org.example;
import com.microsoft.playwright.*;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().launch();
Page page = browser.newPage();
page.navigate("https://playwright.dev/");
page.screenshot(new Page.ScreenshotOptions()
.setPath(Paths.get("example.png")));
browser.close();
}
}
}
Run it with:
mvn compile exec:java -Dexec.mainClass="org.example.App"
Browsers run headlessly by default. For visual debugging, launch headed and slow actions down:
Browser browser = playwright.chromium().launch(
new BrowserType.LaunchOptions()
.setHeadless(false)
.setSlowMo(250));
Choose Chromium, Firefox or WebKit
Browser chromium = playwright.chromium().launch();
Browser firefox = playwright.firefox().launch();
Browser webkit = playwright.webkit().launch();
Use the same page and locator APIs across all three engines. Run a focused smoke test in each engine when cross-browser behavior matters.
3. Browser contexts and test isolation
A BrowserContext is an isolated, in-memory browser profile. It keeps cookies, local storage and session state separate. Create a fresh context for each test so one test cannot authenticate another or inherit its storage. Browser contexts documentation
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().launch();
BrowserContext context = browser.newContext(
new Browser.NewContextOptions()
.setViewportSize(1440, 900)
.setLocale("en-US")
.setTimezoneId("America/New_York"));
Page page = context.newPage();
page.navigate("https://example.com");
context.close();
browser.close();
}
For authentication-heavy suites, save storage state once and load it into a new context when appropriate. Do not reuse a context between tests that are intended to be independent.
4. Locators that survive UI changes
Locators are Playwright’s central mechanism for auto-waiting and retry-ability. Prefer the way a user identifies an element over implementation details such as a generated CSS class. Locators documentation
| Locator | Best use | Java example |
|---|---|---|
getByRole |
Buttons, links, headings and other accessible controls | page.getByRole(AriaRole.BUTTON, ...) |
getByLabel |
Form fields with an accessible label | page.getByLabel("Email") |
getByText |
Visible non-interactive text | page.getByText("Welcome") |
getByPlaceholder |
Inputs identified by placeholder | page.getByPlaceholder("Search") |
getByTestId |
An explicit testing contract | page.getByTestId("save") |
locator |
Fallback for a stable CSS or XPath contract | page.locator("[data-state=ready]") |
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
import com.microsoft.playwright.options.AriaRole;
page.getByLabel("User Name").fill("John");
page.getByLabel("Password").fill("secret-password");
page.getByRole(AriaRole.BUTTON,
new Page.GetByRoleOptions().setName("Sign in"))
.click();
assertThat(page.getByText("Welcome, John!")).isVisible();
Locators resolve against the current DOM when an action runs, which is useful when a frontend re-renders components.
5. Waiting, navigation and assertions
Actions wait for an element to be present and actionable. Playwright assertions retry until the expected condition is true or the assertion timeout expires. This is generally more reliable than inserting arbitrary sleeps. Actionability and assertions
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
page.navigate("https://example.com");
assertThat(page).hasTitle("Example Domain");
assertThat(page.locator("h1")).hasText("Example Domain");
assertThat(page.locator("a")).hasAttribute("href", "https://iana.org/domains/example");
When navigation must finish before the next action, combine the triggering action and the expected navigation:
page.getByRole(AriaRole.LINK,
new Page.GetByRoleOptions().setName("More information"))
.click();
assertThat(page).hasURL("https://www.iana.org/help/example-domains");
Waiting for a specific condition
page.waitForSelector("[data-testid=results]");
assertThat(page.getByTestId("results")).isVisible();
Prefer a locator assertion or a domain-specific condition when possible. Use a short explicit wait only for a condition Playwright cannot express directly.
The Locator.all() edge case
Locator.all() returns immediately and does not wait for a changing list to finish rendering. Calling it while results are still arriving can produce an incomplete or flaky collection. First wait for a stable condition, then enumerate:
Locator rows = page.getByRole(AriaRole.LISTITEM);
assertThat(rows).hasCount(3);
for (Locator row : rows.all()) {
System.out.println(row.innerText());
}
6. A complete test-style example
package org.example;
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.AriaRole;
import java.nio.file.Paths;
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
public class LoginSmoke {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().launch();
BrowserContext context = browser.newContext(
new Browser.NewContextOptions().setViewportSize(1440, 900));
Page page = context.newPage();
page.navigate("https://your-app.example/login");
assertThat(page).hasTitle("Sign in");
page.getByLabel("User Name").fill(System.getenv("TEST_USER"));
page.getByLabel("Password").fill(System.getenv("TEST_PASSWORD"));
page.getByRole(AriaRole.BUTTON,
new Page.GetByRoleOptions().setName("Sign in"))
.click();
assertThat(page.getByRole(AriaRole.HEADING,
new Page.GetByRoleOptions().setName("Dashboard")))
.isVisible();
page.screenshot(new Page.ScreenshotOptions()
.setPath(Paths.get("artifacts/dashboard.png"))
.setFullPage(true));
context.close();
browser.close();
}
}
}
Keep credentials in environment variables or your CI secret store. Never commit real passwords to source control.
7. Screenshots, full pages and PDFs
Viewport and full-page screenshots
page.screenshot(new Page.ScreenshotOptions()
.setPath(Paths.get("viewport.png"))
.setType("png"));
page.screenshot(new Page.ScreenshotOptions()
.setPath(Paths.get("full-page.jpg"))
.setType("jpeg")
.setQuality(85)
.setFullPage(true));
Full-page capture can be tall on long documents. For deterministic output, set the viewport, wait for the page’s meaningful content, and ensure lazy-loaded content has been triggered before capture.
Element screenshots
page.getByRole(AriaRole.HEADING,
new Page.GetByRoleOptions().setName("Pricing"))
.screenshot(new Locator.ScreenshotOptions()
.setPath(Paths.get("pricing-heading.png")));
page.locator(".invoice").screenshot(new Locator.ScreenshotOptions()
.setPath(Paths.get("invoice.png")));
PDF output
PDF generation is supported by Chromium. It requires headless mode:
page.pdf(new Page.PdfOptions()
.setPath(Paths.get("page.pdf"))
.setFormat("A4")
.setPrintBackground(true)
.setLandscape(false));
8. Recording a workflow with Codegen
Codegen opens a browser and Playwright Inspector so you can perform actions, add assertions and copy starter code. Its locator generator prioritizes role, text and test-id locators and tries to make ambiguous matches unique. Codegen documentation
mvn exec:java -e \
-Dexec.mainClass=com.microsoft.playwright.CLI \
-Dexec.args="codegen demo.playwright.dev/todomvc"
- Interact with the page in the opened browser.
- Use the Inspector to record clicks, fills and navigations.
- Add visibility, text or value assertions.
- Copy the generated Java code.
- Rename locators, remove incidental actions and extract reusable page objects.
Generated code is a starting point. Keep assertions that describe a real user-visible contract and replace accidental selectors with stable roles, labels or test IDs.
9. Configuration options you will use often
| Need | Option or API | Why it matters |
|---|---|---|
| Visible debugging | LaunchOptions.setHeadless(false) |
Shows the browser window. |
| Slower debugging | LaunchOptions.setSlowMo(250) |
Spaces out actions. |
| Viewport | NewContextOptions.setViewportSize(width, height) |
Makes layout and screenshots repeatable. |
| Locale | setLocale("en-US") |
Controls locale-sensitive rendering. |
| Timezone | setTimezoneId("America/New_York") |
Reproduces date and time behavior. |
| Permissions | setPermissions(...) |
Controls browser permissions in a context. |
| Headers | setExtraHTTPHeaders(...) |
Adds test or API headers to requests. |
| Geolocation | setGeolocation(...) |
Tests location-aware pages with permission enabled. |
| Proxy | LaunchOptions.setProxy(...) |
Routes browser traffic through a proxy. |
Keep environment-specific values outside the test code and construct the context from configuration. This lets the same test run against staging, production-like previews and local development.
10. Troubleshooting Playwright Java
| Symptom | Likely cause | Fix |
|---|---|---|
Executable doesn't exist |
Browser binaries were not installed or no longer match the library. | Run the Playwright CLI install command again after dependency changes. |
| Browser fails to start on Linux | Missing shared libraries or sandbox dependencies. | Use install --with-deps chromium in the image or CI setup. |
| Timeout waiting for a locator | Wrong accessible name, hidden element, wrong page, or a page state that never occurs. | Inspect the DOM and accessibility name, confirm the URL, and assert the prerequisite state before clicking. |
| Flaky fixed-time waits | Network and rendering time vary between runs. | Replace sleeps with locator actions, web-first assertions or a specific readiness condition. |
Locator.all() returns too few items |
The list is still rendering; all() does not wait. |
Wait for a count or stable marker before enumerating. |
| Tests affect one another | Cookies or local storage are shared. | Create a new BrowserContext for each test. |
| Headed mode cannot launch in CI | No display server is available. | Use headless mode, or provide a CI display service when visual debugging is required. |
| Screenshot misses lazy content | Images or sections load only after scrolling or interaction. | Scroll through the page, wait for the relevant locator, then capture full page. |
| PDF call fails in Firefox or WebKit | PDF generation is a Chromium capability. | Use a Chromium page for PDF output. |
11. Performance, reliability and cost considerations
- Reuse the browser process: launching one browser and creating contexts per test is usually cheaper than launching a new browser for every test.
- Keep contexts short-lived: close pages and contexts so memory from large pages does not accumulate.
- Parallelize carefully: independent contexts can run concurrently, but shared test data, rate limits and CPU contention can make parallel tests less reliable.
- Install browsers in the image: CI runs become more predictable when the matching browser binaries and Linux dependencies are prepared during image creation.
- Capture artifacts on failure: save screenshots, traces or HTML when a test fails so the failure can be diagnosed without rerunning immediately.
- Control page variability: fix viewport, locale, timezone and test data when pixel output matters.
- Budget for downloads: browser binaries and OS dependencies consume CI storage and network bandwidth; cache them between jobs where your CI system supports it.
12. Or skip the browser setup
If your goal is a clean website screenshot rather than browser automation, ScreenshotNeo provides a one-request screenshot API and an MCP server for AI agents. It removes cookie and consent banners, newsletter popups and chat widgets before capture; bot checks, blank pages, failed loads and cache hits are not billed. Every response identifies the page verdict and billing result with X-Page-Verdict and X-Billed headers. The MCP tools are take_screenshot, get_page_info and capture_pdf.
See the ScreenshotNeo API documentation for the complete option list. The basic call is:
cURL
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
-o shot.webp
Python
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://stripe.com'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
ScreenshotNeo supports full-page capture with lazy images loaded, CSS element capture, dark mode, device presets and custom viewports, retina scale, PDFs with paper size, margins, landscape and page ranges, HTML/CSS rendering, custom JavaScript and CSS, clicks, selector waits, delays, network-idle waits, request and resource blocking, headers, cookies, user agents, Authorization, timezone, geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed image links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, a usage API and an OpenAPI specification. Existing parameter names used by other screenshot APIs also work to make migration easier.
Plans include 1,000 screenshots per month free with no card, then Starter at $5 for 3,000, Growth at $15 for 15,000, Pro at $39 for 60,000, Scale at $99 for 250,000 and Business at $249 for 1,000,000. Yearly billing gives two months free, and every feature is available on every plan. Create a free ScreenshotNeo account.
13. FAQ
Can Playwright Java test all three browser engines?
Yes. The Java API exposes Chromium, Firefox and WebKit through the same Playwright object. Install the engines you intend to run and include them in your CI matrix.
Does Playwright require Java 8?
Java 8 or newer is the baseline described by the official Java installation guide. Newer JDKs are also commonly used; keep your Maven compiler settings consistent with your runtime.
Should I use CSS selectors or XPath?
Use role, label, text, placeholder, alt-text or test-id locators first. Use CSS or XPath when the application exposes a stable contract that those user-facing locators cannot express.
Why does a test pass locally but fail in CI?
Check browser installation and Linux dependencies, headless versus headed mode, viewport differences, missing environment variables, timing assumptions and test data shared between workers.
Can Codegen produce production-ready tests?
It produces useful starter code and locators. Review the generated flow, remove incidental actions, rename variables, add meaningful assertions and isolate setup before committing it as a maintained test.


