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()| Argument | Effect | Use when |
|---|---|---|
--headless=new | Modern headless mode | CI, and any run without a display |
--window-size=W,H | Sets the viewport | Deterministic responsive layout |
--disable-dev-shm-usage | Avoids the small shared memory segment | Docker and Kubernetes |
--incognito | No profile state | Isolating a run from stored data |
--user-data-dir=PATH | Uses a persistent profile | Reusing a login across runs |
--proxy-server=host:port | Routes traffic through a proxy | Corporate 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=newmode 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
.crdownloadsuffix; 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-dirleaks 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.
Related
WebDriver setup Screenshots, logs and debugging flaky tests
Last refreshed 2026-09-18.