Browser options and headless execution

Configure Chrome and Firefox through options objects: headless mode, window size, download directories, profiles and per-environment settings.

The options object is where configuration lives

Almost every per-run setting - headless, window size, download path, proxy, extra capabilities - goes through an options object that you pass to the driver. Arguments are command-line flags for the browser; preferences are profile settings; capabilities are what the browser advertises to the driver.

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()
options.add_argument("--window-size=1440,900")
options.add_argument("--disable-dev-shm-usage")   # small /dev/shm in containers
options.add_argument("--no-sandbox")              # containers only, see the warning
options.add_argument("--lang=en-GB")

driver = webdriver.Chrome(options=options)
print(driver.capabilities["browserVersion"])
driver.quit()
ArgumentEffectUse when
--headless=newModern headless modeCI, and any run without a display
--window-size=W,HSets the viewportDeterministic responsive layout
--disable-dev-shm-usageAvoids the small shared memory segmentDocker and Kubernetes
--incognitoNo profile stateIsolating a run from stored data
--user-data-dir=PATHUses a persistent profileReusing a login across runs
--proxy-server=host:portRoutes traffic through a proxyCorporate networks, recording
⚠️
--no-sandbox disables a core browser security boundary and should be used only inside a disposable container. On a developer machine it turns any renderer exploit into a host compromise. If you need it, run the whole suite in Docker rather than weakening the browser on your laptop.

Headless mode and sizing

import os

def build_driver():
    options = Options()
    if os.environ.get("CI"):
        options.add_argument("--headless=new")
        options.add_argument("--disable-dev-shm-usage")
        options.add_argument("--no-sandbox")
    options.add_argument("--window-size=1440,900")
    driver = webdriver.Chrome(options=options)
    if not os.environ.get("CI"):
        driver.maximize_window()
    return driver
  • Old headless is not the same browser as the one your users run; the --headless=new mode shares the real rendering path, so it is the version to use.
  • Always set an explicit window size. A default headless viewport is smaller than a desktop one, which silently changes which responsive breakpoint your test exercises.
  • maximize_window() is meaningless in headless mode - there is no screen to maximise to.
  • Headless is faster and behaves the same for most assertions, but it will not catch anything that depends on real GPU compositing.
# force the same configuration locally as in CI
CI=1 python -m pytest tests/test_smoke.py -q

# check the real viewport your test sees
python -c "from selenium import webdriver; d=webdriver.Chrome(); print(d.execute_script('return [innerWidth, innerHeight]')); d.quit()"

Downloads, profiles and Firefox

import tempfile, pathlib
from selenium.webdriver.chrome.options import Options

download_dir = tempfile.mkdtemp(prefix="selenium-dl-")

options = Options()
prefs = {
    "download.default_directory": download_dir,
    "download.prompt_for_download": False,
    "download.directory_upgrade": True,
    "safebrowsing.enabled": True,
}
options.add_experimental_option("prefs", prefs)
options.set_capability("goog:loggingPrefs", {"browser": "ALL", "performance": "ALL"})
from selenium.webdriver.firefox.options import Options as FirefoxOptions

ff = FirefoxOptions()
ff.add_argument("-headless")
ff.set_preference("browser.download.folderList", 2)
ff.set_preference("browser.download.dir", download_dir)
ff.set_preference("browser.helperApps.neverAsk.saveToDisk", "text/csv")
driver = webdriver.Firefox(options=ff)
  • Use a fresh temporary directory per test run, or a previous run's file will satisfy an assertion about a download that never happened.
  • Chromium names a partially written file with a .crdownload suffix; wait for that suffix to disappear before asserting.
  • Firefox needs its own preference names - the Chrome prefs dictionary does nothing there.
  • A persistent --user-data-dir leaks state between runs. Prefer a saved cookie or storage state over a reused profile.

FAQ

Should tests run headless by default?
Headless in CI, headed locally while you are writing or debugging a test. The behaviour is close enough that headless is the right default for the suite, but watching a test fail in a visible browser is still the fastest way to understand why.
Why is my download test passing locally but failing in CI?
Almost always the download directory. The default path does not exist, is not writable, or differs per runner. Set the directory explicitly through options and assert on the resolved path inside that directory.

WebDriver setup Screenshots, logs and debugging flaky tests

Last refreshed 2026-09-18.