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.
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=4443Connecting 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).
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