Ensuring Thread Safety in Parallel Test Execution with Selenium, Cucumber, and Java
🔒 What Is Thread Safety in Selenium Automation?
When multiple threads (tests or scenarios) run simultaneously, they must not interfere with each other’s data or browser sessions.
⚠️ Example: Common Problem Without Thread Safety
If your WebDriver is declared as:
public static WebDriver driver;
and you run tests in parallel, all tests share the same driver object — leading to several issues such as:
1️⃣ Browser windows closing unexpectedly
2️⃣ Wrong data being accessed by other tests
3️⃣ Screenshots captured from the wrong browser
4️⃣ Flaky or inconsistent test results
5️⃣ One test closing the browser while another is still running
6️⃣ Tests opening URLs in the same browser window
7️⃣ Random “Session not found” errors
➡️ Without thread safety, your parallel runs become unstable and unreliable.
💡 How To Achieve Thread Safety
We can achieve thread safety using the ThreadLocal class in our automation framework.
🧰 What Is ThreadLocal?
ThreadLocal provides each thread with its own copy of a variable.
So, if you store your WebDriver in a ThreadLocal, each parallel test thread gets its own driver instance.
🚀 Why Thread Safety Matters in Automation Frameworks
When using Cucumber + TestNG/JUnit with parallel execution enabled, multiple tests or scenarios run at the same time.
Each scenario must have its own WebDriver instance to ensure:
✅ Browsers don’t conflict
✅ Reliable parallel execution
✅ Independent test data and sessions
✅ Stable and reproducible test results
✅ Easy scalability on Selenium Grid or cloud platforms
🧩 Step-by-Step Implementation
1️⃣ Create a DriverManager Class
package driver;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
public class DriverManager {
// ThreadLocal variable to store WebDriver instance per thread
private static ThreadLocal<WebDriver> tlDriver = new ThreadLocal<>();
public static void setDriver(String browser) {
if (browser.equalsIgnoreCase("chrome")) {
ChromeOptions options = new ChromeOptions();
options.addArguments("--start-maximized");
tlDriver.set(new ChromeDriver(options));
}
// You can add Firefox, Edge, etc.
}
// Get the driver for current thread
public static WebDriver getDriver() {
return tlDriver.get();
}
// Quit driver for current thread
public static void quitDriver() {
if (tlDriver.get() != null) {
tlDriver.get().quit();
tlDriver.remove(); // Important: remove reference to prevent memory leak
}
}
}
2️⃣ Use DriverManager in Your Hooks Class (Cucumber)
package hooks;
import io.cucumber.java.After;
import io.cucumber.java.Before;
import driver.DriverManager;
public class Hooks {
@Before
public void setUp() {
// You can read browser name from config or Cucumber tag
DriverManager.setDriver("chrome");
DriverManager.getDriver().get("https://example.com");
}
@After
public void tearDown() {
DriverManager.quitDriver();
}
}
✅ This ensures that:
Before each scenario, a new driver is created for that thread.
After each scenario, the driver is quit and cleaned up safely.
3️⃣ Use It in Your Page Objects or Step Definitions
Instead of passing the driver around manually:
WebDriver driver = DriverManager.getDriver();
driver.findElement(By.id("username")).sendKeys("admin");
or in your Page Object class:
public class LoginPage {
WebDriver driver = DriverManager.getDriver();
public void enterUsername(String user) {
driver.findElement(By.id("username")).sendKeys(user);
}
}
✅ This way, every thread automatically uses its own isolated WebDriver.
4️⃣ Enable Parallel Execution in Cucumber
In your TestNG runner or JUnit runner, enable parallel execution:
@CucumberOptions(
features = "src/test/resources/features",
glue = {"stepdefs", "hooks"},
plugin = {"pretty", "html:target/cucumber-report.html"}
)
public class TestRunner extends AbstractTestNGCucumberTests {
@Override
@DataProvider(parallel = true)
public Object[][] scenarios() {
return super.scenarios();
}
}
💡 The parallel = true makes each scenario run in its own thread.
Because your WebDriver is thread-local, there’s no conflict.
⚠️ Common Mistakes to Avoid
❌ Declaring WebDriver driver as a static variable → causes cross-thread conflicts
❌ Not calling tlDriver.remove() → memory leaks
❌ Using the same test data file for write operations → need thread-safe data access
❌ Mixing implicit and explicit waits globally across threads
🧩 Optional: Thread-Safe Utilities
If you have helper classes like WaitUtils, make them thread-safe too by always using the current driver:
public class WaitUtils {
public static WebElement waitForVisible(By locator) {
WebDriver driver = DriverManager.getDriver();
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
return wait.until(ExpectedConditions.visibilityOfElementLocated(locator));
}
}
✅ Final Takeaway:
Thread safety ensures each test thread runs in isolation with its own browser instance.
Using ThreadLocal makes your Selenium + Cucumber framework stable, scalable, and ready for parallel execution.
Comments
Post a Comment