Headless browser execution is the foundation that makes running real-browser tests in CI practical at all. This lesson pulls together the headless-specific considerations that matter most across different CI environments.
Why Headless Is Non-Negotiable for CI
Most CI runners — whether GitHub Actions, Jenkins agents, or custom build servers — have no display server (X11, Wayland, etc.) available at all. A visible browser like plain Chrome simply cannot launch in that environment; ChromeHeadless and FirefoxHeadless are specifically designed to run without one.
// This fails on most CI runners with no display server:
browsers: ['Chrome']
// This works reliably:
browsers: ['ChromeHeadless']
The difference is entirely about whether a real graphical window needs to be drawn — headless mode never attempts that.
Headless-Specific Environment Considerations
--no-sandbox // usually required in containerized CI
--disable-gpu // avoids missing/unsupported GPU driver issues
--disable-dev-shm-usage // avoids small /dev/shm crashes in containers
--window-size=1280,720 // ensures consistent, predictable viewport dimensions
Headless Chrome and Firefox both support these flags, though exact flag names/behavior can differ slightly between them.
A consistent --window-size avoids layout-dependent test differences between local and CI viewport defaults.
None of these flags change what your application code does — they only affect the browser process itself.
Historically, some teams used a virtual display (xvfb) to run visible browsers headlessly; native headless mode has made this largely unnecessary.
Headless CI Cheat Sheet
The environment-level checklist for stable headless CI runs.
Consideration
Recommendation
Display server availability
Assume none exists; always use a headless browser variant
Container sandboxing
Add --no-sandbox for Docker/container-based CI
Shared memory limits
Add --disable-dev-shm-usage in containers
Viewport consistency
Set an explicit --window-size
Legacy virtual display approach
xvfb — largely unnecessary with native headless support
A Brief Note on xvfb (Historical Context)
Before headless Chrome and Firefox existed, running a visible browser in a display-less environment required xvfb (X Virtual Framebuffer) — a virtual display server that let a real, visible browser 'think' it had a screen to draw on. Some older CI configurations you may encounter still reference xvfb-run for this reason; on any reasonably modern browser version, native headless mode makes this unnecessary.
It's tempting to aggressively tune every browser flag for maximum CI speed, but a headless CI setup that's rock-solid and occasionally a few seconds slower is far more valuable than one that's marginally faster but flakes unpredictably. Start with the well-documented, widely-used flag set, and only deviate when you have a specific, measured reason to.
Common Mistakes
Configuring a visible browser for CI and being surprised when it fails to launch on a display-less runner.
Reaching for xvfb unnecessarily when native headless mode already solves the same problem more simply.
Over-tuning browser flags for marginal speed gains at the cost of run stability.
Not setting an explicit viewport size, causing layout-sensitive tests to behave inconsistently across environments.
Key Takeaways
Headless browsers are essential for CI runners, which almost never have a display server available.
xvfb was the historical workaround before native headless support existed, and is rarely needed today.
Stability should be prioritized over marginal speed optimizations for CI browser configuration.
Pro Tip
If you inherit a CI pipeline still using xvfb-run, treat migrating it to native ChromeHeadless/FirefoxHeadless as a straightforward, low-risk simplification — it removes an entire extra moving part with no loss of functionality on any reasonably modern browser version.
You now understand headless CI testing broadly. Next, move into advanced topics, starting with writing a custom browser launcher.