Selenium Grid 4 is the part of Selenium that lets you run browser tests on more than one machine, more than one browser, and more than one configuration at the same time. That sounds simple, but the practical value is usually about something more specific: reducing queue time in CI, isolating browser-specific failures, and keeping test infrastructure manageable when parallel execution becomes necessary.

This tutorial focuses on the parts teams actually need to understand to use Grid well, not just to get a demo running. We will cover the Selenium Grid 4 architecture, how sessions are created, what “hub” and “node” mean in Grid 4 terms, how to set up a local Grid, how to run tests in parallel, and where things usually break in real pipelines.

For readers who want the official source as they go, the primary reference is the Selenium documentation and the Selenium Grid docs.

What Selenium Grid 4 is for, and what it is not

Selenium Grid is a distributed test execution layer. Your test code still talks to the WebDriver API, but the browser session may run on another process or machine. That gives you:

  • parallel execution across browsers or machines
  • separation between test runner and browser runtime
  • coverage for different browser and OS combinations
  • better resource utilization in CI

What it does not give you is stability by itself. Grid can make a flaky suite faster, but it cannot fix weak locators, timing assumptions, or test data collisions. It also does not remove the operational work of maintaining browser images, Docker containers, network access, and observability.

Grid is infrastructure, not a test quality layer. If the suite is brittle on one machine, a distributed setup usually makes the brittleness harder to diagnose, not easier.

Selenium Grid 4 architecture in practical terms

Grid 4 introduced a more modular architecture than the older hub-and-node mental model. The classic terms are still useful, but the implementation is more flexible.

At a high level, Grid 4 includes these roles:

  • Router: receives incoming WebDriver requests and forwards them
  • Distributor: decides where new sessions should run
  • Session Map: tracks which session lives on which node
  • Event Bus: lets Grid components communicate
  • Node: hosts browser instances

In a small local setup, these pieces can run in a single process. In a larger setup, they may be split across multiple machines or containers.

Why this matters

If you think only in terms of “hub and node”, you may miss where failures happen. A request can fail because:

  • the router never received the request
  • the distributor could not find a matching node
  • the node ran out of browser capacity
  • the session was created but lost from the session map
  • the browser crashed after startup

That is why Grid observability matters. Logs from one component are not enough when diagnosing session failures.

When teams should use Selenium Grid 4

Selenium Grid 4 makes sense when at least one of these is true:

  1. You need to run the same suite across multiple browser types.
  2. Your CI pipeline is too slow because tests run serially.
  3. You want test execution to be isolated from your build agent.
  4. You need a more realistic browser environment than headless local execution.
  5. Your organization already owns infrastructure to host the grid.

A common mistake is to adopt Grid because parallel execution sounds faster, but without first splitting tests into independent units. If tests share state, depend on order, or reuse mutable accounts, the grid will surface race conditions that already existed.

If your team wants distributed browser testing without running Grid infrastructure, a managed platform can be easier to operate. For example, Endtest, an agentic AI [Test automation](https://en.wikipedia.org/wiki/Test_automation) platform, offers a codeless alternative with distributed real-browser execution and avoids the day-to-day burden of grid administration. That is not a universal replacement for Grid, but it is relevant when the team wants less infrastructure ownership.

Selenium Grid 4 setup: local first, then expand

A reliable Selenium Grid 4 setup usually starts locally in Docker. That gives you a repeatable environment and a quick way to verify that your tests can connect, create sessions, and run in parallel.

Basic local Grid with Docker

Selenium publishes Docker images for Grid components, and the Grid docs show the current supported patterns. A common local topology is a standalone container that includes the router, distributor, session map, event bus, and node registration path.

A simple example looks like this:

docker run -d --name selenium-grid \
  -p 4444:4444 \
  -p 7900:7900 \
  selenium/standalone-chrome:latest

This is not the only way to run Grid 4, and for serious usage you should follow the official Grid documentation rather than relying on one-liner examples. But the point of a local launch is to prove the connection path from test code to browser.

Then point your Selenium tests at the Grid endpoint instead of a local driver binary.

Python example: remote Chrome session

from selenium import webdriver
from selenium.webdriver.common.by import By

options = webdriver.ChromeOptions() options.add_argument(“–headless=new”)

driver = webdriver.Remote( command_executor=”http://localhost:4444/wd/hub”, options=options, )

try: driver.get(“https://example.com”) print(driver.title) finally: driver.quit()

This example uses webdriver.Remote, which is the key change. Your framework no longer launches the browser locally, it requests a session from Grid.

What to verify before you move on

A functional setup should prove these things:

  • the test runner can reach the Grid endpoint
  • a session is created successfully
  • the browser version matches your expectations
  • the browser can access the application under test
  • the session ends cleanly with quit()

If any of those fail, fix them before adding parallelism. Parallel execution just multiplies the failure mode.

Sessions, capabilities, and browser matching

A session is the live browser connection created by Grid for one test. In WebDriver terms, your code sends desired capabilities or browser options, and Grid tries to match them to an available node.

In practice, the matching logic is where many setup problems show up. You might request:

  • Chrome version 126
  • Firefox with a specific OS image
  • a browser in headless mode
  • a mobile emulation profile

If no node advertises a compatible capability set, session creation fails.

Keep capabilities minimal

Over-specifying capabilities is a common source of unnecessary fragility. Only request what your test genuinely needs. If your tests do not require a fixed browser version, do not pin one without reason. If you are using containers, make sure your node images are aligned with the browser versions your application team supports.

Example of a targeted capability set

from selenium import webdriver

options = webdriver.ChromeOptions() options.set_capability(“browserName”, “chrome”) options.set_capability(“pageLoadStrategy”, “normal”)

driver = webdriver.Remote( “http://localhost:4444/wd/hub”, options=options, )

You do not need to set every possible capability. The smaller and clearer the request, the easier it is for Grid to place the session.

Parallel execution, and the part that actually matters

Parallel execution is the most common reason teams adopt Selenium Grid, but it only works when the suite is structured for concurrency.

The safe unit of parallelism is usually the test, not the line of code

Most test runners can parallelize by file, class, or test case. The right choice depends on how much shared setup your suite uses.

Good candidates for parallelism:

  • independent end-to-end tests
  • tests that each create their own users and data
  • read-only smoke tests
  • cross-browser matrix runs

Risky candidates:

  • tests that share one account
  • tests that depend on existing orders, messages, or sessions
  • tests that mutate the same backend records
  • tests with global setup that is not isolated per worker

Example: pytest-xdist with Selenium Remote

import pytest
from selenium import webdriver

@pytest.fixture def driver(): options = webdriver.ChromeOptions() driver = webdriver.Remote(“http://localhost:4444/wd/hub”, options=options) yield driver driver.quit()

def test_homepage_title(driver): driver.get(“https://example.com”) assert “Example” in driver.title

Then run with multiple workers:

pytest -n 4

That is the easy part. The hard part is making sure each worker gets isolated test data and does not collide on shared backend state.

Watch for data contention

Parallel browser execution often exposes backend bottlenecks before browser bottlenecks. For example:

  • one test locks a record another test expects to mutate
  • two tests create the same username
  • cleanup jobs delete fixtures still in use
  • rate limits trigger because the suite now acts like four users instead of one

If your suite gets slower or less stable in Grid, look at your test data model before blaming Selenium.

A practical Selenium Grid tutorial for debugging sessions

When Grid does not create a session, debug in layers.

1. Confirm the Grid endpoint is alive

curl http://localhost:4444/status

You should see a JSON response that indicates the Grid is ready.

2. Check node availability

If you run separate nodes, make sure they are registered and healthy. The Grid UI and logs should show the node inventory and current slots.

3. Inspect browser capability mismatch

A session request can fail because your code asks for a browser or platform combination that no node supports. That is usually visible in the distributor or node logs.

4. Verify network reachability from the node

Even if Grid starts correctly, the browser running in a container may not reach your application, especially if the app is running on localhost from the test runner’s point of view. Inside a container, localhost means the container itself.

That is a frequent source of confusion in CI. Use the network address that makes sense from the browser container, not from your laptop.

5. Increase logging only when needed

Verbose logs can help, but too much logging can hide the relevant message. Start with component logs and session creation failures, then expand.

The fastest path to debugging Grid is usually to isolate whether the problem is session allocation, browser startup, or application reachability. Do not troubleshoot all three at once.

Running Chrome and Firefox in a distributed setup

A common Selenium distributed testing pattern is to run the same tests against multiple browsers.

You can parameterize browser choice in your test framework and use the same test body for different session configurations. The exact implementation depends on language and runner, but the idea is consistent: one test definition, multiple browser sessions.

Example test matrix approach

import pytest
from selenium import webdriver

@pytest.fixture(params=[“chrome”, “firefox”]) def driver(request): if request.param == “chrome”: options = webdriver.ChromeOptions() else: options = webdriver.FirefoxOptions()

driver = webdriver.Remote("http://localhost:4444/wd/hub", options=options)
yield driver
driver.quit()

This kind of parameterization is useful for smoke coverage, but it can become expensive if applied to the entire suite. A better pattern is to reserve full browser matrices for the paths that are most likely to break cross-browser.

Prefer risk-based browser coverage

In practice, not every test needs to run on every browser every time. Common strategies include:

  • full matrix for a small smoke suite
  • Chrome only for most PR checks
  • Firefox and Safari on a nightly schedule
  • targeted browser reruns when a defect is browser-specific

That strategy reduces queue pressure while still keeping cross-browser risk visible.

Using Docker Compose for a more realistic local Grid

If you want a reproducible Grid environment for a team, Docker Compose is often easier than hand-starting containers.

services:
  selenium:
    image: selenium/standalone-chrome:latest
    ports:
      - "4444:4444"
      - "7900:7900"

This is intentionally minimal. In a real setup, you may want separate nodes, persistent logs, environment variables for memory settings, and health checks. The main value of Compose is that it gives everyone the same starting point.

Common failure modes in Selenium Grid 4

Here are the issues that show up repeatedly in production-like test setups.

1. The test is not actually isolated

If one worker changes state another worker depends on, the grid is not the problem. The suite design is.

2. Browser startup is slower than expected

Containers, image pulls, CPU limits, and antivirus software can all affect startup time. Do not assume “parallel” means “instant”.

3. The node can start a browser, but the browser cannot reach the app

This often happens with container networking, DNS, self-signed certificates, or localhost misuse.

4. Timeouts are too short for a distributed path

A timeout that was fine on a local desktop may fail in a containerized browser. Adjust waits based on the execution environment, not on wishful thinking.

5. Grid hides the real bottleneck

If one test suddenly becomes slower with parallel execution, check backend rate limits, database contention, and shared credentials.

Observability for test infrastructure

A Grid is easier to operate when you can answer three questions quickly:

  • which session is running now?
  • where did the request fail?
  • what changed since the last successful run?

That means treating browser infrastructure like production infrastructure, with logs, artifact collection, and clear ownership.

Useful artifacts include:

  • Grid logs
  • browser console logs
  • screenshots on failure
  • network traces where applicable
  • CI job metadata
  • test runner output with session IDs

If you only keep the final assertion failure, you often lose the setup context that explains why the test failed.

Selenium Grid vs managed browser testing platforms

Selenium Grid is a strong option when you want control over the execution environment and you are prepared to own the setup.

A managed platform can be a better fit when the team wants distributed execution without investing in grid maintenance. Endtest is one example, it supports migration from existing Selenium tests and uses editable, platform-native steps in a low-code workflow. That can reduce the infrastructure burden for teams that care more about test coverage and less about running browser clusters.

The tradeoff is straightforward:

  • Selenium Grid: more control, more infrastructure ownership
  • Managed platform: less administration, less control over the raw execution layer

If your team is already deep in Selenium and needs a known path forward, Grid remains valid. If your team spends too much time keeping browser nodes alive, debugging capacity issues, or managing upgrades, a simpler alternative may free up time for actual test design.

A practical rollout plan

If you are introducing Grid 4 to an existing team, do not migrate everything at once.

Phase 1, prove connectivity

  • run one test remotely
  • confirm session creation
  • confirm browser logs are accessible

Phase 2, add a small parallel suite

  • choose a handful of stable tests
  • avoid shared-state workflows
  • verify data isolation

Phase 3, introduce browser matrix coverage

  • target the tests most sensitive to browser differences
  • separate smoke coverage from full regression

Phase 4, operationalize it

  • document where logs live
  • define who owns Grid upgrades
  • establish node health checks
  • set a policy for browser version pinning

If you skip these phases, the first time the grid fails will probably be during a release window.

Conclusion

A Selenium Grid 4 tutorial is really a tutorial about distributed test execution discipline. Grid helps you run more browser sessions, but it also forces you to make test isolation, observability, and infrastructure ownership explicit.

For teams that need flexible, self-managed browser infrastructure, Grid 4 is worth learning. For teams that want distributed browser testing without becoming Grid operators, a maintained platform such as Endtest browser testing may be the simpler path.

Either way, the underlying rule is the same: make session creation visible, keep test data isolated, and treat flaky failures as evidence, not noise.