skip to content

A Selenium suite leaves chromedriver processes and temp profile directories on the CI agent - what causes that and how do you fix it?

level: seniorimportance: should knowfreq 55%

answer

  1. Ask what was never released
  2. Which teardown call reaches the driver?
  3. Only one command stops the executable
  4. The temp profile dies with the session
  5. Look for a create with no delete

basics

~20 s

Each leftover process is a session that was never deleted. In Selenium 4 only quit() sends Delete Session and stops the driver executable the client launched, so close-only teardown and killed runs leave both behind.

solid answer

~40 s

Leftover `chromedriver` processes mean sessions that were never deleted. In Selenium 4 the Java client stops the driver executable it launched only when the quit command runs, so any path that skips `quit()` - teardown that calls `close()` instead, a run killed on a job timeout, an agent that reboots mid-suite - leaves the executable holding its port and often the browser too. The temp directories come from the same place: ChromeDriver starts Chrome with a generated `--user-data-dir` under the system temp directory when you supply no profile, and deletes it while handling Delete Session, never on a kill. Diagnose by matching orphan process start times against test start times and looking for new sessions with no matching delete in the driver log. Fix the teardown, then add a pre-run sweep as a backstop.

code

java · 10 lines
java
WebDriver driver = new ChromeDriver();
try {
    driver.get("https://give.example.org/donate");
    driver.findElement(By.id("amount")).sendKeys("25");
    driver.findElement(By.id("gift-aid")).click();
    driver.findElement(By.id("donate-now")).click();
    System.out.println(driver.findElement(By.id("receipt-id")).getText());
} finally {
    driver.quit();
}

go deeper

for a junior

Know that leftover driver processes mean sessions that were never ended, and that quit is the call that ends one. You are not expected to diagnose the agent yet, only to name the missing call.

for a middle

Explain why close-only teardown leaves the driver executable running even though the browser disappeared, and why the temporary profile directory is removed while the session is deleted rather than when a process dies.

for a senior

Walk the diagnosis out loud: match orphan start times to test start times, look for a created session with no matching delete in the driver log, fix the teardown, then add a pre-run sweep as a backstop.

for a principal

Own the standard for the fleet: what a runner guarantees between jobs, who is accountable when a shared agent fills its disk, and whether disposable runners are worth their provisioning cost.

A leftover `chromedriver` on an agent is neither a Selenium bug nor the browser vendor's doing: it is a session that was never deleted. In **Selenium 4** a local browser test involves three independent operating-system processes - your client, the driver executable it launched, and the browser that executable launched - and exactly one command tears the whole chain down. ## Start from what holds each resource | resource | released by | left behind by | |---|---|---| | the session and its windows | **Delete Session**, `DELETE /session/{session id}` | any path that skips `quit()` | | the browser process | the driver, while it handles Delete Session | an abandoned session, or a killed driver | | the generated profile directory | the driver, while it handles Delete Session | a killed driver, or a session never deleted | | the driver executable | the client, once the quit command completes | `close()`-only teardown, or a killed client | Read the table backwards from the symptom. Orphaned `chromedriver` processes but no orphaned browsers point at teardown that ended sessions without quitting. Orphaned browsers as well point at sessions that were simply abandoned. A growing temp directory with no live processes points at something killing the driver rather than asking it to shut down. ## Why close()-only teardown is the usual culprit - `close()` sends **Close Window**, not Delete Session. It shuts the focused top-level browsing context and touches nothing else. - When it happens to shut the last window, the session ends and the browser exits - which is why such a suite looks healthy on screen while driver processes pile up invisibly behind it. - The client stops the driver service **only** when the quit command runs. A suite that never calls `quit()` never gives it the chance, so every test contributes one more executable holding a port. - A test that closed one of two windows is worse: the session is still alive, so the browser is alive, and its profile directory stays on disk for the life of the agent. ## The paths where quit() genuinely cannot run 1. The job hits its wall-clock limit and the agent kills the client. A kill signal does not run a `finally` block, so Delete Session is never sent. 2. The client dies another way - the machine reboots, the run environment is stopped, the kernel's out-of-memory killer picks the JVM. 3. The connection to the driver or to a remote end breaks before the delete command lands, orphaning the session on the far side. Where the `quit()` call is written - which fixture, hook or base class owns it - is a framework-structure decision and belongs to whoever owns your suite's layout. The question at this level is narrower and mechanical: did Delete Session actually reach the driver? ## Reading the evidence on the agent - Compare the **start times** of the orphan processes with the start times of your tests. One orphan per test points at teardown; a cluster sharing a timestamp points at a killed run. - Read the **driver log**. Both `chromedriver` and `geckodriver` log the commands they serve, so a session id that appears at creation and never appears again at deletion is your leak, named precisely. - Watch the **system temp directory** grow. One generated user-data directory per abandoned session is a slow, quiet disk leak that surfaces weeks later as a build failure with no obvious cause. - On a remote run, ask the remote end rather than the agent: an abandoned session stays visible there, holding its browser and its slot, until that end's own session timeout reaps it. ## The fix, and the backstop that is not a fix - **The fix is `quit()`.** Every session ends with it, including sessions whose windows you already closed by hand. It is the only call that both deletes the session and stops the executable your client started. - Put it in a `finally` so the ordinary failure path - a check that throws halfway through the donation-checkout flow - still reaches it. - **The backstop is a sweep**, and it is a backstop: before each run, remove stale driver and browser processes and clear the temp directory the agent uses. Treat everything it reaps as a defect report. A sweep that finds nothing is the goal, not a wasted step. - Resist the shortcut of killing driver processes instead of quitting sessions. A kill never runs the driver's own cleanup, so the profile directory it would have deleted stays behind - you trade a process leak for a disk leak and lose the log line that would have told you which test was at fault.

  • Why does killing the driver process leave its temporary profile directory behind?
    Because removing that directory is part of ending the session: the driver deletes the user-data directory it generated while it handles Delete Session. A kill signal never reaches that code, so the directory survives until something sweeps the temp filesystem - which is why a process killer trades one leak for another.
  • The suite runs against a Grid instead of a local browser. What changes about the leak?
    Your client no longer owns a driver executable, so nothing accumulates on the agent. The residue moves to the node, where an abandoned session keeps its browser and its slot until the node's own session timeout reaps it. `quit()` is what releases it immediately; a killed client does not.
  • How would you prove to yourself that the leak is actually fixed?
    Count before and after. Record driver and browser process counts plus the size of the agent's temp directory immediately before and after a full run; a fixed suite returns both to their starting values. Keep the check running, because one new test with the wrong teardown reintroduces the leak silently.

saying these in an interview costs you the question

  • Blames the browser vendor instead of a session that was never deleted
  • Says close() also shuts down the chromedriver process
  • Thinks killing chromedriver cleans up its temporary profile directory
  • Assumes the operating system reclaims a browser when the test client exits
  • Schedules a process killer instead of fixing the missing quit()