---
title: Run Selenium tests in parallel | TestingBot
description: A suite of 200 UI tests that each take 20 seconds needs over an hour
  on one thread. Spread across 10 browsers it needs a few minutes. TestNG makes the…
source_url:
  html: https://testingbot.com/resources/courses/selenium-testng/parallel-execution
  md: https://testingbot.com/resources/courses/selenium-testng/parallel-execution.md
---

Course

[Selenium Java Tutorial with TestNG](https://testingbot.com/resources/courses/selenium-testng)

Chapter 8 of 11

1. [1 Selenium and TestNG: how they fit together](https://testingbot.com/resources/courses/selenium-testng)
2. [2 Set up a Maven project for Selenium](https://testingbot.com/resources/courses/selenium-testng/project-setup)
3. [3 Your first Selenium test with TestNG](https://testingbot.com/resources/courses/selenium-testng/first-test)
4. [4 TestNG annotations and the driver lifecycle](https://testingbot.com/resources/courses/selenium-testng/annotations)
5. [5 Hard assertions vs SoftAssert](https://testingbot.com/resources/courses/selenium-testng/assertions)
6. [6 Data-driven tests with DataProvider](https://testingbot.com/resources/courses/selenium-testng/data-providers)
7. [7 Suites, groups and parameters in testng.xml](https://testingbot.com/resources/courses/selenium-testng/testng-xml)
8. [8 Run Selenium tests in parallel](https://testingbot.com/resources/courses/selenium-testng/parallel-execution)
9. [9 Listeners, screenshots and reports](https://testingbot.com/resources/courses/selenium-testng/listeners-and-reports)
10. [10 Run the suite on a Selenium grid](https://testingbot.com/resources/courses/selenium-testng/run-on-a-selenium-grid)
11. [11 Knowledge check](https://testingbot.com/resources/courses/selenium-testng/knowledge-check)

[Exit course](https://testingbot.com/resources/courses)

 Chapter 8 of 11 Intermediate 8 min read 3 quick checks Updated Nov 2026 

# Run Selenium tests in parallel
On this page
1. [The parallel attribute](https://testingbot.com#the-parallel-attribute)
2. [Why the chapter 3 BaseTest breaks](https://testingbot.com#why-the-chapter-3-basetest-breaks)
3. [One driver per thread with ThreadLocal](https://testingbot.com#one-driver-per-thread-with-threadlocal)
4. [Try it](https://testingbot.com#try-it)
5. [Everything else that must not be shared](https://testingbot.com#everything-else-that-must-not-be-shared)
6. [Choosing thread-count](https://testingbot.com#choosing-thread-count)
7. [Parallel by tests for cross-browser runs](https://testingbot.com#parallel-by-tests-for-cross-browser-runs)

A suite of 200 UI tests that each take 20 seconds needs over an hour on one thread. Spread across 10 browsers it needs a few minutes. TestNG makes the threading part a one-line change in `testng.xml`; making your _tests_ safe to run on those threads is the real work, and it comes down to one rule: **every thread gets its own WebDriver, and nothing else mutable is shared.**

## The parallel attribute[#](https://testingbot.com#the-parallel-attribute)

```xml
<suite name="Course suite" parallel="methods" thread-count="3">
```

`parallel` decides what TestNG is allowed to run at the same time:

| Value | Runs concurrently | Typical use |
| --- | --- | --- |
| `methods` | individual `@Test` methods, even within one class | maximum parallelism; needs a driver per thread |
| `classes` | whole classes; methods within a class run on the same thread, in sequence | classes that share a per-class browser (`@BeforeClass`) |
| `tests` | `<test>` blocks; everything inside one block runs on one thread | cross-browser suites with one `<test>` per browser |
| `instances` | instances created by a `@Factory`; methods of one instance share a thread | data-driven classes built with factories |
| `none` | nothing (the default) | |

`thread-count` is the size of the thread pool (default 5). It caps how many things run at once regardless of how many are eligible. Parallel data providers (chapter 5) use a separate pool sized by `data-provider-thread-count` (default 10), so a suite with `thread-count="3"` and a parallel data provider can have more than three browsers open at once. Size both with that in mind.

Methods connected by `dependsOnMethods` are still run in dependency order; TestNG holds the dependent method back until its dependency has finished.

With `parallel="classes"` and `thread-count="4"`, how do the methods inside one class run?

 In sequence, on the same thread In parallel, up to four at a time Each on a new thread In a random order on random threads

Class-level parallelism runs whole classes concurrently; methods within a class stay on one thread, one after another.

## Why the chapter 3 BaseTest breaks[#](https://testingbot.com#why-the-chapter-3-basetest-breaks)

Our `BaseTest` stores the driver in an instance field. With `parallel="methods"`, TestNG runs several methods of the _same instance_ on different threads at once. Thread A's `@BeforeMethod` sets `driver`, thread B's `@BeforeMethod` overwrites it, both tests now drive B's browser, and whichever finishes first quits it under the other. The symptoms look random: `NoSuchSessionException`, elements typed into the wrong page, assertions that fail on another test's data.

Making the field `static` makes it worse: then every instance of every class shares it.

## One driver per thread with ThreadLocal[#](https://testingbot.com#one-driver-per-thread-with-threadlocal)

A `ThreadLocal` holds a separate value for each thread. Each test thread sets and reads its own driver, and never sees another thread's:

```java
package com.example;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxOptions;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Optional;
import org.testng.annotations.Parameters;

public abstract class BaseTest {

    private static final ThreadLocal<WebDriver> DRIVER = new ThreadLocal<>();

    public static WebDriver driver() {
        return DRIVER.get();
    }

    @BeforeMethod(alwaysRun = true)
    @Parameters("browser")
    public void startDriver(@Optional("chrome") String browser) {
        DRIVER.set(createDriver(browser));
    }

    @AfterMethod(alwaysRun = true)
    public void stopDriver() {
        WebDriver driver = DRIVER.get();
        if (driver != null) {
            try {
                driver.quit();
            } finally {
                DRIVER.remove();
            }
        }
    }

    private static WebDriver createDriver(String browser) {
        boolean headless = Boolean.getBoolean("headless");
        if ("firefox".equals(browser)) {
            FirefoxOptions options = new FirefoxOptions();
            if (headless) options.addArguments("-headless");
            return new FirefoxDriver(options);
        }
        ChromeOptions options = new ChromeOptions();
        if (headless) options.addArguments("--headless=new", "--window-size=1920,1080");
        return new ChromeDriver(options);
    }
}
```

Three details make this robust:

1. **The `ThreadLocal` is `static`, the driver inside it is per thread.** One `ThreadLocal` object for the whole JVM, one value per thread.
2. **`@BeforeMethod` and `@AfterMethod` run on the same thread as the test method.** That is what guarantees the test reads the driver its own setup created. (TestNG runs a method's configuration methods on the thread that runs the method.)
3. **`DRIVER.remove()` after `quit()`.** TestNG reuses pool threads. Without `remove()`, a thread keeps pointing at the previous test's dead session. If the next setup on that thread fails before it calls `set()`, teardown and listeners act on that stale driver and bury the real setup error under a `NoSuchSessionException`.

`driver()` is now `public static`, which the screenshot listener in the next chapter will need. Because every test already calls `driver()` (chapter 3), none of the test classes change. If you kept a `waitUpTo` helper, have it call `driver()` instead of reading a field.

To fix random parallel failures, a teammate makes the driver field `static`. What happens?

 It fixes the problem, because static fields are thread-safe It makes it worse: every instance of every class now shares one driver Nothing changes

Static means one value for the whole JVM. A per-thread driver needs a `ThreadLocal`, which is static itself but holds one value per thread.

## Try it[#](https://testingbot.com#try-it)

Update `testng.xml`:

```xml
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Course suite" parallel="methods" thread-count="3" data-provider-thread-count="2">
  <test name="Chrome">
    <parameter name="browser" value="chrome"/>
    <classes>
      <class name="com.example.FirstTest"/>
      <class name="com.example.FormDefaultsTest"/>
      <class name="com.example.WebFormDataTest"/>
    </classes>
  </test>
</suite>
```

Switch the `textInputs` provider from chapter 5 to `parallel = true`, then run `mvn test -Dheadless=true`. Several Chrome processes start at once and the total time drops. Add `System.out.println(Thread.currentThread().getName())` to a test if you want to watch the threads.

## Everything else that must not be shared[#](https://testingbot.com#everything-else-that-must-not-be-shared)

The driver is the obvious shared resource. These are the less obvious ones that cause the "only fails in parallel" bugs:

- **`SoftAssert` fields.** Create one per test method (chapter 4).
- **Page objects stored in fields.** A page object wraps a driver. Create it inside the test from `driver()`, or store it in a `ThreadLocal` too.
- **Test data.** Two threads logging in as the same user can log each other out, and two threads editing the same record race each other. Give each test its own data, for example a user created through an API in setup or a unique suffix such as `"user-" + UUID.randomUUID()`.
- **Static helpers with state.** A static `WebDriverWait`, `SimpleDateFormat` or counter is shared by every thread.
- **Files.** Screenshots or downloads written to a fixed file name overwrite each other; include the test name and a timestamp in the path.

Two parallel tests log in as the same user and one keeps getting logged out. What is the fix?

 Lower `thread-count` to 1 for those tests only Add a `Thread.sleep` before login Store the user in a `ThreadLocal` Give each test its own user, for example created through an API in setup or with a unique suffix

Shared server-side data is not protected by thread-local storage. Each test needs its own record.

## Choosing thread-count[#](https://testingbot.com#choosing-thread-count)

More threads is not always faster.

- **Locally** , each Chrome instance needs CPU and memory. Past a point, extra browsers make every test slower, explicit waits begin to time out, and the suite becomes flaky instead of fast. Start near the number of CPU cores and measure.
- **On a remote grid** , the browsers run elsewhere, so your machine only coordinates. The limit becomes the number of parallel sessions your grid account allows. On TestingBot, the docs state that sessions beyond your plan's limit are queued (for up to 6 minutes) and start as soon as a slot frees up, so setting `thread-count` slightly above your limit is harmless, but far above it only produces queue time. Our [parallel testing overview](https://testingbot.com/features/parallel-testing) and the [parallel calculator](https://testingbot.com/support/parallel-calculator) help estimate how many slots a suite actually needs.

## Parallel by tests for cross-browser runs[#](https://testingbot.com#parallel-by-tests-for-cross-browser-runs)

`parallel="tests"` is the natural fit when each `<test>` block targets a different browser:

```xml
<suite name="Cross-browser" parallel="tests" thread-count="2">
  <test name="Chrome">
    <parameter name="browser" value="chrome"/>
    <packages><package name="com.example"/></packages>
  </test>
  <test name="Firefox">
    <parameter name="browser" value="firefox"/>
    <packages><package name="com.example"/></packages>
  </test>
</suite>
```

Chrome and Firefox run side by side, while the tests inside each block run one after another. Our `ThreadLocal` base class works unchanged in every mode, which is exactly why it is worth setting up even before you need parallelism.

If your team uses JUnit 5 rather than TestNG, the same driver-per-thread idea applies; the [parallel JUnit example](https://testingbot.com/support/web-automate/selenium/java/parallel-junit) shows it.

[Previous · Chapter 7 Suites, groups and parameters in testng.xml](https://testingbot.com/resources/courses/selenium-testng/testng-xml) [Next · Chapter 9 Listeners, screenshots and reports](https://testingbot.com/resources/courses/selenium-testng/listeners-and-reports)

On this page

1. [The parallel attribute](https://testingbot.com#the-parallel-attribute)
2. [Why the chapter 3 BaseTest breaks](https://testingbot.com#why-the-chapter-3-basetest-breaks)
3. [One driver per thread with ThreadLocal](https://testingbot.com#one-driver-per-thread-with-threadlocal)
4. [Try it](https://testingbot.com#try-it)
5. [Everything else that must not be shared](https://testingbot.com#everything-else-that-must-not-be-shared)
6. [Choosing thread-count](https://testingbot.com#choosing-thread-count)
7. [Parallel by tests for cross-browser runs](https://testingbot.com#parallel-by-tests-for-cross-browser-runs)

Certification

This course has a free, hands-on certificate.

[How it works](https://testingbot.com/resources/courses/selenium-testng/certification)
