Selenium Grid
Parallel execution across browsers
Introduction to Selenium Grid
Selenium Grid allows you to run tests on multiple machines and browsers simultaneously, dramatically reducing test execution time. It distributes test execution across different environments for cross-browser and cross-platform testing.
When to Use Selenium Grid
- Large test suites that take too long to run sequentially
- Cross-browser compatibility testing requirements
- Testing on multiple OS platforms
- CI/CD pipelines requiring fast feedback
- Distributed team with shared test infrastructure
Grid 4 Architecture
Selenium Grid 4 introduces a modern architecture with separate components for better scalability and flexibility.
Grid 4 Components
┌─────────────────────────────────────────────────────────────┐
│ SELENIUM GRID │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────────┐ ┌──────────────────┐ │
│ │ Router │───▶│ Distributor │───▶│ Session Queue │ │
│ └─────────┘ └─────────────┘ └──────────────────┘ │
│ │ │
│ │ ┌─────────────┐ ┌──────────────────┐ │
│ └────────▶│ Session Map │ │ Event Bus │ │
│ └─────────────┘ └──────────────────┘ │
│ │
├─────────────────────────────────────────────────────────────┤
│ NODES │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Node 1 │ │ Node 2 │ │ Node 3 │ │
│ │ (Chrome) │ │ (Firefox)│ │ (Edge) │ │
│ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────┘
Grid Modes
Mode | Description | Use Case
--------------|------------------------------------|--------------------------
Standalone | All components in single process | Local development, small teams
Hub & Node | Traditional separate hub and nodes | Simple distributed setup
Fully Distributed | Each component runs separately | Large-scale, cloud deployments
Standalone Mode Setup
Standalone mode is the simplest way to run Selenium Grid. All components run in a single process.
Download Selenium Server
# Download from official Selenium site
# https://www.selenium.dev/downloads/
# Or use curl
curl -O https://github.com/SeleniumHQ/selenium/releases/download/selenium-4.27.0/selenium-server-4.27.0.jar
Start Standalone Grid
# Basic standalone start
java -jar selenium-server-4.27.0.jar standalone
# With specific port
java -jar selenium-server-4.27.0.jar standalone --port 4444
# With session timeout
java -jar selenium-server-4.27.0.jar standalone --session-timeout 300
Connect to Standalone Grid
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import java.net.URL;
public class GridTest {
public static void main(String[] args) throws Exception {
ChromeOptions options = new ChromeOptions();
// Connect to Grid
URL gridUrl = new URL("http://localhost:4444");
WebDriver driver = new RemoteWebDriver(gridUrl, options);
driver.get("https://example.com");
System.out.println("Title: " + driver.getTitle());
driver.quit();
}
}
Hub and Node Setup
Hub and Node mode separates the coordinator (Hub) from the test executors (Nodes), allowing distributed setup across multiple machines.
Start Hub
# Start the hub (on machine 1)
java -jar selenium-server-4.27.0.jar hub
# Hub starts on port 4444 by default
# Console available at http://localhost:4444/ui
Start Node
# Start node on same machine
java -jar selenium-server-4.27.0.jar node
# Start node on different machine (specify hub URL)
java -jar selenium-server-4.27.0.jar node --hub http://hub-ip:4444
# Node with specific browsers
java -jar selenium-server-4.27.0.jar node \
--hub http://hub-ip:4444 \
--max-sessions 5
Node Configuration (node.toml)
# node.toml
[node]
detect-drivers = true
max-sessions = 5
[relay]
url = "http://hub-ip:4444"
status-endpoint = "/status"
[[node.driver-configuration]]
display-name = "Chrome"
stereotype = '{"browserName": "chrome", "platformName": "Windows 10"}'
max-sessions = 3
[[node.driver-configuration]]
display-name = "Firefox"
stereotype = '{"browserName": "firefox", "platformName": "Windows 10"}'
max-sessions = 2
Start Node with Config
java -jar selenium-server-4.27.0.jar node --config node.toml
Multi-Machine Setup Example
Machine 1 (Hub): 192.168.1.100:4444
Machine 2 (Node): 192.168.1.101 - Chrome, Firefox
Machine 3 (Node): 192.168.1.102 - Edge, Safari
Machine 4 (Node): 192.168.1.103 - Chrome (Linux)
Using RemoteWebDriver
RemoteWebDriver connects your tests to Selenium Grid, allowing test execution on remote browsers.
Basic RemoteWebDriver
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import java.net.URL;
public class RemoteTest {
WebDriver driver;
public void setup() throws Exception {
ChromeOptions options = new ChromeOptions();
URL gridUrl = new URL("http://localhost:4444");
driver = new RemoteWebDriver(gridUrl, options);
}
}
Browser-Specific Options
// Chrome
ChromeOptions chromeOptions = new ChromeOptions();
chromeOptions.addArguments("--headless");
chromeOptions.addArguments("--window-size=1920,1080");
// Firefox
FirefoxOptions firefoxOptions = new FirefoxOptions();
firefoxOptions.addArguments("--headless");
// Edge
EdgeOptions edgeOptions = new EdgeOptions();
edgeOptions.addArguments("--start-maximized");
// Create RemoteWebDriver with options
WebDriver driver = new RemoteWebDriver(gridUrl, chromeOptions);
Platform-Specific Capabilities
import org.openqa.selenium.Platform;
import org.openqa.selenium.remote.DesiredCapabilities;
// Request specific platform
ChromeOptions options = new ChromeOptions();
options.setPlatformName("Windows 10");
// Or use DesiredCapabilities
DesiredCapabilities caps = new DesiredCapabilities();
caps.setBrowserName("chrome");
caps.setPlatform(Platform.WIN10);
caps.setVersion("latest");
WebDriver driver = new RemoteWebDriver(gridUrl, caps);
Complete BaseTest with Grid
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.firefox.FirefoxOptions;
import org.testng.annotations.*;
import java.net.URL;
public class BaseTest {
protected WebDriver driver;
private static final String GRID_URL = "http://localhost:4444";
@Parameters({"browser"})
@BeforeMethod
public void setup(@Optional("chrome") String browser) throws Exception {
URL gridUrl = new URL(GRID_URL);
switch (browser.toLowerCase()) {
case "firefox":
FirefoxOptions ffOptions = new FirefoxOptions();
driver = new RemoteWebDriver(gridUrl, ffOptions);
break;
case "edge":
EdgeOptions edgeOptions = new EdgeOptions();
driver = new RemoteWebDriver(gridUrl, edgeOptions);
break;
default:
ChromeOptions chromeOptions = new ChromeOptions();
driver = new RemoteWebDriver(gridUrl, chromeOptions);
}
driver.manage().window().maximize();
}
@AfterMethod
public void teardown() {
if (driver != null) {
driver.quit();
}
}
}
Parallel Test Execution
TestNG enables parallel test execution with simple XML configuration.
Parallel by Methods
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Parallel Suite" parallel="methods" thread-count="4">
<test name="All Tests">
<classes>
<class name="tests.LoginTest"/>
<class name="tests.SearchTest"/>
<class name="tests.CheckoutTest"/>
</classes>
</test>
</suite>
Parallel by Classes
<suite name="Parallel Suite" parallel="classes" thread-count="3">
<!-- Each test class runs in separate thread -->
<test name="All Tests">
<classes>
<class name="tests.LoginTest"/>
<class name="tests.SearchTest"/>
<class name="tests.CheckoutTest"/>
</classes>
</test>
</suite>
Cross-Browser Parallel Testing
<suite name="Cross Browser Suite" parallel="tests" thread-count="3">
<test name="Chrome Tests">
<parameter name="browser" value="chrome"/>
<classes>
<class name="tests.LoginTest"/>
<class name="tests.SearchTest"/>
</classes>
</test>
<test name="Firefox Tests">
<parameter name="browser" value="firefox"/>
<classes>
<class name="tests.LoginTest"/>
<class name="tests.SearchTest"/>
</classes>
</test>
<test name="Edge Tests">
<parameter name="browser" value="edge"/>
<classes>
<class name="tests.LoginTest"/>
<class name="tests.SearchTest"/>
</classes>
</test>
</suite>
Thread-Safe BaseTest
public class BaseTest {
// ThreadLocal ensures each thread has its own driver instance
protected static ThreadLocal<WebDriver> driverThread = new ThreadLocal<>();
private static final String GRID_URL = "http://localhost:4444";
public static WebDriver getDriver() {
return driverThread.get();
}
@Parameters({"browser"})
@BeforeMethod
public void setup(@Optional("chrome") String browser) throws Exception {
URL gridUrl = new URL(GRID_URL);
WebDriver driver;
switch (browser.toLowerCase()) {
case "firefox":
driver = new RemoteWebDriver(gridUrl, new FirefoxOptions());
break;
case "edge":
driver = new RemoteWebDriver(gridUrl, new EdgeOptions());
break;
default:
driver = new RemoteWebDriver(gridUrl, new ChromeOptions());
}
driverThread.set(driver);
getDriver().manage().window().maximize();
}
@AfterMethod
public void teardown() {
if (getDriver() != null) {
getDriver().quit();
driverThread.remove();
}
}
}
Docker Setup
Docker provides the easiest way to set up Selenium Grid with consistent, reproducible environments.
Docker Compose (docker-compose.yml)
version: "3"
services:
selenium-hub:
image: selenium/hub:4.27.0
container_name: selenium-hub
ports:
- "4442:4442"
- "4443:4443"
- "4444:4444"
chrome:
image: selenium/node-chrome:4.27.0
shm_size: 2gb
depends_on:
- selenium-hub
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
- SE_NODE_MAX_SESSIONS=4
firefox:
image: selenium/node-firefox:4.27.0
shm_size: 2gb
depends_on:
- selenium-hub
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
- SE_NODE_MAX_SESSIONS=4
edge:
image: selenium/node-edge:4.27.0
shm_size: 2gb
depends_on:
- selenium-hub
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
- SE_NODE_MAX_SESSIONS=4
Start Docker Grid
# Start all containers
docker-compose up -d
# View running containers
docker-compose ps
# View logs
docker-compose logs -f
# Scale Chrome nodes
docker-compose up -d --scale chrome=3
# Stop and remove containers
docker-compose down
Dynamic Grid with Docker Compose
version: "3"
services:
selenium-hub:
image: selenium/hub:4.27.0
container_name: selenium-hub
ports:
- "4442:4442"
- "4443:4443"
- "4444:4444"
chrome:
image: selenium/node-chrome:4.27.0
shm_size: 2gb
depends_on:
- selenium-hub
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
deploy:
replicas: 3 # Run 3 Chrome instances
Video Recording with Docker
version: "3"
services:
chrome:
image: selenium/node-chrome:4.27.0
shm_size: 2gb
depends_on:
- selenium-hub
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
chrome-video:
image: selenium/video:ffmpeg-6.1.1-20240621
volumes:
- ./videos:/videos
depends_on:
- chrome
environment:
- DISPLAY_CONTAINER_NAME=chrome
- SE_VIDEO_FILE_NAME=chrome_video.mp4
Cloud Grid Providers
Cloud providers offer managed Selenium Grid infrastructure with extensive browser and platform coverage.
BrowserStack Integration
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.MutableCapabilities;
public class BrowserStackTest {
public static final String USERNAME = System.getenv("BROWSERSTACK_USERNAME");
public static final String ACCESS_KEY = System.getenv("BROWSERSTACK_ACCESS_KEY");
public static final String URL = "https://" + USERNAME + ":" + ACCESS_KEY +
"@hub-cloud.browserstack.com/wd/hub";
public void setup() throws Exception {
MutableCapabilities caps = new MutableCapabilities();
caps.setCapability("browserName", "Chrome");
caps.setCapability("browserVersion", "latest");
HashMap<String, Object> bsOptions = new HashMap<>();
bsOptions.put("os", "Windows");
bsOptions.put("osVersion", "11");
bsOptions.put("projectName", "My Project");
bsOptions.put("buildName", "Build 1.0");
caps.setCapability("bstack:options", bsOptions);
WebDriver driver = new RemoteWebDriver(new URL(URL), caps);
}
}
Sauce Labs Integration
public class SauceLabsTest {
public static final String USERNAME = System.getenv("SAUCE_USERNAME");
public static final String ACCESS_KEY = System.getenv("SAUCE_ACCESS_KEY");
public static final String URL = "https://" + USERNAME + ":" + ACCESS_KEY +
"@ondemand.us-west-1.saucelabs.com:443/wd/hub";
public void setup() throws Exception {
MutableCapabilities caps = new MutableCapabilities();
caps.setCapability("browserName", "chrome");
caps.setCapability("browserVersion", "latest");
caps.setCapability("platformName", "Windows 11");
HashMap<String, Object> sauceOptions = new HashMap<>();
sauceOptions.put("name", "Test Name");
sauceOptions.put("build", "Build 1.0");
caps.setCapability("sauce:options", sauceOptions);
WebDriver driver = new RemoteWebDriver(new URL(URL), caps);
}
}
LambdaTest Integration
public class LambdaTestTest {
public static final String USERNAME = System.getenv("LT_USERNAME");
public static final String ACCESS_KEY = System.getenv("LT_ACCESS_KEY");
public static final String URL = "https://" + USERNAME + ":" + ACCESS_KEY +
"@hub.lambdatest.com/wd/hub";
public void setup() throws Exception {
ChromeOptions options = new ChromeOptions();
options.setPlatformName("Windows 10");
options.setBrowserVersion("latest");
HashMap<String, Object> ltOptions = new HashMap<>();
ltOptions.put("project", "My Project");
ltOptions.put("build", "Build 1.0");
ltOptions.put("name", "Test Name");
options.setCapability("LT:Options", ltOptions);
WebDriver driver = new RemoteWebDriver(new URL(URL), options);
}
}
Cloud vs Self-Hosted Grid
- Cloud Pros: No infrastructure maintenance, massive browser coverage, built-in reporting
- Cloud Cons: Cost at scale, network latency, data privacy concerns
- Self-Hosted Pros: Full control, no recurring costs, data stays internal
- Self-Hosted Cons: Infrastructure maintenance, limited browser versions
Best Practices
Retry Logic for Grid
import org.testng.IRetryAnalyzer;
import org.testng.ITestResult;
public class RetryAnalyzer implements IRetryAnalyzer {
private int retryCount = 0;
private static final int MAX_RETRY = 2;
@Override
public boolean retry(ITestResult result) {
if (retryCount < MAX_RETRY) {
retryCount++;
return true;
}
return false;
}
}
// Usage
@Test(retryAnalyzer = RetryAnalyzer.class)
public void testWithRetry() {
// Test code
}
Grid Health Check
import java.net.HttpURLConnection;
import java.net.URL;
public class GridHealthCheck {
public static boolean isGridReady(String gridUrl) {
try {
URL url = new URL(gridUrl + "/status");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
int responseCode = conn.getResponseCode();
return responseCode == 200;
} catch (Exception e) {
return false;
}
}
public static void waitForGrid(String gridUrl, int timeoutSeconds) {
long endTime = System.currentTimeMillis() + (timeoutSeconds * 1000);
while (System.currentTimeMillis() < endTime) {
if (isGridReady(gridUrl)) {
System.out.println("Grid is ready!");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
throw new RuntimeException("Grid not ready within timeout");
}
}
Key Takeaways
- Selenium Grid enables parallel execution across browsers and platforms
- Grid 4 offers standalone, hub-node, and fully distributed modes
- Docker provides the easiest setup for consistent Grid environments
- Use ThreadLocal for thread-safe parallel test execution
- Cloud providers offer managed Grid with extensive browser coverage
- Always implement proper cleanup and error handling
Next Steps
Continue Your Learning Journey
- CI/CD Integration - Integrate Grid with Jenkins, GitHub Actions, GitLab CI
- Extent Reports - Generate rich HTML reports with parallel test results
- Kubernetes - Deploy Grid on Kubernetes for auto-scaling
- Performance Testing - Combine with JMeter for load testing
- Mobile Testing - Extend to Appium Grid for mobile automation