ScreenshotNeo

BlogHow-to

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.

By the ScreenshotNeo team1 October 202611 min read

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"
  1. Interact with the page in the opened browser.
  2. Use the Inspector to record clicks, fills and navigations.
  3. Add visibility, text or value assertions.
  4. Copy the generated Java code.
  5. 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.