Skip to main content
Back to blog

How to Integrate Selenium Grid with Azure DevOps

Step-by-step guide to integrating Selenium Grid with Azure DevOps pipelines. Covers Docker-based Selenium Grid setup, self-hosted agents, browser node configuration, running parallel cross-browser tests, and publishing results.

InnovateBits3 min read
Share

Selenium Grid lets you run tests across multiple browsers and operating systems in parallel. Integrating it with Azure DevOps gives you scalable, parallel cross-browser test execution in CI without paying for a cloud testing platform.


Selenium Grid architecture in Azure DevOps

Azure Pipeline Agent
       │
       ▼
Docker Compose (on the agent)
  ├── Selenium Hub      (distributes sessions)
  ├── Chrome Node 1     (runs Chrome tests)
  ├── Chrome Node 2     (parallel Chrome)
  └── Firefox Node      (runs Firefox tests)
       │
       ▼
Test code connects to Hub → Hub routes to available Node

Docker Compose for Selenium Grid

# docker-compose.selenium-grid.yml
version: "3"
services:
  selenium-hub:
    image: selenium/hub:4.21.0
    container_name: selenium-hub
    ports:
      - "4444:4444"
    environment:
      - SE_NODE_MAX_SESSIONS=4
 
  chrome-node-1:
    image: selenium/node-chrome:4.21.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=2
      - SE_VNC_NO_PASSWORD=1
 
  chrome-node-2:
    image: selenium/node-chrome:4.21.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
 
  firefox-node:
    image: selenium/node-firefox:4.21.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

Connecting tests to the Grid

// Java WebDriver connecting to Grid
ChromeOptions options = new ChromeOptions();
options.addArguments("--no-sandbox");
 
WebDriver driver = new RemoteWebDriver(
    new URL("http://localhost:4444/wd/hub"),
    options
);
# Python WebDriver connecting to Grid
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
 
options = Options()
options.add_argument("--no-sandbox")
 
driver = webdriver.Remote(
    command_executor="http://localhost:4444/wd/hub",
    options=options
)

Azure Pipelines YAML

trigger:
  branches:
    include: [main]
 
pool:
  name: Self-Hosted-Linux   # Self-hosted agent with Docker installed
  # Microsoft-hosted agents don't persist Docker between steps — use self-hosted
 
steps:
  - script: |
      docker-compose -f docker-compose.selenium-grid.yml up -d
      echo "Waiting for Grid to be ready..."
      timeout 60 bash -c 'until curl -s http://localhost:4444/wd/hub/status | grep -q "ready.*true"; do sleep 2; done'
    displayName: Start Selenium Grid
 
  - task: JavaToolInstaller@0
    inputs:
      versionSpec: '17'
      jdkArchitectureOption: x64
      jdkSourceOption: PreInstalled
 
  - script: |
      mvn test \
        -DGRID_URL=http://localhost:4444/wd/hub \
        -DBASE_URL=$(STAGING_URL) \
        -Dsurefire.failIfNoSpecifiedTests=false
    displayName: Run cross-browser tests
 
  - task: PublishTestResults@2
    inputs:
      testResultsFormat: JUnit
      testResultsFiles: '**/surefire-reports/TEST-*.xml'
      testRunTitle: Selenium Grid — $(Build.BuildNumber)
      mergeTestResults: true
    condition: always()
 
  - script: docker-compose -f docker-compose.selenium-grid.yml down -v
    displayName: Stop Selenium Grid
    condition: always()

Running tests in parallel across browsers

// TestNG parallel execution across browsers
@DataProvider(name = "browsers", parallel = true)
public Object[][] browsers() {
    return new Object[][] {
        { "chrome" },
        { "firefox" },
    };
}
 
@Test(dataProvider = "browsers")
public void checkoutTest(String browserName) throws MalformedURLException {
    MutableCapabilities caps;
    if ("chrome".equals(browserName)) {
        caps = new ChromeOptions();
    } else {
        caps = new FirefoxOptions();
    }
    
    WebDriver driver = new RemoteWebDriver(
        new URL("http://localhost:4444/wd/hub"), caps
    );
    // Run test...
    driver.quit();
}

Common errors and fixes

Error: Grid hub starts but nodes don't register Fix: Nodes register via the event bus on ports 4442/4443. Ensure these ports are exposed and the SE_EVENT_BUS_HOST env var points to the hub service name (not localhost).

Error: "No such session" errors during parallel execution Fix: Sessions are per-driver instance. Don't share WebDriver instances across threads. Create a new driver per test method.

Error: shm_size has no effect and Chrome crashes Fix: Add --disable-dev-shm-usage to Chrome options. In Docker, /dev/shm is limited to 64MB by default — Chrome needs more for rendering. The option disables shared memory use.

Error: Grid shows nodes as available but tests queue indefinitely Fix: Check SE_NODE_MAX_SESSIONS on each node. If set too low, requests queue. Set to match available CPU cores (2 sessions per CPU core is a good rule).

Free newsletter

Stay ahead in AI-driven QA

Get practical tutorials on test automation, AI testing, and quality engineering — straight to your inbox. No spam, unsubscribe any time.

Discussion

Sign in with GitHub to comment · powered by Giscus