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
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=4Start the grid:
docker-compose up -d
# Check status at http://localhost:4444Connecting 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 HubURL gridUrl = new URL("http://localhost:4444");
// Chrome on GridChromeOptions chromeOptions = new ChromeOptions();WebDriver chromeDriver = new RemoteWebDriver(gridUrl, chromeOptions);
// Firefox on GridFirefoxOptions firefoxOptions = new FirefoxOptions();WebDriver firefoxDriver = new RemoteWebDriver(gridUrl, firefoxOptions);
// Edge on GridEdgeOptions edgeOptions = new EdgeOptions();WebDriver edgeDriver = new RemoteWebDriver(gridUrl, edgeOptions);
// Use like a regular driverchromeDriver.get("https://example.com");System.out.println(chromeDriver.getTitle());
// Always quit when donechromeDriver.quit();from selenium import webdriverfrom selenium.webdriver.common.options import ArgOptions
# Connect to Grid Hubgrid_url = "http://localhost:4444"
# Chrome on Gridchrome_options = webdriver.ChromeOptions()chrome_driver = webdriver.Remote( command_executor=grid_url, options=chrome_options)
# Firefox on Gridfirefox_options = webdriver.FirefoxOptions()firefox_driver = webdriver.Remote( command_executor=grid_url, options=firefox_options)
# Edge on Gridedge_options = webdriver.EdgeOptions()edge_driver = webdriver.Remote( command_executor=grid_url, options=edge_options)
# Use like a regular driverchrome_driver.get("https://example.com")print(chrome_driver.title)
# Always quit when donechrome_driver.quit()const { Builder } = require('selenium-webdriver');const chrome = require('selenium-webdriver/chrome');
// Connect to Grid Hubconst gridUrl = 'http://localhost:4444';
// Chrome on Gridconst chromeDriver = await new Builder() .usingServer(gridUrl) .forBrowser('chrome') .build();
// Firefox on Gridconst firefoxDriver = await new Builder() .usingServer(gridUrl) .forBrowser('firefox') .build();
// Use like a regular driverawait chromeDriver.get('https://example.com');console.log(await chromeDriver.getTitle());
// Always quit when doneawait chromeDriver.quit();using OpenQA.Selenium;using OpenQA.Selenium.Remote;using OpenQA.Selenium.Chrome;
// Connect to Grid HubUri gridUrl = new Uri("http://localhost:4444");
// Chrome on Gridvar chromeOptions = new ChromeOptions();IWebDriver chromeDriver = new RemoteWebDriver(gridUrl, chromeOptions);
// Firefox on Gridvar firefoxOptions = new FirefoxOptions();IWebDriver firefoxDriver = new RemoteWebDriver(gridUrl, firefoxOptions);
// Use like a regular driverchromeDriver.Navigate().GoToUrl("https://example.com");Console.WriteLine(chromeDriver.Title);
// Always quit when donechromeDriver.Quit();Grid Configuration Options
Environment Variables
# docker-compose.yml node configurationchrome: 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=trueScaling Nodes
# Scale Chrome nodes to 3 instancesdocker-compose up -d --scale chrome=3
# Check node statuscurl http://localhost:4444/statusCross-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>import pytestfrom selenium import webdriver
def pytest_addoption(parser): parser.addoption("--browser", action="store", default="chrome")
@pytest.fixturedef 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 recordingversion: '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:/videosAccess VNC at http://localhost:7900 (password: secret)
Cloud Grid Services
| Service | Pros | Cons |
|---|---|---|
| Sauce Labs | Huge browser selection | Expensive |
| BrowserStack | Real devices | Expensive |
| LambdaTest | Good pricing | Fewer features |
| Self-hosted | Full control | Maintenance overhead |
Best Practices
- Set session timeouts - Prevent hung sessions from blocking nodes
- Use Docker volumes for test artifacts (screenshots, videos)
- Monitor Grid status - Check
/statusendpoint - Scale based on load - Add nodes as needed
- Clean up sessions - Always call
driver.quit()
Next Steps
- Parallel Execution - Local parallelization
- Headless Browsers - Faster grid nodes
- pytest Integration - Python CI/CD setup
- TestNG and JUnit - Java CI/CD setup
- Browser Options - Configure remote browsers