Selenium Grid & Parallel Execution
The RecipeBox regression suite now has 20 data-driven "Add Recipe" tests plus everything from earlier lessons — login, search, dropdowns, alerts. Run that one test at a time, on one browser, and it takes forever. Selenium Grid and TestNG parallel execution solve two related but different problems: running across multiple browsers/OS combinations, and simply running many tests at the same time instead of one after another.
What Selenium Grid Actually Is
Selenium Grid is a hub-and-node setup. Your test code talks to one central hub; the hub forwards each test's session request to a registered node — a machine (or container) running an actual browser. This lets you:
- Run the same test against Chrome, Firefox, and Edge at once, on different nodes.
- Run many tests simultaneously across several nodes instead of queuing them on one machine.
- Keep the machine that runs tests separate from the machines that execute browsers.
| Piece | Role |
|---|---|
| Hub | Receives test requests, matches them to a node with a matching browser/OS, and routes WebDriver commands to it. |
| Node | A machine/container with real browsers installed. Actually executes the clicks, typing, navigation. |
| Your test code | Points RemoteWebDriver at the hub's URL instead of launching a local browser directly. |
Connecting to a Grid hub instead of a local browser is a small code change — swap the driver:
URL hubUrl = new URL("http://localhost:4444/wd/hub");
ChromeOptions options = new ChromeOptions();
WebDriver driver = new RemoteWebDriver(hubUrl, options);
driver.get("https://recipebox.example.com/login");
Parallel Execution with TestNG
Separately from Grid, TestNG can run multiple @Test methods (or whole classes) concurrently on your local machine, each launching its own browser instance. This is controlled in testng.xml via the parallel attribute:
<suite name="RecipeBoxRegressionSuite" parallel="methods" thread-count="4">
<test name="AddRecipeTests">
<classes>
<class name="tests.AddRecipeTest"/>
<class name="tests.SearchRecipeTest"/>
</classes>
</test>
</suite>
parallel="methods" with thread-count="4" tells TestNG to run up to 4 @Test methods at the same time, across threads. Your 20 data-driven testAddRecipe invocations from the previous lesson can now run 4 at a time instead of one at a time.
| parallel= value | Behavior |
|---|---|
| "methods" | Each @Test method runs on its own thread. |
| "classes" | Each test class runs on its own thread; methods within a class stay sequential. |
| "tests" | Each <test> tag in the XML runs on its own thread. |
The Thread-Safety Trap: One Shared WebDriver
// WRONG — do not share one driver instance across parallel threads
public class BaseTest {
public static WebDriver driver; // every thread fights over this one instance
}
The Fix: ThreadLocal<WebDriver>
ThreadLocal gives each thread its own private copy of a variable. Every parallel TestNG thread gets its own WebDriver instance, own browser window, own session — no cross-talk.
public class BaseTest {
protected static ThreadLocal<WebDriver> driverThread = new ThreadLocal<>();
@BeforeMethod
public void setUp() {
ChromeOptions options = new ChromeOptions();
WebDriver driver = new ChromeDriver(options);
driverThread.set(driver); // stored per-thread, not shared
}
protected WebDriver getDriver() {
return driverThread.get();
}
@AfterMethod
public void tearDown() {
getDriver().quit();
driverThread.remove(); // avoid leaking the thread's driver reference
}
}
public class AddRecipeTest extends BaseTest {
@Test(dataProvider = "recipeData")
public void testAddRecipe(String title, String category, int prepTime) {
WebDriver driver = getDriver(); // this thread's own instance
AddRecipePage addRecipePage = new AddRecipePage(driver);
addRecipePage.enterTitle(title);
addRecipePage.selectCategory(category);
addRecipePage.enterPrepTime(prepTime);
addRecipePage.save();
Assert.assertTrue(addRecipePage.isRecipeSaved(title),
"Recipe '" + title + "' did not save successfully");
}
}