How to Build a CI Pipeline With CircleCI and Selenium Grid
Run Selenium tests on CircleCI with a reachable Grid endpoint, reliable startup checks, saved test results, and concurrency sized for your browsers and CI resources.
To run Selenium tests on CircleCI, configure a job in .circleci/config.yml, make a Selenium Grid endpoint reachable from the test process, wait for Grid readiness, run the tests with a remote WebDriver client, and save test results and useful logs. For a small, disposable run, use Grid Standalone in the job’s network. For multiple machines, browser versions, or operating systems, connect the job to a private shared Grid and use its Hub or Router URL.
The correct WebDriver URL depends on the network topology. http://localhost:4444 works only when the test process and Grid share the relevant network namespace or CircleCI network arrangement. In a CircleCI Docker executor with a Selenium service container, use the service’s reachable hostname and port. In a shared Grid setup, use the private Hub or Router address. Selenium Grid routes WebDriver commands to browser instances and supports parallel sessions and cross-browser coverage. Selenium Grid documentation
1. Choose where Grid runs
Decide how the test job will reach the Grid before writing its configuration. The job executor, service containers, and any remote Docker environment can have different network boundaries.
| Pattern | When it fits | Client endpoint | Tradeoff |
|---|---|---|---|
| Grid Standalone in the job network | A small, disposable run that needs one Grid deployment | The address reachable from the test container, commonly a service hostname and port 4444 | Simple setup; browser capacity and coverage are limited to that deployment |
| Private shared Hub and Nodes | Tests need more capacity or nodes with different browsers, versions, or platforms | The Hub address | Requires maintaining reachable Grid components and controlling access |
| Private Distributed Grid | Grid components need to be distributed across infrastructure | The Router address | Offers a more distributed deployment, with more components to operate |
Grid Standalone defaults to port 4444. In Hub-and-Node mode, nodes must communicate with the Hub and Event Bus; Selenium documents default Event Bus ports 4442 and 4443. A test client should call the Hub or Router entry point, not an arbitrary node. Review the [Grid endpoint documentation](https://www.selenium.dev/documentation/grid/advanced_features/endpoints/) and [Grid setup guide](https://www.selenium.dev/documentation/grid/getting_started/) for the selected mode.
CircleCI’s Docker executor runs job steps in the first, primary image. Secondary service containers can share a network with that primary container, but this does not mean that every host or remote Docker daemon shares its localhost. CircleCI recommends the machine executor when Docker Compose must manage a multi-container setup. Treat remote Docker networking and volumes as a separate topology. See [CircleCI’s Docker executor guide](https://circleci.com/docs/guides/execution-managed/using-docker/) and [Docker Compose guide](https://circleci.com/docs/guides/execution-managed/docker-compose/).
2. Add a CircleCI job
CircleCI pipelines are configured in the repository, usually at .circleci/config.yml. A workflow invokes jobs when the project’s configured triggers match. Pin the primary image to a deliberate version instead of relying on latest. Choose a runtime image that matches the project and includes the dependencies needed to run its tests.
This is a topology-aware template, not a copy-and-run universal Grid deployment. Replace the placeholders with the project’s actual runtime, Grid service image and tag, readiness check, test command, and report directory. Set GRID_URL to the hostname and port that the test container can actually resolve.
version: 2.1
jobs:
browser-tests:
docker:
- image: cimg/<runtime>:<pinned-tag>
# Add a compatible Selenium Grid Standalone service image here
# if Grid is a secondary container in this job's shared network.
environment:
GRID_URL: http://<reachable-grid-host>:4444
steps:
- checkout
- run:
name: Install project dependencies
command: <install dependencies for this repository>
- run:
name: Wait for Selenium Grid
command: <poll the Grid status endpoint until ready or timeout>
- run:
name: Run browser tests
command: <invoke the repository's test command>
- store_test_results:
path: <test-results-directory>
- store_artifacts:
path: <useful-logs-or-test-artifacts-directory>
workflows:
browser-tests:
jobs:
- browser-tests
CircleCI’s browser testing page shows Selenium started as a background process, but it is an example rather than a current, production-ready Grid recipe. Do not reuse its old Selenium 3.5 download URL as a version recommendation. Select and pin compatible Selenium server, browser, and runtime versions using the current Selenium Grid setup documentation. CircleCI’s [pipeline guide](https://circleci.com/docs/guides/orchestrate/pipelines/) explains workflows and jobs; its [automated testing guide](https://circleci.com/docs/guides/test/test/) covers test output and result integration.
3. Wait for Grid, then connect the test client
Grid startup is asynchronous. Poll a readiness or status endpoint from the same network context as the tests, with a finite timeout. A fixed short sleep can pass on one run and race on another. Use the endpoint and readiness semantics supported by the Grid version you pinned; Selenium documents Grid endpoints in its [endpoint guide](https://www.selenium.dev/documentation/grid/advanced_features/endpoints/).
For Java, Selenium’s remote client uses RemoteWebDriver with the Grid URL and browser options. This method is runnable once the project has a compatible Selenium Java dependency and the GRID_URL environment variable is set to a reachable Grid endpoint:
import java.net.URL;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.remote.RemoteWebDriver;
public class GridSmokeTest {
public static void main(String[] args) throws Exception {
String endpoint = System.getenv("GRID_URL");
if (endpoint == null || endpoint.isBlank()) {
throw new IllegalStateException("Set GRID_URL to the reachable Grid endpoint");
}
WebDriver driver = new RemoteWebDriver(new URL(endpoint), new ChromeOptions());
try {
driver.get("https://example.com");
System.out.println("Page title: " + driver.getTitle());
} finally {
driver.quit();
}
}
}
Use the browser options and capabilities supported by the Grid nodes. If a requested browser or capability is unavailable, the session may fail to start. Always call quit() in a finally block or framework teardown so the session releases its Grid slot, including when an assertion fails.
4. Save test results and diagnose failures
Configure the test framework to write JUnit-style XML or another result format CircleCI can ingest, then set store_test_results.path to that actual directory. Store Selenium server logs and relevant browser-test artifacts when useful. A placeholder path that does not match the framework’s output will not preserve results.
When a run fails, inspect evidence from both sides of the WebDriver connection:
- CircleCI job output: dependency installation, readiness polling, test command, and framework errors.
- Grid status and server logs: node registration, available slots, requested capabilities, and session creation failures.
- Test results and artifacts: failing test names, assertion details, screenshots or logs produced by the framework.
- Network reachability from the test container: hostname resolution and connection to the configured Grid endpoint.
CircleCI’s browser guide documents background Selenium startup and its test guide documents result collection. Neither prescribes one universal readiness command or result directory; those depend on your Grid version and test framework.
5. Plan parallelism around measured capacity
Grid distributes WebDriver sessions across available browser capacity. Add concurrency only when the nodes have enough CPU and memory for the browsers and test workload. Selenium’s Grid setup guidance gives around 1 GB of RAM per browser session as an operational recommendation and describes a default concurrent-session limit tied to available processors. Selenium also says these recommendations may not fit every environment; measure the workload rather than treating them as guarantees. [Grid setup and sizing guidance](https://www.selenium.dev/documentation/grid/getting_started/)
Estimate the number of sessions you can run at once from the tightest limit: available Grid slots, CPU, memory, CircleCI job resources, and any application-side test constraints. More sessions can increase queueing or instability when they oversubscribe a node. There is no universal speedup figure: total runtime depends on test duration, resource limits, queueing, and available slots. Selenium explains parallel execution in its [When to Use Grid guide](https://www.selenium.dev/documentation/grid/applicability/).
| Decision | Standalone in job | Shared or distributed Grid |
|---|---|---|
| Setup and operations | One disposable service per run is easier to start and discard | Requires operating the Hub or Router and browser nodes |
| Browser and platform coverage | Limited to browsers available in that deployment | Nodes can provide distinct browser versions and platforms |
| Concurrency | Bound by the job’s Grid and compute resources | Can use more node capacity, subject to measured limits |
| Isolation | Can keep the service within the job network | Needs private routing and network controls |
| Failure diagnosis | Review job and local Grid logs together | Review job output, Grid status, node logs, and saved results |
6. Keep the Grid private
Do not expose an unprotected Grid endpoint to the public internet. Selenium warns that an unprotected Grid can expose internal applications and allow third parties to run custom binaries. Keep an ephemeral Grid inside the CI network, or put a shared Grid behind appropriate network controls. Expose only the ports required for the chosen topology. In Hub-and-Node mode, account for Event Bus ports 4442 and 4443 where required. See Selenium’s [Grid security guidance](https://www.selenium.dev/documentation/grid/getting_started/).
7. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Connection refused | Grid is still starting, is not listening on that port, or the endpoint points to the wrong host | Poll readiness with a timeout; verify the Grid process, port, and endpoint from the test container. |
| Unknown host or DNS failure | The configured service hostname is not resolvable from the job’s network | Use the hostname configured for the CircleCI service or the private shared Grid address reachable from the test process. |
| Works locally, fails in CircleCI | The local setup assumes shared localhost, host networking, or volumes that the CircleCI executor does not provide |
Check the executor and network topology. With Docker Compose, consider CircleCI’s machine executor guidance. |
| Session creation fails or capability is rejected | No node offers the requested browser or version, or the capability does not match what the node supports | Check Grid status and node capabilities; align the test options with installed browsers and pinned versions. |
| Tests hang while waiting for a browser | Grid slots are occupied, a prior session was not quit, or demand exceeds node capacity | Ensure teardown calls quit(), inspect active sessions and node logs, and reduce concurrency until capacity is measured. |
| Intermittent startup failures | A fixed sleep races with variable Grid startup time | Poll the supported status endpoint until ready, with a finite deadline and a useful failure message. |
| CircleCI shows no test results | The configured result path does not match the test framework’s output, or the framework did not produce files | Check the job workspace and framework configuration, then set store_test_results.path to the generated results directory. |
| Node never joins Hub | Hub or Event Bus host and ports are unreachable, or node registration settings do not match the topology | Check component addresses and firewall rules, including Event Bus ports 4442 and 4443 where applicable. |
| Runs slow down after adding parallel workers | CPU, memory, Grid slots, or the application under test are saturated | Measure resource use and queueing, then adjust concurrency or add suitable node capacity. |
Or skip the browser setup
If the goal is to capture a page image or PDF rather than run interactive browser assertions, ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF. The call below uses the documented API parameters; see the ScreenshotNeo API documentation for options and response details.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
ScreenshotNeo accepts cookie and consent banners like a visitor, then removes 60+ known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server gives AI agents tools for screenshots, page information, and PDF capture. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots. Learn about ScreenshotNeo or sign up for 1,000 free screenshots a month, with no card.
Frequently asked questions
Does Selenium Grid replace the test framework?
No. The test framework still defines and runs tests. Grid provides remote browser sessions for the WebDriver client.
Can CircleCI run browser tests without Grid?
Yes, depending on the test setup. This guide covers remote WebDriver through Grid, which is useful when you need remote or distributed browser capacity.
Should I use an ephemeral or shared Grid?
Use an ephemeral Standalone deployment for a small isolated run when its browser capacity is enough. Use a private shared or distributed Grid when you need more nodes, browser coverage, or capacity and can operate its network and security boundaries.
Can screenshots prove that a UI test passed?
A screenshot captures a page state; it does not replace test assertions, browser interaction, or framework results. Use WebDriver tests to validate behavior, and use screenshot capture when an image artifact is the deliverable.


