Every engineer maintaining a UI testing pipeline has encountered this specific failure: a perfectly healthy CI/CD pipeline suddenly halts, throwing a SessionNotCreatedException. The error logs reveal the culprit is a ChromeDriver version mismatch.
This occurs when the execution environment updates the Chrome browser to a new major version overnight, rendering the previously installed ChromeDriver incompatible. Historically, QA and DevOps teams resorted to brittle workarounds like disabling OS-level update services or relying on third-party driver-manager packages.
Google and the Selenium project have recently standardized a permanent solution. By migrating your infrastructure to Chrome for Testing binaries and leveraging modern Selenium 4 capabilities, you can achieve a zero-maintenance, deterministic CI/CD pipeline test fix.
The Root Cause of WebDriver Version Mismatch
To understand the fix, you must understand how Google Chrome behaves under the hood.
Standard Google Chrome is a consumer product. It includes a background auto-update mechanism (Omaha on Windows, update_engine on macOS/Linux) designed to push security patches immediately. Standard Chrome cannot easily be pinned to a specific version.
Conversely, ChromeDriver strictly enforces major-version parity with the browser. If your pipeline server pulls Chrome 122 via an OS package manager, but your dependency cache supplies ChromeDriver 121, the Selenium Server rejects the session creation request.
Enter Chrome for Testing (CfT)
To address the needs of QA automation tools, Google introduced Chrome for Testing binaries. CfT is a dedicated flavor of Chrome that strips out the auto-updater mechanism entirely. It integrates cleanly into automated workflows, ensuring that the browser version on your runner remains strictly immutable until you explicitly bump it in your source control.
The Modern Fix: Selenium Manager and CfT
Since Selenium version 4.11.0, the framework includes a built-in binary management tool called Selenium Manager. If you request a specific browser version in your code, Selenium Manager will automatically download the exact matching Chrome for Testing binaries and the corresponding ChromeDriver into your local cache (~/.cache/selenium).
You no longer need to manually download drivers or use tools like webdriver-manager.
Step 1: Update Your Test Configuration (TypeScript)
Update your automation codebase to utilize the latest selenium-webdriver package. Configure your chrome.Options to declare a strict browser version.
import { Builder, Browser } from 'selenium-webdriver';
import chrome from 'selenium-webdriver/chrome';
/**
* Initializes a deterministic Selenium WebDriver session using Chrome for Testing.
*
* By specifying `setBrowserVersion`, Selenium Manager automatically intercepts
* the startup process, downloading the exact CfT binary and matching ChromeDriver.
*/
export async function createDeterministicDriver(version: string = '122.0.6261.128') {
const options = new chrome.Options();
// Explicitly request a specific Chrome for Testing version
options.setBrowserVersion(version);
// Use the modern headless mode implementation (--headless=new)
options.addArguments(
'--headless=new',
'--disable-gpu',
'--no-sandbox',
'--disable-dev-shm-usage'
);
const driver = await new Builder()
.forBrowser(Browser.CHROME)
.setChromeOptions(options)
.build();
return driver;
}
By passing a specific version string to setBrowserVersion(), you instruct Selenium Manager to bypass any consumer version of Chrome installed on the CI runner and instead provision the exact required CfT binary.
Step 2: Optimize the CI/CD Pipeline Configuration
While Selenium Manager handles the downloading automatically, downloading a 130MB+ browser binary on every pipeline execution adds latency. To finalize the CI/CD pipeline test fix, you must cache the Selenium Manager directory.
Below is an optimized GitHub Actions workflow demonstrating how to execute Selenium automated testing with robust caching.
name: UI Automation Pipeline
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
cache: 'npm'
- name: Install Dependencies
run: npm ci
# Cache the Selenium Manager directory to prevent downloading CfT on every run
- name: Cache Chrome for Testing Binaries
uses: actions/cache@v4
with:
path: ~/.cache/selenium
key: ${{ runner.os }}-selenium-cft-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-selenium-cft-
- name: Execute E2E Tests
run: npm run test:e2e
Deep Dive: Why This Architecture Works
When the Builder().build() command is invoked, Selenium Manager performs a specific sequence of operations:
- Discovery: It checks the local system
PATHfor the requested browser version. - Resolution: If the version is missing (or if a specific version like
122.0.6261.128is requested but not found locally), it queries the official CfT JSON endpoints (googlechromelabs.github.io/chrome-for-testing). - Provisioning: It downloads both the browser and the matching ChromeDriver directly into
~/.cache/selenium/. - Execution: It bypasses the system's default Chrome registry paths and explicitly points the WebDriver process to the newly downloaded CfT executable.
Because the caching mechanism in the CI pipeline saves the state of ~/.cache/selenium/, subsequent runs skip the provisioning step entirely. This results in execution speeds comparable to having pre-installed runner images, but with absolute version determinism.
Common Pitfalls and Edge Cases
Corporate Firewalls Blocking CfT Endpoints
In highly regulated enterprise environments, CI/CD runners may not have public internet access. If your runner cannot reach the CfT JSON endpoints, Selenium Manager will fail silently or throw a timeout error.
The Solution: You must configure Selenium Manager to use an internal proxy or artifact repository. Currently, you can point Selenium Manager to an offline cache by pre-populating the ~/.cache/selenium directory in your custom Docker image before pushing it to your private container registry.
Specifying "Stable" vs. Strict SemVer
Selenium Manager allows you to pass the string "stable" instead of a strict semantic version number (e.g., options.setBrowserVersion('stable')).
While this avoids hardcoding versions in your repository, it undermines the goal of absolute determinism. If Google promotes a new stable release while your pipeline is queuing, your cache key will miss, and the pipeline will download the new version. Always use strict Semantic Versioning (MAJOR.MINOR.BUILD.PATCH) in CI/CD environments.
Outdated Selenium Client Libraries
This architecture strictly requires Selenium 4.11.0 or higher. If your project is bound to Selenium 3.x, the modern setBrowserVersion API will not invoke Selenium Manager, as the binary manager did not exist in older builds. You will fall back to standard system discovery, and the ChromeDriver version mismatch will persist. Prioritize upgrading your language bindings to the latest stable release.