Skip to main content
SeleniumDecoded

Parallel Execution

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

Selenium 3 & 4 Stable Verified against Selenium 4.48.0

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

Related lessons