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