What Headless Mode Actually Does
Headless mode runs a real browser engine — the same Chromium or Gecko rendering pipeline used in headed mode — but without creating an actual visible window or compositing to a screen buffer. This means DOM parsing, CSS layout, and JavaScript execution all behave identically to the headed browser; what's skipped is the GPU-composited window rendering, which is exactly why headless runs are faster and lighter on memory. It is not a lightweight simulation or a different engine — a headless Chrome instance passes the same JavaScript conformance tests as headed Chrome because it's the same V8 engine underneath.
Cricket analogy: It's like a team running a full net practice session with real bowling machines and real bats, just without spectators in the stands — the actual cricket happening is identical, only the audience-facing display is absent.
Enabling Headless Mode
Modern Chrome and Edge use the --headless=new flag (the newer headless implementation that shares more code with headed Chrome than the legacy --headless mode, fixing several rendering discrepancies), configured via ChromeOptions.add_argument("--headless=new"). Firefox is switched to headless via FirefoxOptions().add_argument("-headless") or the simpler options.headless = True property depending on binding version. Because headless runs often execute inside containers without a real display server, pairing headless flags with --no-sandbox and --disable-dev-shm-usage is common in CI, since Docker's default /dev/shm size is too small for Chrome's shared memory needs and can cause crashes under load.
Cricket analogy: It's like a curator preparing a pitch specifically for a day-night pink-ball Test, using a different set of preparation flags than a standard red-ball match, because the conditions demand it.
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_argument("--headless=new")
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
options.add_argument("--window-size=1920,1080")
driver = webdriver.Chrome(options=options)
driver.get("https://example.com")
print(driver.title)
# Even headless, screenshots still capture the rendered layout
driver.save_screenshot("homepage.png")
driver.quit()Tradeoffs and Debugging Headless Failures
Headless mode's biggest practical drawback is debuggability: when a test fails, there's no window to glance at, so teams must lean on driver.save_screenshot() at the point of failure, full-page HTML dumps via driver.page_source, and browser console log capture through driver.get_log("browser") to reconstruct what went wrong. A small number of behaviors also genuinely differ headless-vs-headed — historically things like navigator.webdriver detection, WebGL rendering, and file download prompts — so a suite that's green in headless mode occasionally still deserves a periodic headed run, especially before major releases, to catch anything headless mode structurally can't reproduce.
Cricket analogy: It's like a coach reviewing a bowler's action purely from ball-tracking data with no video feed — you can reconstruct most of what happened, but occasionally you still need the camera footage to catch something data alone misses.
Selenium 4's BiDi protocol support enables real-time console log and network event streaming even in headless mode, giving you far richer failure diagnostics than screenshots alone — worth adopting for suites that run exclusively headless in CI.
Don't assume headless and headed runs will always produce identical results for viewport-dependent code: without an explicit --window-size, headless Chrome defaults to a smaller viewport than a typical headed window, which can silently change which elements are 'visible' and trigger different responsive CSS breakpoints.
- Headless mode runs the same rendering engine and JS runtime as headed mode, just without a composited display window.
- Chrome/Edge use --headless=new; Firefox uses -headless or the options.headless property.
- Container-based CI runs commonly pair headless flags with --no-sandbox and --disable-dev-shm-usage to avoid crashes.
- Headless makes debugging harder since there's no window to observe, requiring screenshots, page_source dumps, and console logs.
- A handful of behaviors (navigator.webdriver, WebGL, download prompts) can genuinely differ between headless and headed mode.
- Always set an explicit --window-size in headless mode to avoid unintended responsive layout differences.
- Selenium 4's BiDi protocol enables real-time log/network streaming even without a visible browser window.
Practice what you learned
1. What does headless mode actually skip when running a browser?
2. Which Chrome flag enables the modern headless implementation that shares more code with headed Chrome?
3. Why is --disable-dev-shm-usage commonly added alongside headless flags in Docker-based CI?
4. What is a key debugging challenge unique to headless test failures?
5. Why should you always set an explicit --window-size in headless mode?
Was this page helpful?
You May Also Like
Cross-Browser Testing
Learn how to write and run Selenium tests that validate application behavior consistently across Chrome, Firefox, Edge, and Safari.
Selenium in CI/CD Pipelines
Integrate Selenium test suites into continuous integration pipelines so UI regressions are caught automatically on every commit or pull request.
Selenium Grid Basics
Learn how Selenium Grid distributes WebDriver sessions across multiple machines and browsers so test suites can run remotely and at scale.