Skip to main content
SeleniumDecoded

Selenium Grid

Distribute tests across multiple machines and browsers using Selenium Grid for scalable test execution.

Selenium 4 Stable

Selenium Grid allows you to run tests on multiple machines with different browsers simultaneously, dramatically reducing test execution time for large test suites.

Grid Architecture

┌─────────────────┐
│ Hub (Router) │
│ Port 4444 │
└────────┬────────┘
┌────────────────────┼────────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ Node 1 │ │ Node 2 │ │ Node 3 │
│ Chrome │ │ Firefox │ │ Edge │
│ Windows │ │ Linux │ │ Windows │
└───────────────┘ └───────────────┘ └───────────────┘

Quick Start with Docker

Docker Compose Setup

docker-compose.yml
version: '3.8'
services:
selenium-hub:
image: selenium/hub:latest
container_name: selenium-hub
ports:
- "4442:4442"
- "4443:4443"
- "4444:4444"
chrome:
image: selenium/node-chrome:latest
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:latest
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:latest
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 the grid:

Terminal window
docker-compose up -d
# Check status at http://localhost:4444

Connecting to Grid

Connect to Selenium Grid
Selenium 4 Stable
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import java.net.URL;
// Connect to Grid Hub
URL gridUrl = new URL("http://localhost:4444");
// Chrome on Grid
ChromeOptions chromeOptions = new ChromeOptions();
WebDriver chromeDriver = new RemoteWebDriver(gridUrl, chromeOptions);
// Firefox on Grid
FirefoxOptions firefoxOptions = new FirefoxOptions();
WebDriver firefoxDriver = new RemoteWebDriver(gridUrl, firefoxOptions);
// Edge on Grid
EdgeOptions edgeOptions = new EdgeOptions();
WebDriver edgeDriver = new RemoteWebDriver(gridUrl, edgeOptions);
// Use like a regular driver
chromeDriver.get("https://example.com");
System.out.println(chromeDriver.getTitle());
// Always quit when done
chromeDriver.quit();
from selenium import webdriver
from selenium.webdriver.common.options import ArgOptions
# Connect to Grid Hub
grid_url = "http://localhost:4444"
# Chrome on Grid
chrome_options = webdriver.ChromeOptions()
chrome_driver = webdriver.Remote(
command_executor=grid_url,
options=chrome_options
)
# Firefox on Grid
firefox_options = webdriver.FirefoxOptions()
firefox_driver = webdriver.Remote(
command_executor=grid_url,
options=firefox_options
)
# Edge on Grid
edge_options = webdriver.EdgeOptions()
edge_driver = webdriver.Remote(
command_executor=grid_url,
options=edge_options
)
# Use like a regular driver
chrome_driver.get("https://example.com")
print(chrome_driver.title)
# Always quit when done
chrome_driver.quit()
const { Builder } = require('selenium-webdriver');
const chrome = require('selenium-webdriver/chrome');
// Connect to Grid Hub
const gridUrl = 'http://localhost:4444';
// Chrome on Grid
const chromeDriver = await new Builder()
.usingServer(gridUrl)
.forBrowser('chrome')
.build();
// Firefox on Grid
const firefoxDriver = await new Builder()
.usingServer(gridUrl)
.forBrowser('firefox')
.build();
// Use like a regular driver
await chromeDriver.get('https://example.com');
console.log(await chromeDriver.getTitle());
// Always quit when done
await chromeDriver.quit();
using OpenQA.Selenium;
using OpenQA.Selenium.Remote;
using OpenQA.Selenium.Chrome;
// Connect to Grid Hub
Uri gridUrl = new Uri("http://localhost:4444");
// Chrome on Grid
var chromeOptions = new ChromeOptions();
IWebDriver chromeDriver = new RemoteWebDriver(gridUrl, chromeOptions);
// Firefox on Grid
var firefoxOptions = new FirefoxOptions();
IWebDriver firefoxDriver = new RemoteWebDriver(gridUrl, firefoxOptions);
// Use like a regular driver
chromeDriver.Navigate().GoToUrl("https://example.com");
Console.WriteLine(chromeDriver.Title);
// Always quit when done
chromeDriver.Quit();

Grid Configuration Options

Environment Variables

# docker-compose.yml node configuration
chrome:
image: selenium/node-chrome:latest
environment:
# Connection
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
# Capacity
- SE_NODE_MAX_SESSIONS=5 # Max concurrent sessions
- SE_NODE_SESSION_TIMEOUT=300 # Session timeout in seconds
# Node info
- SE_NODE_OVERRIDE_MAX_SESSIONS=true

Scaling Nodes

Terminal window
# Scale Chrome nodes to 3 instances
docker-compose up -d --scale chrome=3
# Check node status
curl http://localhost:4444/status

Cross-Browser Testing

Run Test Across Browsers
Selenium 4 Stable
import org.testng.annotations.*;
import org.openqa.selenium.remote.RemoteWebDriver;
import java.net.URL;
public class CrossBrowserTest {
private WebDriver driver;
private String browser;
@Parameters({"browser"})
@BeforeMethod
public void setup(String browser) throws Exception {
this.browser = browser;
URL gridUrl = new URL("http://localhost:4444");
switch (browser.toLowerCase()) {
case "chrome":
driver = new RemoteWebDriver(gridUrl, new ChromeOptions());
break;
case "firefox":
driver = new RemoteWebDriver(gridUrl, new FirefoxOptions());
break;
case "edge":
driver = new RemoteWebDriver(gridUrl, new EdgeOptions());
break;
}
}
@Test
public void testHomepage() {
driver.get("https://example.com");
Assert.assertTrue(driver.getTitle().contains("Example"));
}
@AfterMethod
public void teardown() {
if (driver != null) {
driver.quit();
}
}
}
// testng.xml
// <suite name="Cross Browser" parallel="tests" thread-count="3">
// <test name="Chrome"><parameter name="browser" value="chrome"/>...</test>
// <test name="Firefox"><parameter name="browser" value="firefox"/>...</test>
// <test name="Edge"><parameter name="browser" value="edge"/>...</test>
// </suite>
conftest.py
import pytest
from selenium import webdriver
def pytest_addoption(parser):
parser.addoption("--browser", action="store", default="chrome")
@pytest.fixture
def driver(request):
browser = request.config.getoption("--browser")
grid_url = "http://localhost:4444"
if browser == "chrome":
options = webdriver.ChromeOptions()
elif browser == "firefox":
options = webdriver.FirefoxOptions()
elif browser == "edge":
options = webdriver.EdgeOptions()
driver = webdriver.Remote(command_executor=grid_url, options=options)
yield driver
driver.quit()
# Run with different browsers:
# pytest --browser=chrome
# pytest --browser=firefox
# pytest --browser=edge
# Or run all browsers in parallel with pytest-xdist:
# pytest -n 3 --browser=chrome &
# pytest -n 3 --browser=firefox &
# pytest -n 3 --browser=edge &
const { Builder } = require('selenium-webdriver');
const browsers = ['chrome', 'firefox', 'edge'];
describe('Cross Browser Tests', function() {
browsers.forEach((browserName) => {
describe(`${browserName} tests`, function() {
let driver;
beforeEach(async function() {
driver = await new Builder()
.usingServer('http://localhost:4444')
.forBrowser(browserName)
.build();
});
afterEach(async function() {
await driver.quit();
});
it('should load homepage', async function() {
await driver.get('https://example.com');
const title = await driver.getTitle();
expect(title).to.include('Example');
});
});
});
});
[TestFixture("chrome")]
[TestFixture("firefox")]
[TestFixture("edge")]
public class CrossBrowserTest
{
private IWebDriver driver;
private string browser;
public CrossBrowserTest(string browser)
{
this.browser = browser;
}
[SetUp]
public void Setup()
{
Uri gridUrl = new Uri("http://localhost:4444");
switch (browser)
{
case "chrome":
driver = new RemoteWebDriver(gridUrl, new ChromeOptions());
break;
case "firefox":
driver = new RemoteWebDriver(gridUrl, new FirefoxOptions());
break;
case "edge":
driver = new RemoteWebDriver(gridUrl, new EdgeOptions());
break;
}
}
[Test]
public void TestHomepage()
{
driver.Navigate().GoToUrl("https://example.com");
Assert.That(driver.Title, Does.Contain("Example"));
}
[TearDown]
public void Teardown()
{
driver?.Quit();
}
}

Grid with Video Recording

# docker-compose with video recording
version: '3.8'
services:
selenium-hub:
image: selenium/hub:latest
ports:
- "4444:4444"
chrome-video:
image: selenium/node-chrome:latest
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_START_VNC=true
- SE_START_NO_VNC=true
ports:
- "7900:7900" # VNC port
video:
image: selenium/video:latest
depends_on:
- chrome-video
environment:
- DISPLAY_CONTAINER_NAME=chrome-video
- FILE_NAME=chrome_video.mp4
volumes:
- ./videos:/videos

Access VNC at http://localhost:7900 (password: secret)

Cloud Grid Services

ServiceProsCons
Sauce LabsHuge browser selectionExpensive
BrowserStackReal devicesExpensive
LambdaTestGood pricingFewer features
Self-hostedFull controlMaintenance overhead

Best Practices

  1. Set session timeouts - Prevent hung sessions from blocking nodes
  2. Use Docker volumes for test artifacts (screenshots, videos)
  3. Monitor Grid status - Check /status endpoint
  4. Scale based on load - Add nodes as needed
  5. Clean up sessions - Always call driver.quit()

Next Steps