If you have 100 tests and each takes 30 seconds, running them one by one takes 50 minutes. Run them in parallel with 5 threads? 10 minutes. Parallel execution is how real teams make their nightly regression suite finish before morning stand-up.
TestNG lets you parallelize at four levels. Each level determines what gets its own thread:
| Level | What Gets Its Own Thread | Best For |
|---|---|---|
| parallel="methods" | Each @Test method | Many independent tests in one class |
| parallel="classes" | Each test class | Multiple test classes that are independent |
| parallel="tests" | Each <test> tag in XML | Running Banking and Shopping modules simultaneously |
| parallel="instances" | Each test class instance | Multiple instances of the same class with different data |
<!-- Run each <test> tag in its own thread -->
<suite name="Parallel Suite" parallel="tests" thread-count="2">
<test name="Banking"> <!-- Thread 1 -->
<classes>
<class name="com.autopractice.tests.BankingLoginTest" />
<class name="com.autopractice.tests.BankingDashboardTest" />
</classes>
</test>
<test name="Shopping"> <!-- Thread 2 -->
<classes>
<class name="com.autopractice.tests.ShoppingCartTest" />
<class name="com.autopractice.tests.ShoppingCheckoutTest" />
</classes>
</test>
</suite><!-- Run each CLASS in its own thread -->
<suite name="Class Parallel" parallel="classes" thread-count="4">
<test name="All Tests">
<classes>
<class name="com.autopractice.tests.BankingLoginTest" />
<class name="com.autopractice.tests.BankingDashboardTest" />
<class name="com.autopractice.tests.ShoppingCartTest" />
<class name="com.autopractice.tests.ShoppingCheckoutTest" />
</classes>
</test>
</suite>Here is the biggest trap. If you have a WebDriver field in your base class and 4 threads are running, all 4 threads share the same WebDriver variable. Thread 1 opens Chrome for a banking test. Thread 2 overwrites the variable with a new Chrome for shopping. Now Thread 1 is controlling Thread 2's browser. Tests fail randomly, and you spend days debugging.
NEVER use a regular class field for WebDriver when running in parallel. Every single parallel execution bug I have debugged came down to this. Use ThreadLocal<WebDriver> — it gives each thread its own isolated copy of the variable.
ThreadLocal is a Java class that stores a separate value for each thread. When Thread 1 calls threadLocal.set(driver1) and Thread 2 calls threadLocal.set(driver2), they do not interfere with each other.
public class BaseTest {
private static ThreadLocal<WebDriver> driverThread =
new ThreadLocal<>();
private static ThreadLocal<WebDriverWait> waitThread =
new ThreadLocal<>();
@BeforeMethod
public void setUp() {
WebDriver driver = new ChromeDriver();
driver.manage().window().maximize();
driverThread.set(driver); // Store in ThreadLocal
waitThread.set(new WebDriverWait(driver, Duration.ofSeconds(10)));
}
public WebDriver getDriver() {
return driverThread.get(); // Get THIS thread's driver
}
public WebDriverWait getWait() {
return waitThread.get();
}
@AfterMethod
public void tearDown() {
WebDriver driver = driverThread.get();
if (driver != null) {
driver.quit();
driverThread.remove(); // Prevent memory leaks
waitThread.remove();
}
}
}// Test class uses getDriver() instead of direct field access
public class BankingLoginTest extends BaseTest {
@Test(groups = {"smoke"})
public void testSuccessfulLogin() {
getDriver().get("https://www.testerrank.com/banking");
getDriver().findElement(By.id("username")).sendKeys("admin");
getDriver().findElement(By.id("password")).sendKeys("admin123");
getDriver().findElement(
By.cssSelector("button[type='submit']")).click();
getWait().until(ExpectedConditions.urlContains("/dashboard"));
Assert.assertTrue(
getDriver().getCurrentUrl().contains("/dashboard")
);
}
}Always call threadLocal.remove() in @AfterMethod. Without it, the thread goes back to the pool still holding a reference to the closed driver. The next test on that thread gets a stale reference. This causes "session not created" or "invalid session id" errors that are extremely hard to debug.
Each thread launches a browser. A Chrome browser uses 200-400 MB of RAM. So thread-count depends on your machine:
Do not set thread-count higher than your machine can handle. If you set 10 threads on a 4 GB laptop, Chrome will eat all your RAM, the OS will start swapping to disk, and everything will be slower than running sequentially. Start low, increase gradually.
Q: How do you achieve parallel execution in TestNG? How do you handle thread safety?
A: Parallel execution is configured in testng.xml using the parallel and thread-count attributes. You can run in parallel at four levels: methods, classes, tests, or instances. For thread safety, I use ThreadLocal<WebDriver> to ensure each thread has its own browser. In BaseTest, @BeforeMethod creates a new ChromeDriver and stores it in ThreadLocal. Test classes call getDriver() to get their thread's browser. @AfterMethod calls driver.quit() and threadLocal.remove() to prevent memory leaks.
Key Point: Use parallel + thread-count in testng.xml for parallel execution. Use ThreadLocal<WebDriver> for thread safety. Always remove() in @AfterMethod.