If you’ve worked with Playwright, you already know it’s one of the most powerful modern automation tools for browser testing. Developed by Microsoft, Playwright supports cross-browser automation for Chromium, Firefox, and WebKit, and it’s gaining huge traction for end-to-end web testing.
While Playwright shines with speed, reliability, and cross-browser support, many Java developers quickly notice one major limitation when using it standalone:
Playwright (by itself) doesn’t support running tests in parallel out of the box.
Note: Playwright’s Node.js library has its own runner, but not in Playwright Java
So, when you’re using Playwright with Java, you need a test runner such as TestNG or JUnit to manage:
- Test execution lifecycle
- Parallel runs
- Assertions and reporting
- Parameterization and grouping
Among these, TestNG is often the preferred choice in Java automation projects because of its easy configuration for parallel execution, flexible test grouping, and suite-level control.
So, in this article, we’ll use the TestNG features to run the Playwright tests in parallel. If you want to learn about TestNG, please go to our TestNG tutorial Page.
- Importing TestNG dependency
- Writing Playwright Tests
- Running the tests in Sequential Order
- Running the Playwright tests in Parallel
Importing TestNG dependency
Add the latest Maven TestNG dependency to your Maven or Gradle project.
<!-- https://mvnrepository.com/artifact/org.testng/testng -->
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.11.0</version>
</dependency>
Below is our full pom.xml file
<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 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>PlaywrightDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<name>Archetype - PlaywrightDemo</name>
<url>http://maven.apache.org</url>
<dependencies>
<!-- https://mvnrepository.com/artifact/com.microsoft.playwright/playwright -->
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>1.53.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.testng/testng -->
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.11.0</version>
</dependency>
</dependencies>
</project>
Writing Playwright Tests
Now, let’s set up a few tests inside a class called “CodekruTest.java”. Instead of writing the tests inside a main() method, which isn’t how real-world projects usually work, we’ll use TestNG annotations. Each test case will be marked with a @Test annotation, making it easy to organize and run them individually.
So, first we will run them in sequential, and then will run them in parallel using TestNG.
import com.microsoft.playwright.*;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
public class CodekruTest {
private ThreadLocal<Playwright> playwright = new ThreadLocal<>();
private ThreadLocal<Browser> browser = new ThreadLocal<>();
private ThreadLocal<BrowserContext> context = new ThreadLocal<>();
private ThreadLocal<Page> page = new ThreadLocal<>();
@BeforeMethod(alwaysRun = true)
public void setUp(){
playwright.set(Playwright.create());
browser.set(playwright.get().chromium().
launch(new BrowserType.LaunchOptions().setHeadless(false)));
context.set(browser.get().newContext());
page.set(context.get().newPage());
}
@Test
public void test1() {
page.get().navigate("https://testkru.com/Elements/Buttons");
Locator buttonLocator = page.get().locator("#doubleClickButtonLabel");
System.out.println("Running test1 on Thread Id: " + Thread.currentThread().getId());
String actualButtonText = buttonLocator.innerText();
Assert.assertEquals(actualButtonText,"1) Double-Click on button");
}
@Test
public void test2() {
page.get().navigate("https://testkru.com/Elements/Buttons");
Locator buttonLocator = page.get().locator("#rightClickButtonLabel");
System.out.println("Running test2 on Thread Id: " + Thread.currentThread().getId());
String actualButtonText = buttonLocator.innerText();
Assert.assertEquals(actualButtonText,"2) Right-Click on Button");
}
@Test
public void test3() {
page.get().navigate("https://testkru.com/Elements/Buttons");
Locator buttonLocator = page.get().locator("#leftClickButtonLabel");
System.out.println("Running test3 on Thread Id: " + Thread.currentThread().getId());
String actualButtonText = buttonLocator.innerText();
Assert.assertEquals(actualButtonText,"3) Left-Click on Button");
}
@Test
public void test4() {
page.get().navigate("https://testkru.com/Elements/Buttons");
Locator buttonLocator = page.get().locator("#disabledButtonLabel");
System.out.println("Running test4 on Thread Id: " + Thread.currentThread().getId());
String actualButtonText = buttonLocator.innerText();
Assert.assertEquals(actualButtonText,"4) Disabled Button");
}
@AfterMethod(alwaysRun = true)
public void tearDown() {
context.get().close();
browser.get().close();
playwright.get().close();
}
}
- This class CodekruTest is a TestNG test class that uses Playwright for Java to automate browser actions.
- The @BeforeMethod setup method runs before each test and is responsible for starting Playwright, opening a Chromium browser, creating a new browser context, and opening a fresh page so that each test starts with a clean environment.
- There are four @Test methods. Each test goes to the same webpage and checks a different button. Playwright locators are used to find the buttons, and TestNG Assert.assertEquals checks that the button text is correct.
- In each test, we are printing the “Thread Id” so that we can see whether the tests are running on the same thread or not. If the thread Id is the same for all tests, it means they ran one after another in sequence. If the thread Ids are different, it shows that the tests were executed in parallel.
- After each test, the @AfterMethod teardown method runs to close the browser context, close the browser, and stop Playwright, which cleans up all the resources.
- TestNG annotations like @BeforeMethod, @Test, and @AfterMethod make it easy to control when the setup, tests, and cleanup happen, so everything runs smoothly and independently.
Using ThreadLocal allows this class to safely run multiple tests at the same time on different threads without the browsers interfering with each other, which is useful for parallel execution in TestNG.
Running the tests in Sequential Order
Next, let’s create a testng.xml file at the root of the project that will control the execution of our tests, running them one after the other.
<suite name="codekru">
<test name="PlaywrighDemo" >
<classes>
<class name="CodekruTest" />
</classes>
</test>
</suite>
Project Structure –

Note: Please read this article if you want to learn how to create a testng.xml file.
The XML file above will execute all the tests in the CodekruTest class one after the other in sequence. Here’s what the output looks like when you run it:
Running test1 on Thread Id: 1
Running test2 on Thread Id: 1
Running test3 on Thread Id: 1
Running test4 on Thread Id: 1
As you can see, all the tests ran on the same thread, which means they were executed one after the other in sequence.
Running the Playwright tests in Parallel
Now, let’s look at how to run the tests in parallel. TestNG offers several ways to execute tests simultaneously, but in this example, we’ll run all the test methods in parallel. If we had multiple classes, we could also choose to run the entire classes in parallel instead.
Note: Please read this article, to know different modes of parallelism in TestNG.
To run the cases in parallel, we just need to run the below line with the <suite> tag in testng.xml file.
parallel = "methods" thread-count="4"
This tells TestNG to execute four test methods at the same time, running them in parallel.
Updated testng.xml file –
<suite name="codekru" parallel = "methods" thread-count="4">
<test name="PlaywrighDemo" >
<classes>
<class name="CodekruTest" />
</classes>
</test>
</suite>
Output after running the above XML file –
Running test3 on Thread Id: 20
Running test4 on Thread Id: 21
Running test1 on Thread Id: 18
Running test2 on Thread Id: 19
Here, we can observe that each test has a different “Thread Id,” which means they ran on separate threads and were executed in parallel.
We hope that you have liked the article. If you have any doubts or concerns, please write to us in the comments or mail us at admin@codekru.com.
Related Articles –