Lesson 8 of 14 ~75 min
Course progress
0%

Parallel Test Execution Strategies

Master parallel and distributed test execution with TestNG, Selenium Grid, and thread management

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 parallel
  • parallel="classes" - Run test classes in parallel
  • parallel="tests" - Run <test> tags in parallel
  • parallel="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

  1. Thread Safety: Always use ThreadLocal for WebDriver
  2. Resource Limits: Set appropriate thread counts based on available resources
  3. Isolation: Ensure tests don’t share state
  4. Cleanup: Always cleanup resources in @AfterMethod
  5. Grid: Use Selenium Grid for distributed execution
  6. Monitoring: Track execution metrics
  7. 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:

  1. Implement ThreadLocal WebDriver management
  2. Configure TestNG for parallel execution
  3. Set up Selenium Grid for distributed testing
  4. Create a WebDriver connection pool
  5. Add execution time monitoring
  6. Implement intelligent test selection
  7. Achieve 50% reduction in total execution time

Next: Learn advanced performance profiling →