ScreenshotNeo

BlogHow-to

How to Fix ChromeDriver Hangs When Running Multiple Test Cases

Find where ChromeDriver stalls, isolate parallel sessions, verify versions, and make teardown reliable with practical Selenium fixes.

By the ScreenshotNeo team1 October 20266 min read

ChromeDriver hangs have different causes depending on where execution stops: creating a session, running a browser command, coordinating parallel workers, or shutting down. Start by locating that stage, reproduce the smallest failing case, and then increase concurrency one step at a time.

ChromeDriver is a separate executable that Selenium WebDriver uses to control Chrome. Its startup and shutdown are part of every test’s lifecycle. Keep one isolated driver session per concurrently running test, match Chrome with ChromeDriver, and always call quit() in teardown.

1. Identify exactly where the run hangs

Add timestamps before and after each lifecycle boundary. A test runner waiting for a worker is different from a WebDriver command that never returns, a failed session creation, or a browser that closes while ChromeDriver remains alive.

long mark(String name) {
    long now = System.nanoTime();
    System.out.printf("%d %s%n", System.currentTimeMillis(), name);
    return now;
}

WebDriver driver = null;
try {
    mark("before driver construction");
    driver = new ChromeDriver();
    mark("after driver construction");

    driver.get("https://example.com");
    mark("after navigation");

    // test commands...
    mark("before test end");
} finally {
    mark("before quit");
    if (driver != null) {
        driver.quit();
    }
    mark("after quit");
}

Record the Selenium binding, Chrome version, ChromeDriver version, operating system, test framework, container or Grid details, concurrency, and the first exception. Preserve ChromeDriver verbose logs and process state before killing anything.

2. Compare one test with parallel execution

  1. Run the smallest affected test alone.
  2. Run two copies concurrently.
  3. Increase workers gradually while keeping all other settings fixed.

If the test passes alone but hangs when tests overlap, inspect parallel settings, shared static driver fields, shared Chrome profiles, shared ports, and code that passes a driver between tests. Each test must own its WebDriver instance and profile. One test must never call quit() on a session another test is using.

JUnit 5 example: one driver per test

import org.junit.jupiter.api.*;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;

class CheckoutTest {
    private WebDriver driver;

    @BeforeEach
    void start() {
        driver = new ChromeDriver();
    }

    @AfterEach
    void stop() {
        if (driver != null) {
            driver.quit();
            driver = null;
        }
    }

    @Test
    void loadsCheckout() {
        driver.get("https://example.com/checkout");
        Assertions.assertTrue(driver.getTitle().length() > 0);
    }
}

For a parallel runner, do not store the driver in a process-wide singleton. If framework hooks run on different threads, use the framework’s per-test fixture or a carefully scoped ThreadLocal, and remove the value after quitting.

3. Verify Chrome and ChromeDriver versions

Chrome and ChromeDriver versions should match; Selenium’s Chrome guidance says a mismatch causes the driver to error. [ChromeDriver lifecycle documentation] [Selenium Chrome documentation]

google-chrome --version
chromedriver --version
java -version

Also record the Selenium library version. Do not assume that a historical issue against an older release describes your current release. Upgrade or pin the browser, driver, and Selenium versions together in your CI image, then rerun the single-test comparison.

4. Make teardown unconditional

Put driver.quit() in a finally block or your test framework’s guaranteed teardown hook. quit() ends the WebDriver session and should terminate the associated service process. If it does not return, collect logs and process information first.

WebDriver driver = null;
try {
    driver = new ChromeDriver();
    runTest(driver);
} catch (RuntimeException failure) {
    // Keep the original failure; teardown still runs.
    throw failure;
} finally {
    if (driver != null) {
        try {
            driver.quit();
        } catch (RuntimeException cleanupFailure) {
            System.err.println("Driver cleanup failed: " + cleanupFailure);
        }
    }
}

A browser that closes while a ChromeDriver process remains is a cleanup failure, not proof that every hang has the same cause. Save the driver log, process list, and timestamps before environment-specific cleanup.

5. Check for shared resources

Resource Failure pattern Fix
Chrome profile directory Sessions block or fail to start Give each worker a unique temporary --user-data-dir.
Driver variable One test closes another test’s browser Use per-test fixtures; remove global mutable state.
Ports Workers wait for a service or cannot bind Let ChromeDriver choose ports or allocate unique ports.
DevTools connection Parallel sessions stall during startup or commands Keep sessions isolated and capture verbose logs.
Grid node Session creation waits indefinitely Check node capacity, queue state, and the Grid server log.

Historical Selenium reports describe parallel-thread, session-creation, and cleanup hangs in different environments. They are diagnostic examples, not evidence of one universal defect: issue 11703, issue 13182, docker-selenium issue 87, and issue 10863.

6. Capture useful evidence

  • Exact Chrome, ChromeDriver, Selenium, operating-system, container, Grid, and test-runner versions.
  • Worker count and whether the run is local, containerized, or remote.
  • Timestamps before and after construction, navigation, each long command, and quit.
  • The first exception and complete ChromeDriver verbose output.
  • Process state while the hang is active, including parent and child processes.
  • The smallest test and the concurrency level that first reproduces the problem.

Change one variable at a time. A downgrade, timeout, or Chrome flag is not a general fix without evidence that it changes the failing stage.

7. Useful Chrome options for controlled diagnostics

Keep diagnostic options minimal. A temporary unique profile can separate profile locking from other causes:

ChromeOptions options = new ChromeOptions();
options.addArguments("--user-data-dir=/tmp/chrome-profile-" +
        java.util.UUID.randomUUID());
WebDriver driver = new ChromeDriver(options);

In containers, verify that Chrome can start with the image’s user, shared-memory settings, and sandbox policy. Treat any container flags as environment-specific; record them with the reproduction instead of copying a flag from an unrelated issue.

8. Performance, reliability, and cost considerations

  • More workers increase throughput only while CPU, memory, file descriptors, ports, and Grid capacity remain available. Increase concurrency gradually.
  • One isolated browser per test uses more memory than a shared browser, but prevents cross-test state and teardown races.
  • Reuse a session only when tests are intentionally serialized and the fixture guarantees reset state; never share it across parallel workers.
  • Set command and page-load timeouts for genuinely slow operations, but do not use a large timeout to hide a deadlocked worker.
  • Always retain the first failure. A later cleanup exception can otherwise obscure the original cause.

Or skip the browser setup

If your goal is to capture pages rather than maintain Selenium sessions, ScreenshotNeo provides a single HTTP request for PNG, JPEG, WebP, or PDF output. It accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.

See the ScreenshotNeo API documentation for all options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

There are 1,000 free screenshots each month with no card. Paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

Common errors and fixes

Symptom Likely cause Action
Session never starts Version mismatch, unavailable binary, profile lock, or exhausted Grid capacity Check versions and paths, use a unique profile, inspect Grid and driver logs.
Only parallel runs hang Shared driver, profile, port, or worker resource Run one test, isolate every resource, then raise concurrency gradually.
Navigation never returns Page load, network, or browser process issue Timestamp navigation, inspect browser and driver logs, and verify the URL independently.
Quit hangs or leaves processes Environment-specific cleanup failure Capture process state and logs before applying controlled cleanup.
Later tests fail after one exception Teardown skipped or shared state leaked Use unconditional teardown and reset per-test state.

FAQ

Should I reduce parallelism permanently?

Use one worker as a diagnostic baseline. Keep the highest concurrency that remains reliable after sessions and resources are isolated.

Does a matching version guarantee no hangs?

No. Matching versions remove one common startup cause; profile locks, Grid capacity, shared state, and cleanup can still hang.

Should I kill ChromeDriver processes in CI?

Only after collecting evidence and ensuring the process belongs to the failed run. Fix lifecycle ownership and teardown first.

Where should I report a reproducible defect?

Include the minimal test, exact versions, operating system or image, concurrency, logs, timestamps, and whether the failure occurs during creation, commands, or quit.