Grid is running. Dashboard shows Chrome, Firefox, Edge nodes — all green. Now what? You need to tell your tests to use the Grid instead of local browsers. That means RemoteWebDriver + browser options.
Don't hardcode the driver creation in every test. Build a factory. One class that knows how to create any browser — locally or on Grid. Every test asks the factory for a driver. The factory figures out where to run it.
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxOptions;
import org.openqa.selenium.edge.EdgeDriver;
import org.openqa.selenium.edge.EdgeOptions;
import org.openqa.selenium.remote.RemoteWebDriver;
import java.net.URL;
public class WebDriverFactory {
private static final String GRID_URL =
System.getProperty("grid.url", "");
public static WebDriver createDriver(String browser) throws Exception {
boolean useGrid = !GRID_URL.isEmpty();
boolean headless = Boolean.parseBoolean(
System.getProperty("headless", "false"));
switch (browser.toLowerCase()) {
case "chrome":
ChromeOptions chromeOpts = new ChromeOptions();
if (headless) {
chromeOpts.addArguments("--headless=new");
}
chromeOpts.addArguments("--no-sandbox");
chromeOpts.addArguments("--disable-dev-shm-usage");
chromeOpts.addArguments("--window-size=1920,1080");
return useGrid
? new RemoteWebDriver(new URL(GRID_URL), chromeOpts)
: new ChromeDriver(chromeOpts);
case "firefox":
FirefoxOptions ffOpts = new FirefoxOptions();
if (headless) {
ffOpts.addArguments("--headless");
}
ffOpts.addArguments("--width=1920");
ffOpts.addArguments("--height=1080");
return useGrid
? new RemoteWebDriver(new URL(GRID_URL), ffOpts)
: new FirefoxDriver(ffOpts);
case "edge":
EdgeOptions edgeOpts = new EdgeOptions();
if (headless) {
edgeOpts.addArguments("--headless=new");
}
edgeOpts.addArguments("--no-sandbox");
edgeOpts.addArguments("--window-size=1920,1080");
return useGrid
? new RemoteWebDriver(new URL(GRID_URL), edgeOpts)
: new EdgeDriver(edgeOpts);
default:
throw new IllegalArgumentException(
"Unsupported browser: " + browser
+ ". Use chrome, firefox, or edge.");
}
}
}When running parallel tests, each thread needs its own WebDriver. If two threads share one driver, chaos. ThreadLocal gives each thread its own isolated copy. This is non-negotiable for parallel execution.
import org.openqa.selenium.WebDriver;
import org.testng.annotations.*;
public class BaseTest {
// Each thread gets its own driver — no interference
protected static ThreadLocal<WebDriver> driverThread =
new ThreadLocal<>();
@Parameters({"browser"})
@BeforeMethod
public void setUp(
@Optional("chrome") String browser) throws Exception {
WebDriver driver = WebDriverFactory.createDriver(browser);
driverThread.set(driver);
driver.manage().window().maximize();
}
protected WebDriver getDriver() {
return driverThread.get();
}
@AfterMethod
public void tearDown() {
WebDriver driver = driverThread.get();
if (driver != null) {
driver.quit();
driverThread.remove(); // Prevent memory leaks
}
}
}<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Cross-Browser Suite" parallel="tests" thread-count="3">
<test name="Chrome Tests">
<parameter name="browser" value="chrome" />
<classes>
<class name="com.practice.tests.LoginTest" />
<class name="com.practice.tests.DashboardTest" />
</classes>
</test>
<test name="Firefox Tests">
<parameter name="browser" value="firefox" />
<classes>
<class name="com.practice.tests.LoginTest" />
<class name="com.practice.tests.DashboardTest" />
</classes>
</test>
<test name="Edge Tests">
<parameter name="browser" value="edge" />
<classes>
<class name="com.practice.tests.LoginTest" />
<class name="com.practice.tests.DashboardTest" />
</classes>
</test>
</suite>If you use a regular WebDriver field instead of ThreadLocal, parallel tests will fight over the same browser instance. You'll get random NullPointerExceptions, wrong pages, and tests that pass/fail randomly. ThreadLocal is mandatory.
The @Optional("chrome") annotation means if you run a test without the TestNG XML (like directly from IntelliJ), it defaults to Chrome. No XML needed for local development.
Key Point: WebDriver Factory + ThreadLocal + TestNG parallel XML = same tests running on Chrome, Firefox, and Edge simultaneously on Grid.