skip to content

In Selenium, why does pinning one --user-data-dir in ChromeOptions break a parallel run?

level: juniorimportance: should knowfreq 40%

answer

  1. Two browsers, one directory
  2. The flag is passed through untouched
  3. Chrome locks a live profile directory
  4. Unset is safer than pinned
  5. Firefox copies a profile, Chrome does not

basics

~20 s

Chrome passes --user-data-dir straight through, and only one live browser may own a profile directory. Pinning the same path across workers makes every worker but one fail to start, and leaks state between the ones that run in turn.

solid answer

~40 s

`ChromeOptions.addArguments("--user-data-dir=/path")` is handed to the browser binary verbatim, so every worker running that code gets the same directory. Chrome treats a live user data directory as single-instance: the second worker's browser either refuses to start or has its launch forwarded to the instance already holding the directory, so the driver never gets the browser it waited for and the new-session call fails with `SessionNotCreatedException`. Workers that acquire the directory in turn also inherit each other's cookies, `localStorage` and cache. The default is already safe: with no `--user-data-dir` set, the driver creates a temporary profile per session. Derive the path per worker instead, for example `Files.createTempDirectory("museum-catalogue-worker-")`. Firefox differs usefully: `FirefoxOptions.setProfile(new FirefoxProfile(templateDir))` copies the template into a fresh temporary directory per instance, while `addArguments("-profile", path)` passes the path through unchanged.

code

java · 18 lines
java
import java.nio.file.Files;
import java.nio.file.Path;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;

public class MuseumCatalogueWorker {

  public static WebDriver openCatalogue() throws Exception {
    Path profile = Files.createTempDirectory("museum-catalogue-worker-");
    ChromeOptions options = new ChromeOptions();
    options.addArguments("--user-data-dir=" + profile.toAbsolutePath());

    WebDriver driver = new ChromeDriver(options);
    driver.get("https://museum.example/exhibits");
    return driver;
  }
}

go deeper

for a junior

Be ready to say what a user data directory holds and why two browsers cannot own one at the same time. Knowing that leaving the flag unset is the safe default is most of the answer.

for a middle

Explain the mechanics: the argument is passed to the browser verbatim, the browser enforces single-instance ownership through a lock file, and a failed new-session call is what surfaces as SessionNotCreatedException.

for a senior

An interviewer expects you to spot the pinned path in review before it reaches CI, and to fix it by deriving the directory per worker rather than by serialising the suite or adding retries.

for a principal

Own the pattern, not the instance. Decide how the harness supplies per-worker identity and state so a shortcut like a shared signed-in profile never becomes the cheapest thing for a team to reach for.

## What `--user-data-dir` actually is Chrome keeps everything a browsing identity owns - cookies, `localStorage`, the HTTP cache, extensions, download history, and a lock file marking the directory as in use - under a single **user data directory**. `ChromeOptions.addArguments("--user-data-dir=/path")` hands that path to the browser binary **verbatim**. Selenium does not copy it, does not namespace it, and does not check whether anything else already holds it. Whatever string you write is the string every worker running that code receives. ## The default is already parallel-safe - When your options carry **no** `--user-data-dir`, the driver creates a **fresh temporary profile directory** for that session and discards it when the session ends. Firefox's driver does the same, and `GeckoDriverService.Builder.withProfileRoot(File)` exists precisely to choose which filesystem those temporary profiles land on. - That default is why a naive parallel run of a museum exhibit catalogue suite works at all: eight workers get eight profile directories without anyone configuring one. - The collision is therefore something you **opt into**, almost always as a shortcut. Someone signs in to the catalogue's curator area by hand once, keeps the resulting profile at `/home/ci/museum-curator`, and pins it so every test skips the sign-in step. - Serially that shortcut works and survives review. It fails the first time two workers run together. ## What the browser does with a directory that is already live Chrome treats a live user data directory as **single-instance**: one browser process tree may own it at a time, and the lock file inside it is how that is enforced. A second browser told to use the same directory does not quietly get its own copy. Either it refuses to start, or its launch is forwarded to the instance already holding the directory and the new process exits immediately - and in both cases the driver never gets the browser it was waiting for, so the new-session command fails and the Java binding raises `SessionNotCreatedException`, whose message opens with "Could not start a new session". It reads as flake because it depends on which worker won the race for the directory, and the worker that reports the failure is not the one that took it. ## The fix: one directory per worker, created when the driver is 1. Create the directory **at driver-construction time**, not in a static field and not in a shared constant: `Path dir = Files.createTempDirectory("museum-catalogue-worker-")`. 2. Pass it through: `options.addArguments("--user-data-dir=" + dir.toAbsolutePath())`. 3. Delete it when that worker's session ends, so a long catalogue regression run does not fill the agent's disk with abandoned profiles. If you genuinely need signed-in state, seed it into each fresh directory rather than sharing one. The **sharing**, not the reuse, is what breaks. ## Firefox has a different shape, and it is easy to get wrong the other way | What you write in Selenium 4's Java bindings | What the browser is given | Safe beside a copy of itself | |---|---|---| | `ChromeOptions.addArguments("--user-data-dir=/p")` | `/p`, verbatim | **No** - every worker shares `/p` | | `ChromeOptions` with no `--user-data-dir` | a per-session temporary directory | **Yes** | | `FirefoxOptions.setProfile(new FirefoxProfile(new File("/p")))` | a fresh temporary **copy** of `/p` | **Yes** | | `FirefoxOptions.addArguments("-profile", "/p")` | `/p`, verbatim | **No** | `FirefoxProfile` is a **template**, not a live directory. Its `layoutOnDisk()` copies the model directory into a new temporary directory for that instance and deletes lock files on the way, and `new ProfilesIni().getProfile(name)` copies a named profile for the same reason. So the profile *object* API is accidentally parallel-safe, while Firefox's own `-profile` **argument** is not: it is passed straight through, exactly like Chrome's flag. ## What a pinned profile costs beyond the failed start Workers rarely hold the directory at the same instant, but under parallel scheduling they hold it **in turn**, and everything one worker wrote is waiting there for the next one that acquires it. - **Identity bleeds.** Cookies and `localStorage` survive, so the curator one worker signed in as becomes the identity the next worker starts with on the catalogue admin. - **Cache bleeds.** A stale exhibit thumbnail cached by one worker is served to the next, and cache-related bugs reproduce only under parallel load. - **Writes accumulate.** Preferences, download history and session-restore state all persist into the following worker's run. - **Failures are unattributable.** The worker reporting the failure is rarely the worker that caused it. The profile directory is only one of the things a parallel Selenium worker must hold on its own. Its driver process's listening port, the download directory the browser writes into, and the browser's memory footprint on the host are separate concerns with separate fixes. Getting `--user-data-dir` right removes one specific class of parallel failure and no others.

  • If nobody sets --user-data-dir at all, what profile does each Chrome session actually use?
    The driver creates a fresh temporary profile directory for that session and discards it when the session ends, so every session starts from a clean, unshared profile. Firefox's driver behaves the same way, and `GeckoDriverService.Builder.withProfileRoot(File)` exists to choose which filesystem those temporary profiles are created on.
  • A team pins the directory to reuse a signed-in curator profile and skip login. What would you suggest instead?
    Keep the reuse, drop the sharing. Treat the signed-in directory as a template and give each worker its own copy, or seed the session state per worker after the browser starts. In Selenium's Java Firefox bindings `FirefoxProfile` already works this way: it copies the model directory into a fresh temporary directory for every instance.

A pinned user data directory is one museum locker with one key handed to eight visitors at once: the first one in gets their coat, and everybody else is either locked out or wearing somebody else's.

saying these in an interview costs you the question

  • Claims Selenium makes the user-data-dir unique per session automatically when you set it
  • Thinks two Chrome sessions can safely share one live profile directory
  • Believes a shared profile only affects speed, not test correctness
  • Confuses Chrome's --user-data-dir with Firefox's FirefoxProfile copying behaviour
  • Fixes the collision with a lock or a sleep instead of a per-worker directory