ScreenshotNeo

BlogHow-to

How to Use Playwright with Java TestNG

Set up Playwright in a Java TestNG project, isolate browser state per test, write reliable checks, and run the suite in CI.

By the ScreenshotNeo team1 October 202611 min read

Use Playwright for Java as a Maven dependency, install the browser binaries for that dependency version, and let TestNG manage the lifecycle. Create Playwright and Browser once per test class, then create a fresh BrowserContext and Page for every test method. This keeps tests isolated while avoiding the startup cost of launching a browser for every method.

This guide uses the official Playwright Java APIs and TestNG annotations. The dependency version shown in the official installation example is 1.63.0; treat it as a documentation example and confirm the version you want on the current Playwright installation page.

1. Create the Maven project

Start with a standard Maven project. Playwright Java requires Java 8 or newer according to the official setup documentation. Platform support and minimum versions can change, so check the installation page for your operating system.

<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>example</groupId>
  <artifactId>playwright-testng-demo</artifactId>
  <version>1.0-SNAPSHOT</version>

  <properties>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <playwright.version>1.63.0</playwright.version>
    <testng.version>7.10.2</testng.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>com.microsoft.playwright</groupId>
      <artifactId>playwright</artifactId>
      <version>${playwright.version}</version>
    </dependency>
    <dependency>
      <groupId>org.testng</groupId>
      <artifactId>testng</artifactId>
      <version>${testng.version}</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>3.2.5</version>
        <configuration>
          <includes>
            <include>**/*Test.java</include>
          </includes>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

Put test classes under src/test/java. The Playwright dependency supplies the Java API and the CLI used to download browser binaries.

2. Install Playwright browsers

After adding or changing the Maven dependency, install the matching browser binaries:

mvn dependency:resolve
mvn exec:java -Dexec.mainClass=com.microsoft.playwright.CLI -Dexec.args="install"

To install one engine only, pass its name:

mvn exec:java -Dexec.mainClass=com.microsoft.playwright.CLI -Dexec.args="install chromium"
mvn exec:java -Dexec.mainClass=com.microsoft.playwright.CLI -Dexec.args="install firefox"
mvn exec:java -Dexec.mainClass=com.microsoft.playwright.CLI -Dexec.args="install webkit"

Playwright browsers are coupled to the Playwright version. Repeat the install step when you upgrade the dependency. On Linux CI agents, install operating-system dependencies as well when the runner image does not already contain them:

mvn exec:java -Dexec.mainClass=com.microsoft.playwright.CLI -Dexec.args="install --with-deps chromium"

See the official browser installation guide for the supported command variants and the continuous integration guide for runner-specific examples.

3. Manage Playwright with TestNG annotations

The recommended TestNG lifecycle is:

  1. @BeforeClass: create Playwright and launch the shared Browser.
  2. @BeforeMethod: create a new BrowserContext and Page for the test method.
  3. Test method: navigate, interact, and assert.
  4. @AfterMethod: close the context, which also closes its pages.
  5. @AfterClass: close the Browser and Playwright.

A BrowserContext is an independent browser session. Separate contexts do not share cookies or cache, and non-persistent contexts do not write browsing data to disk. Close contexts before the Browser so Playwright can flush artifacts such as videos or HAR files. This follows the lifecycle pattern in the official Java TestNG guide.

package example;

import com.microsoft.playwright.Browser;
import com.microsoft.playwright.BrowserContext;
import com.microsoft.playwright.BrowserType;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.Playwright;
import org.testng.annotations.AfterClass;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;

public abstract class PlaywrightTestBase {
  protected Playwright playwright;
  protected Browser browser;
  protected BrowserContext context;
  protected Page page;

  @BeforeClass(alwaysRun = true)
  public void launchBrowser() {
    playwright = Playwright.create();
    browser = playwright.chromium().launch(
        new BrowserType.LaunchOptions().setHeadless(true));
  }

  @BeforeMethod(alwaysRun = true)
  public void createTestContext() {
    context = browser.newContext(new Browser.NewContextOptions()
        .setViewportSize(1440, 900));
    page = context.newPage();
  }

  @AfterMethod(alwaysRun = true)
  public void closeTestContext() {
    if (context != null) {
      context.close();
      context = null;
      page = null;
    }
  }

  @AfterClass(alwaysRun = true)
  public void closeBrowser() {
    if (browser != null) {
      browser.close();
      browser = null;
    }
    if (playwright != null) {
      playwright.close();
      playwright = null;
    }
  }
}

Set setHeadless(false) while debugging locally. Headless mode is the default and is normally preferred in CI.

4. Write a complete TestNG test

package example;

import com.microsoft.playwright.Locator;
import com.microsoft.playwright.options.AriaRole;
import org.testng.Assert;
import org.testng.annotations.Test;

public class ExampleTest extends PlaywrightTestBase {
  @Test
  public void pageHasExpectedTitle() {
    page.navigate("https://playwright.dev/");
    Assert.assertEquals(page.title(), "Playwright");
  }

  @Test
  public void documentationLinkIsVisible() {
    page.navigate("https://playwright.dev/");

    Locator docsLink = page.getByRole(
        AriaRole.LINK,
        new Page.GetByRoleOptions().setName("Get started"));

    Assert.assertTrue(docsLink.isVisible());
  }

  @Test
  public void searchFormCanBeFilled() {
    page.navigate("https://example.com/");
    page.locator("body").waitFor();
    Assert.assertTrue(page.url().startsWith("https://example.com"));
  }
}

In a real application, replace the example URLs and selectors with elements from your site. Playwright locators provide auto-waiting and retry behavior. Prefer user-facing locators such as roles and labels, then stable test IDs where needed. CSS and XPath selectors are available, but brittle selectors tied to layout or generated class names tend to break during harmless UI changes.

Common locator patterns

// Accessible role and name
page.getByRole(AriaRole.BUTTON,
    new Page.GetByRoleOptions().setName("Save")).click();

// Label associated with an input
page.getByLabel("Email").fill("dev@example.com");

// Stable test ID
page.getByTestId("checkout-submit").click();

// Text, when it is the user-visible contract
page.getByText("Order complete").isVisible();

// CSS locator for a deliberate technical hook
page.locator("[data-state='ready']").waitFor();

Assertions and waiting

Use Playwright’s web-first assertions when available in your chosen assertion style; they wait for the expected browser state. TestNG assertions remain useful for values returned directly by the API, such as a title or URL.

import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;

assertThat(page).hasTitle("Playwright");
assertThat(page.getByRole(AriaRole.HEADING,
    new Page.GetByRoleOptions().setName("Installation"))).isVisible();
assertThat(page).hasURL("https://playwright.dev/java/docs/intro");

Avoid arbitrary sleeps for normal synchronization. Wait for a locator, URL, load state, or application-specific response. Use a short explicit delay only when the application has a real timing requirement that cannot be expressed as an observable condition.

5. Choose the browser and context options

Playwright supports Chromium, Firefox, and WebKit. Run the same test class against each engine when browser coverage is part of your quality target.

browser = playwright.firefox().launch(
    new BrowserType.LaunchOptions().setHeadless(true));

browser = playwright.webkit().launch(
    new BrowserType.LaunchOptions().setHeadless(true));

Context options let each test declare the environment it needs:

context = browser.newContext(new Browser.NewContextOptions()
    .setViewportSize(1280, 800)
    .setLocale("en-US")
    .setTimezoneId("America/New_York")
    .setUserAgent("checkout-tests/1.0")
    .setIgnoreHTTPSErrors(false));
Need Use Reason
Independent login state New context per method Cookies and storage do not leak between tests.
Multiple user roles Separate contexts or storage states Each role gets its own authenticated session.
Responsive coverage setViewportSize Exercise desktop and mobile layouts deterministically.
Regional behavior setLocale, setTimezoneId Reproduce formatting and time-dependent behavior.
Permissions Context permissions options Grant only the browser capabilities the test needs.

6. Test data, authentication, and cleanup

Keep test data independent. If a test creates an account or order, give it a unique identifier and clean it up through an API or fixture where possible. BrowserContext isolation protects browser state, but it cannot undo records written to your application database.

For authenticated suites, log in once to produce a storage state, then create contexts from that state when the login flow itself is not under test. Keep the state file outside source control and delete it after the run if it contains credentials or session tokens.

BrowserContext context = browser.newContext(
    new Browser.NewContextOptions().setStorageStatePath(Paths.get("auth.json")));
Page page = context.newPage();

When the login journey is the subject of a test, start with a clean context instead. Do not share a Page between parallel test methods.

7. Run the suite

mvn test

To run one class:

mvn -Dtest=ExampleTest test

Use a TestNG suite XML file when you need explicit grouping or browser parameters:

<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="browser-suite" parallel="false">
  <test name="smoke">
    <classes>
      <class name="example.ExampleTest"/>
    </classes>
  </test>
</suite>

Run it with:

mvn -Dsurefire.suiteXmlFiles=testng.xml test

8. Parallel execution without state leaks

Parallel TestNG execution can reduce wall-clock time, but each parallel test must own its context and Page. The shared Browser is safe as the expensive process; the test session belongs to the method.

<suite name="parallel-suite" parallel="methods" thread-count="4">
  <test name="parallel-tests">
    <classes>
      <class name="example.ExampleTest"/>
    </classes>
  </test>
</suite>

Do not store mutable per-test data in static fields. If your base class is used with parallel methods, make sure TestNG creates separate fixture instances or use a thread-safe fixture design. Validate parallel behavior against your application’s test data and rate limits before increasing the thread count.

9. Continuous integration

A CI job needs Java, Maven, the Playwright browser binaries, and the operating-system libraries required by those browsers before tests execute. A typical sequence is:

java -version
mvn --version
mvn dependency:resolve
mvn exec:java -Dexec.mainClass=com.microsoft.playwright.CLI -Dexec.args="install --with-deps chromium"
mvn test

Cache Maven dependencies where your CI system permits it. Browser caches can also save download time, but invalidate them when the Playwright dependency changes. Keep headed mode disabled on agents without a display. Save TestNG reports, screenshots, traces, videos, or HAR files as CI artifacts when a failure needs investigation.

10. Debugging failures

Symptom Likely cause Fix
Executable does not exist Browser binaries were not installed or do not match the dependency. Run the Playwright CLI install command again after resolving the current Maven version.
Missing shared library on Linux Browser OS dependencies are absent. Use install --with-deps on a supported CI image or install the required packages through the image.
Element is not found The selector is wrong, the page is still loading, or the element is inside a frame. Prefer a role, label, or test ID; wait for the relevant state; inspect frames and locator counts.
Strict mode violation A locator matches more than one element. Make the locator specific with an accessible name, parent scope, or a deliberate test ID.
Tests affect one another A context, Page, static field, or server-side record is being reused. Create a context per method, remove mutable static state, and isolate test data.
Flaky timeout The test waits for time rather than an observable application condition, or the CI machine is overloaded. Wait for a locator, URL, response, or load state; then review timeout and parallelism settings.
Browser closes before artifacts are saved The Browser closed before its contexts. Close each context in @AfterMethod, then close Browser in @AfterClass.
Headed mode fails in CI No display server is available. Use headless mode or configure the CI display environment.

11. Performance, reliability, and cost

  • Reuse expensive objects: keep Playwright and Browser at class scope, as shown above.
  • Isolate state: create a fresh context and Page for every test method.
  • Control concurrency: raise TestNG thread count only after checking CPU, memory, application capacity, and test-data collisions.
  • Reduce unnecessary navigation: use API setup for records when the browser flow is not the behavior under test.
  • Make waits observable: locator and web-first assertions are more reliable than fixed sleeps.
  • Keep browsers aligned: install binaries after dependency changes so local and CI runs use the expected revision.
  • Capture diagnostics on failure: screenshots and traces shorten the time needed to identify a selector, timing, or environment problem.

Playwright itself is a software dependency; the main run-time costs are CI CPU and memory, browser downloads, test data, and any hosted infrastructure you choose. The official documentation does not define a universal execution benchmark, so size runners from your own suite timings.

12. When you only need a rendered screenshot

Playwright is appropriate when you need assertions, interaction, fixtures, and browser control. If your job is to fetch a clean image or PDF from a URL, maintaining browser setup, consent handling, and capture code can be unnecessary.

Or skip the browser setup

ScreenshotNeo provides a website screenshot API and MCP server. It removes cookie and consent banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, failed loads, timeouts, and cache hits are not billed. Responses identify the result with X-Page-Verdict and X-Billed headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.

One request returns PNG, JPEG, WebP, or PDF. The API also supports full-page capture with lazy images loaded, CSS element capture, dark mode, device presets, custom viewports, retina scale, PDF paper and margin settings, custom CSS and JavaScript, clicks, selector waits, delays, network-idle waits, request blocking, headers, cookies, user agents, Authorization, timezone, geolocation, transparent backgrounds, resizing, TTL caching, signed image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API, and an OpenAPI specification. Existing screenshot API parameter names are accepted to ease migration. See the ScreenshotNeo documentation.

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 bytes = new Uint8Array(await res.arrayBuffer());
await Bun.write('shot.webp', bytes);

The Free plan includes 1,000 shots per month with no card. Paid plans start at $5 for 3,000 shots, and every feature is available on every plan. Create a free ScreenshotNeo account.

FAQ

Should I create Playwright for every TestNG method?

No. Reuse Playwright and Browser at class scope, then create and close a context per method. Creating the entire stack for every method adds startup overhead.

Does a new BrowserContext share cookies with another test?

No. Independent non-persistent contexts have separate cookies, cache, and browsing state.

Can I run Chromium, Firefox, and WebKit from one test class?

Yes. Parameterize the browser choice or run the suite against each Playwright engine when cross-browser coverage is required.

When should I use TestNG assertions instead of Playwright assertions?

Use Playwright web-first assertions for browser state that may change while the page settles. Use TestNG assertions for values you have already retrieved from the Playwright API or for non-browser fixture checks.

Why did a dependency upgrade break CI?

The new Playwright version may expect different browser binaries or system libraries. Resolve the new dependency and rerun the browser installation step, including OS dependencies on Linux runners.