Parallel Test Execution Strategies
Learn advanced techniques for parallel test execution to dramatically reduce test suite execution time.
TestNG Parallel Execution
Configuration Levels
<!-- testng.xml -->
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Parallel Suite" parallel="methods" thread-count="5">
<test name="Regression Tests">
<classes>
<class name="com.example.tests.LoginTest"/>
<class name="com.example.tests.CheckoutTest"/>
<class name="com.example.tests.SearchTest"/>
</classes>
</test>
</suite>
Parallel options:
parallel="methods"- Run test methods in parallelparallel="classes"- Run test classes in parallelparallel="tests"- Run<test>tags in parallelparallel="instances"- Run instances in parallel
Thread-Safe WebDriver Management
public class DriverFactory {
private static ThreadLocal<WebDriver> driverThreadLocal = new ThreadLocal<>();
public static synchronized WebDriver getDriver() {
if (driverThreadLocal.get() == null) {
WebDriver driver = createDriver();
driverThreadLocal.set(driver);
}
return driverThreadLocal.get();
}
private static WebDriver createDriver() {
ChromeOptions options = new ChromeOptions();
options.addArguments("--headless");
options.addArguments("--disable-gpu");
options.addArguments("--window-size=1920,1080");
return new ChromeDriver(options);
}
public static synchronized void quitDriver() {
if (driverThreadLocal.get() != null) {
driverThreadLocal.get().quit();
driverThreadLocal.remove();
}
}
}
Base Test Class
public class ParallelTestBase {
protected WebDriver driver;
@BeforeMethod(alwaysRun = true)
public void setUp() {
driver = DriverFactory.getDriver();
}
@AfterMethod(alwaysRun = true)
public void tearDown() {
DriverFactory.quitDriver();
}
}
Advanced Parallel Patterns
Data Provider Parallelization
@Test(dataProvider = "searchTerms", dataProviderClass = TestDataProvider.class)
public void testSearchParallel(String searchTerm, String expectedResult) {
driver.get("https://example.com/search");
driver.findElement(By.id("search-input")).sendKeys(searchTerm);
driver.findElement(By.id("search-btn")).click();
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement result = wait.until(
ExpectedConditions.presenceOfElementLocated(By.className("result-title"))
);
assertTrue(result.getText().contains(expectedResult));
}
@DataProvider(name = "searchTerms", parallel = true)
public Object[][] getSearchTerms() {
return new Object[][] {
{"laptop", "Laptop"},
{"phone", "Phone"},
{"tablet", "Tablet"},
{"headphones", "Headphones"},
{"monitor", "Monitor"}
};
}
Dynamic Thread Allocation
@Test
public void testWithDynamicThreads() {
int processors = Runtime.getRuntime().availableProcessors();
int threadCount = Math.max(2, processors - 1);
System.setProperty("dataproviderthreadcount", String.valueOf(threadCount));
// TestNG will use dynamic thread count
}
Selenium Grid Integration
Grid Hub Setup
# Start Hub
java -jar selenium-server-4.15.0.jar hub
# Start Node
java -jar selenium-server-4.15.0.jar node \
--detect-drivers true \
--max-sessions 5 \
--session-timeout 300
RemoteWebDriver Configuration
public class GridDriverFactory {
private static final String GRID_URL = "http://localhost:4444";
private static ThreadLocal<WebDriver> driver = new ThreadLocal<>();
public static WebDriver getDriver(String browser, String platform) {
DesiredCapabilities capabilities = new DesiredCapabilities();
switch (browser.toLowerCase()) {
case "chrome":
capabilities.setBrowserName("chrome");
ChromeOptions chromeOptions = new ChromeOptions();
chromeOptions.addArguments("--headless");
capabilities.setCapability(ChromeOptions.CAPABILITY, chromeOptions);
break;
case "firefox":
capabilities.setBrowserName("firefox");
FirefoxOptions firefoxOptions = new FirefoxOptions();
firefoxOptions.addArguments("--headless");
capabilities.setCapability(FirefoxOptions.FIREFOX_OPTIONS, firefoxOptions);
break;
}
capabilities.setPlatform(Platform.fromString(platform));
try {
WebDriver remoteDriver = new RemoteWebDriver(
new URL(GRID_URL), capabilities
);
driver.set(remoteDriver);
return remoteDriver;
} catch (MalformedURLException e) {
throw new RuntimeException("Invalid Grid URL", e);
}
}
public static void quitDriver() {
if (driver.get() != null) {
driver.get().quit();
driver.remove();
}
}
}
Cross-Browser Parallel Testing
<suite name="Cross Browser Suite" parallel="tests" thread-count="3">
<test name="Chrome Tests">
<parameter name="browser" value="chrome"/>
<parameter name="platform" value="LINUX"/>
<classes>
<class name="com.example.tests.RegressionTest"/>
</classes>
</test>
<test name="Firefox Tests">
<parameter name="browser" value="firefox"/>
<parameter name="platform" value="LINUX"/>
<classes>
<class name="com.example.tests.RegressionTest"/>
</classes>
</test>
<test name="Edge Tests">
<parameter name="browser" value="edge"/>
<parameter name="platform" value="WINDOWS"/>
<classes>
<class name="com.example.tests.RegressionTest"/>
</classes>
</test>
</suite>
public class RegressionTest {
private WebDriver driver;
@Parameters({"browser", "platform"})
@BeforeMethod
public void setUp(String browser, String platform) {
driver = GridDriverFactory.getDriver(browser, platform);
}
@Test
public void testLogin() {
driver.get("https://example.com/login");
// Test logic...
}
@AfterMethod
public void tearDown() {
GridDriverFactory.quitDriver();
}
}
Resource Management
Connection Pool
public class DriverPool {
private static final int MAX_POOL_SIZE = 10;
private static final BlockingQueue<WebDriver> driverPool =
new LinkedBlockingQueue<>(MAX_POOL_SIZE);
static {
// Pre-initialize pool
for (int i = 0; i < MAX_POOL_SIZE; i++) {
ChromeOptions options = new ChromeOptions();
options.addArguments("--headless");
driverPool.offer(new ChromeDriver(options));
}
}
public static WebDriver borrowDriver() throws InterruptedException {
return driverPool.take(); // Blocks if pool is empty
}
public static void returnDriver(WebDriver driver) {
if (driver != null) {
// Clean cookies/storage before returning
driver.manage().deleteAllCookies();
try {
driver.get("about:blank");
driverPool.offer(driver);
} catch (Exception e) {
driver.quit();
// Create new driver to replace
driverPool.offer(new ChromeDriver());
}
}
}
public static void shutdownPool() {
driverPool.forEach(WebDriver::quit);
driverPool.clear();
}
}
Test with Pool
public class PooledTest {
private WebDriver driver;
@BeforeMethod
public void setUp() throws InterruptedException {
driver = DriverPool.borrowDriver();
}
@Test
public void testWithPooledDriver() {
driver.get("https://example.com");
// Test logic...
}
@AfterMethod
public void tearDown() {
DriverPool.returnDriver(driver);
}
}
Execution Optimization
Test Prioritization
@Test(priority = 1)
public void testCriticalUserFlow() {
// High priority test
}
@Test(priority = 2, dependsOnMethods = "testCriticalUserFlow")
public void testSecondaryFlow() {
// Lower priority test
}
Intelligent Test Selection
public class TestSelector {
private static final Set<String> SMOKE_TESTS = Set.of(
"testLogin", "testHomePage", "testCheckout"
);
@BeforeMethod
public void checkTestExecution(Method method) {
String profile = System.getProperty("test.profile", "full");
if ("smoke".equals(profile) &&
!SMOKE_TESTS.contains(method.getName())) {
throw new SkipException("Not a smoke test");
}
}
}
Timeout Management
@Test(timeOut = 30000) // 30 seconds max
public void testWithTimeout() {
driver.get("https://example.com");
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.until(ExpectedConditions.titleContains("Example"));
}
Monitoring & Metrics
Execution Time Tracking
public class PerformanceListener implements ITestListener {
private Map<String, Long> testStartTimes = new ConcurrentHashMap<>();
@Override
public void onTestStart(ITestResult result) {
testStartTimes.put(result.getName(), System.currentTimeMillis());
}
@Override
public void onTestSuccess(ITestResult result) {
long duration = System.currentTimeMillis() -
testStartTimes.get(result.getName());
System.out.println(result.getName() + " completed in " +
duration + "ms");
}
}
Resource Usage Monitoring
public class ResourceMonitor {
@BeforeClass
public void startMonitoring() {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
executor.scheduleAtFixedRate(() -> {
Runtime runtime = Runtime.getRuntime();
long usedMemory = runtime.totalMemory() - runtime.freeMemory();
int activeThreads = Thread.activeCount();
System.out.println("Memory: " + (usedMemory / 1024 / 1024) + " MB, " +
"Threads: " + activeThreads);
}, 0, 10, TimeUnit.SECONDS);
}
}
Best Practices
- Thread Safety: Always use ThreadLocal for WebDriver
- Resource Limits: Set appropriate thread counts based on available resources
- Isolation: Ensure tests don’t share state
- Cleanup: Always cleanup resources in @AfterMethod
- Grid: Use Selenium Grid for distributed execution
- Monitoring: Track execution metrics
- Fail Fast: Use dependencies to skip dependent tests on failure
Key Takeaways
- TestNG supports multiple parallel execution modes
- ThreadLocal ensures thread-safe WebDriver instances
- Selenium Grid enables distributed cross-browser testing
- Connection pooling improves resource efficiency
- Proper monitoring helps identify bottlenecks
- Test prioritization optimizes execution time
Practice Exercise
Optimize a slow test suite:
- Implement ThreadLocal WebDriver management
- Configure TestNG for parallel execution
- Set up Selenium Grid for distributed testing
- Create a WebDriver connection pool
- Add execution time monitoring
- Implement intelligent test selection
- Achieve 50% reduction in total execution time
Next: Learn advanced performance profiling →