Skip to main content
SeleniumDecoded

Parallel Execution

Run Selenium tests in parallel to dramatically reduce test execution time.

Selenium 3 & 4 Stable

Running tests in parallel can reduce execution time from hours to minutes. This guide covers parallel execution strategies for each language and framework.

Why Parallel Execution?

Sequential (10 tests × 1 min)Parallel (5 threads)
10 minutes total~2 minutes total

Thread-Safe WebDriver

The key to parallel execution is ensuring each test has its own WebDriver instance:

Thread-Safe Driver Factory
Selenium 3 & 4 Stable
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
public class DriverFactory {
// ThreadLocal ensures each thread gets its own driver
private static ThreadLocal<WebDriver> driver = new ThreadLocal<>();
public static WebDriver getDriver() {
if (driver.get() == null) {
driver.set(new ChromeDriver());
}
return driver.get();
}
public static void quitDriver() {
if (driver.get() != null) {
driver.get().quit();
driver.remove(); // Important: remove from ThreadLocal
}
}
}
import threading
from selenium import webdriver
class DriverFactory:
_driver = threading.local()
@classmethod
def get_driver(cls):
if not hasattr(cls._driver, 'instance') or cls._driver.instance is None:
cls._driver.instance = webdriver.Chrome()
return cls._driver.instance
@classmethod
def quit_driver(cls):
if hasattr(cls._driver, 'instance') and cls._driver.instance:
cls._driver.instance.quit()
cls._driver.instance = None
utils/driverFactory.js
// JavaScript with async/await handles this naturally
// Each test file runs in its own context with Mocha parallel mode
const { Builder } = require('selenium-webdriver');
async function createDriver() {
// Each call creates a new driver
return await new Builder().forBrowser('chrome').build();
}
module.exports = { createDriver };
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using System.Threading;
public class DriverFactory
{
// ThreadLocal ensures each thread gets its own driver
private static ThreadLocal<IWebDriver> _driver =
new ThreadLocal<IWebDriver>();
public static IWebDriver GetDriver()
{
if (_driver.Value == null)
{
_driver.Value = new ChromeDriver();
}
return _driver.Value;
}
public static void QuitDriver()
{
if (_driver.Value != null)
{
_driver.Value.Quit();
_driver.Value = null;
}
}
}

TestNG Parallel Execution (Java)

XML Configuration

testng.xml
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<!-- Run test methods in parallel -->
<suite name="Parallel Suite" parallel="methods" thread-count="5">
<test name="All Tests">
<classes>
<class name="tests.LoginTest"/>
<class name="tests.SearchTest"/>
<class name="tests.CheckoutTest"/>
</classes>
</test>
</suite>
<!-- Or run test classes in parallel -->
<suite name="Parallel Suite" parallel="classes" thread-count="3">
<test name="All Tests">
<classes>
<class name="tests.LoginTest"/>
<class name="tests.SearchTest"/>
<class name="tests.CheckoutTest"/>
</classes>
</test>
</suite>
<!-- Or run tests (groups of classes) in parallel -->
<suite name="Parallel Suite" parallel="tests" thread-count="2">
<test name="Smoke Tests">
<classes>
<class name="tests.SmokeTest"/>
</classes>
</test>
<test name="Regression Tests">
<classes>
<class name="tests.RegressionTest"/>
</classes>
</test>
</suite>

Base Test with ThreadLocal

import org.openqa.selenium.WebDriver;
import org.testng.annotations.*;
public class BaseTest {
protected WebDriver driver;
@BeforeMethod
public void setup() {
driver = DriverFactory.getDriver();
driver.manage().window().maximize();
}
@AfterMethod
public void teardown() {
DriverFactory.quitDriver();
}
}
// Tests extend BaseTest
public class LoginTest extends BaseTest {
@Test
public void testLogin() {
driver.get("https://example.com/login");
// Each thread has its own driver
}
@Test
public void testLogout() {
driver.get("https://example.com");
// Independent of other tests
}
}

pytest-xdist (Python)

Installation and Usage

Terminal window
pip install pytest-xdist
# Run with 4 workers
pytest -n 4
# Auto-detect CPU count
pytest -n auto
# Distribute tests across workers
pytest -n 4 --dist=loadfile # Same file on same worker
pytest -n 4 --dist=loadscope # Same class/module on same worker

Thread-Safe Fixtures

conftest.py
import pytest
from selenium import webdriver
@pytest.fixture(scope="function")
def driver():
"""Each test gets its own driver instance."""
driver = webdriver.Chrome()
driver.maximize_window()
yield driver
driver.quit()
# For session-scoped fixtures with parallel, use file locking
@pytest.fixture(scope="session")
def shared_resource(tmp_path_factory, worker_id):
if worker_id == "master":
# Not running in parallel
return setup_resource()
# Running in parallel - use file locking
root_tmp_dir = tmp_path_factory.getbasetemp().parent
lock_file = root_tmp_dir / "resource.lock"
with FileLock(str(lock_file)):
return setup_resource()

JUnit 5 Parallel (Java)

Configuration

src/test/resources/junit-platform.properties
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.mode.default=concurrent
junit.jupiter.execution.parallel.mode.classes.default=concurrent
junit.jupiter.execution.parallel.config.strategy=fixed
junit.jupiter.execution.parallel.config.fixed.parallelism=4

Thread-Safe Tests

import org.junit.jupiter.api.*;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
@Execution(ExecutionMode.CONCURRENT) // Enable parallel for this class
class ParallelTest {
@BeforeEach
void setup() {
// Each test gets its own driver via ThreadLocal
DriverFactory.getDriver().manage().window().maximize();
}
@Test
void testOne() {
WebDriver driver = DriverFactory.getDriver();
driver.get("https://example.com/page1");
}
@Test
void testTwo() {
WebDriver driver = DriverFactory.getDriver();
driver.get("https://example.com/page2");
}
@AfterEach
void teardown() {
DriverFactory.quitDriver();
}
}

Mocha Parallel (JavaScript)

Configuration

.mocharc.js
module.exports = {
parallel: true,
jobs: 4, // Number of parallel jobs
timeout: 30000
};
// Or via command line
// npx mocha --parallel --jobs 4

Test Structure

// Each test file runs in its own process
const { Builder, By } = require('selenium-webdriver');
describe('Login Tests', function() {
let driver;
beforeEach(async function() {
driver = await new Builder().forBrowser('chrome').build();
});
afterEach(async function() {
await driver.quit();
});
it('should login successfully', async function() {
await driver.get('https://example.com/login');
// Test logic
});
});

NUnit Parallel (C#)

Assembly-Level Parallelism

// In AssemblyInfo.cs or any file
[assembly: Parallelizable(ParallelScope.Fixtures)]
// Or per class
[TestFixture]
[Parallelizable(ParallelScope.All)]
public class ParallelTests
{
private IWebDriver driver;
[SetUp]
public void Setup()
{
driver = DriverFactory.GetDriver();
}
[Test]
public void TestOne()
{
driver.Navigate().GoToUrl("https://example.com");
}
[Test]
public void TestTwo()
{
driver.Navigate().GoToUrl("https://example.com/page2");
}
[TearDown]
public void Teardown()
{
DriverFactory.QuitDriver();
}
}

Best Practices

1. Test Independence

# BAD - Tests depend on each other
def test_create_user(driver):
# Creates user "john"
pass
def test_login_as_user(driver):
# Assumes "john" exists - fails in parallel!
pass
# GOOD - Each test is independent
def test_login(driver):
user = create_test_user() # Create fresh data
login(driver, user)
delete_test_user(user) # Cleanup

2. Unique Test Data

// Generate unique data per test
String uniqueEmail = "user_" + UUID.randomUUID() + "@test.com";
String uniqueUsername = "user_" + System.currentTimeMillis();

3. Resource Isolation

# Each test uses its own database record
@pytest.fixture
def test_product(db):
product = db.create_product(name=f"Product-{uuid.uuid4()}")
yield product
db.delete_product(product.id)

Performance Comparison

Approach100 TestsComplexity
Sequential50 minLow
4 Parallel13 minMedium
8 Parallel7 minMedium
Grid (20 nodes)3 minHigh

Common Issues

IssueSolution
Port conflictsUse dynamic ports or grid
Shared stateUse ThreadLocal/fixtures
Flaky testsEnsure test independence
Resource limitsLimit thread count

Next Steps