How to Run WebdriverIO Tests in Headless Mode
Configure Chrome, Firefox, or Edge for headless WebdriverIO runs, choose native headless or Xvfb, and fix common CI and Docker startup problems.
To run WebdriverIO tests in headless mode, add the browser’s headless flag to that browser’s capabilities in wdio.conf.js, then start the WebdriverIO runner. For Chrome, use goog:chromeOptions.args; for Firefox, use moz:firefoxOptions.args; for Edge, use ms:edgeOptions.args. Native headless mode is the first option to try. On Linux, use Xvfb when your application or tooling needs a display server or desktop behavior.
For example, Chrome can be configured like this:
export const config = {
capabilities: [{
browserName: 'chrome',
'goog:chromeOptions': {
args: ['--headless=new', '--no-sandbox']
}
}]
}
Run the suite with npx wdio run ./wdio.conf.js. Headless means the browser runs without a visible window or UI; it still loads pages and executes browser tests.
1. Configure a headless WebdriverIO run
Headless mode is a browser capability, not a separate WebdriverIO command. Put the flag in the vendor-specific options object for the browser in use. The following is a small ESM configuration for a Mocha suite. It assumes WebdriverIO and its runner are installed, and that at least one spec exists under ./test/specs.
// wdio.conf.js
export const config = {
runner: 'local',
specs: ['./test/specs/**/*.js'],
maxInstances: 1,
capabilities: [{
browserName: 'chrome',
'goog:chromeOptions': {
args: ['--headless=new', '--no-sandbox']
}
}],
framework: 'mocha',
reporters: ['spec'],
mochaOpts: {
ui: 'bdd',
timeout: 60000
}
}
Start the runner from the project directory:
npx wdio run ./wdio.conf.js
Use the configuration filename and extension your project actually has, such as wdio.conf.ts. If the project already has a configuration, add the capability to it rather than replacing its services, hooks, framework, or reporters.
Chrome or Chromium
Use browserName: 'chrome' (or the browser name appropriate to your setup) and the Chrome vendor namespace. The documented pattern uses --headless=new; Docker examples may use --headless. Use the flag supported by the Chrome version installed in your environment.
capabilities: [{
browserName: 'chrome',
'goog:chromeOptions': {
args: ['--headless=new', '--no-sandbox']
}
}]
Firefox
Firefox uses its own capability namespace and flag:
capabilities: [{
browserName: 'firefox',
'moz:firefoxOptions': {
args: ['-headless']
}
}]
Microsoft Edge
Edge uses ms:edgeOptions and the --headless argument:
capabilities: [{
browserName: 'msedge',
'ms:edgeOptions': {
args: ['--headless']
}
}]
Do not copy one browser’s vendor options object to another browser. Safari does not support headless execution according to the WebdriverIO capabilities documentation. See WebdriverIO capabilities for browser-specific capability formats.
2. Choose native headless mode or Xvfb
Try native browser headless mode first when the application and test tooling work without a desktop session. Xvfb (X Virtual Framebuffer) provides a virtual display on Linux. Consider it when tests need DISPLAY, a window manager, GLX, Electron, or other desktop behavior.
WebdriverIO’s runner can use Xvfb on Linux when DISPLAY is absent or headless browser flags are present. Its autoXvfb setting controls whether the runner wraps a worker with Xvfb. If CI already starts an X server, export its DISPLAY before running WebdriverIO or explicitly set autoXvfb: false to avoid runner-managed Xvfb.
// wdio.conf.js: use runner-managed Xvfb when needed
export const config = {
autoXvfb: true,
capabilities: [{
browserName: 'chrome',
'goog:chromeOptions': {
args: ['--headless=new', '--no-sandbox']
}
}]
}
Relevant Xvfb runner options documented by WebdriverIO include:
autoXvfb: enable or disable runner-managed Xvfb. Setting it tofalsedisables its use.xvfbAutoInstall: opt in to installing Xvfb ifxvfb-runis missing. It does not enable Xvfb by itself; its default isfalse.xvfbAutoInstallMode: choose'root'or'sudo'when automatic installation is enabled.xvfbAutoInstallCommand: provide a custom installation command instead of built-in package-manager detection.xvfbMaxRetriesandxvfbRetryDelay: configure retry count and base delay for Xvfb startup failures.
Automatic installation can modify the CI environment and may require privileges. In controlled or locked-down images, install the system package in the image build instead. Package names vary by distribution; WebdriverIO’s Headless & Xvfb guide lists supported package managers and examples.
3. Set up CI and Docker deliberately
In CI, confirm that the browser and driver are available in the worker before interpreting a session-start failure as a test failure. If the job already starts Xvfb or another display server, check that DISPLAY is exported to the WebdriverIO process. If you rely on native headless mode, use the browser flags and confirm the application does not require a desktop environment.
For a Docker image with Chrome, a documented configuration example includes --no-sandbox, --disable-gpu, and a window size:
export const config = {
capabilities: [{
maxInstances: 1,
browserName: 'chrome',
'goog:chromeOptions': {
args: [
'--no-sandbox',
'--disable-gpu',
'--headless',
'--window-size=1440,735'
]
}
}]
}
Adapt those flags to the pinned browser version and the container’s security setup; they are an example, not a requirement for every container. Keep the Chrome version in the image aligned with the ChromeDriver version configured for the project. See the WebdriverIO Docker guide.
If WebdriverIO cannot detect a browser, check the installed binary and driver setup. The browser binary can be set explicitly with goog:chromeOptions.binary or moz:firefoxOptions.binary when needed. Refer to the driver binaries documentation for detection and setup details.
4. Isolate a failing run
When browser startup or a large suite fails, run one spec to separate configuration problems from failures elsewhere in the suite:
npx wdio run ./wdio.conf.js --spec test/specs/example.e2e.js
WebdriverIO also supports selecting a spec this way in its getting started guide. Check that the spec path is valid relative to the project and that the runner is loading the configuration file you edited.
5. Troubleshoot common headless problems
| Symptom | Likely cause | What to check or change |
|---|---|---|
| Browser session fails to start | Browser missing, wrong browser name, unavailable driver, or malformed capability namespace. | Confirm the browser and driver exist in the execution environment. Check the browser name and use that browser’s vendor-specific options object. |
| Headless flag has no effect | Flag is misspelled, belongs to another browser, or is outside the browser’s args array. |
Use Chrome’s goog:chromeOptions.args, Firefox’s moz:firefoxOptions.args, or Edge’s ms:edgeOptions.args with the browser’s documented flag. |
Chrome reports DevToolsActivePort or a user-data-directory collision |
The browser may have crashed and restarted; the profile-directory message can be a symptom rather than the root cause. | Inspect the initial browser launch, display environment, browser/driver pairing, and container resources. The WebdriverIO guide notes that a stable display may resolve some cases; if using parallel workers, ensure each has a unique user-data directory where your setup requires it. |
| Tests fail only on Linux CI | The application, Electron, or tooling may expect a display, window manager, or GLX. | Check DISPLAY; use an existing X server or configure runner-managed Xvfb. If your own display is present, export it or set autoXvfb: false. |
xvfb-run is missing |
Xvfb is not installed in the image, and automatic installation is disabled. | Install the distribution’s Xvfb package in the image, or opt into xvfbAutoInstall only if the CI environment permits package installation. |
| Xvfb starts unreliably | The virtual display process is failing intermittently or the CI environment is unstable. | Review runner logs and the Xvfb setup. Configure xvfbMaxRetries and xvfbRetryDelay when retries are appropriate. |
| Container browser starts locally but not in CI | The image may have a different browser/driver pair, missing dependencies, or different sandbox permissions. | Pin compatible versions, inspect the image’s installed binaries, and adapt flags to the container’s security model. |
| A test times out after browser startup | The suite may be waiting on an app condition, slow resource, or timing assumption rather than headless startup. | Run a single spec, inspect the failing wait and application logs, and use explicit condition-based waits instead of relying on a fixed short delay. |
6. Performance, reliability, and cost considerations
- Performance: Native headless mode avoids rendering a visible browser window and WebdriverIO recommends it when it works. Actual run time depends on the suite, browser, application, and CI resources; the cited documentation gives no benchmark that predicts a particular speedup.
- Reliability: Pin and align browser and driver versions, verify binaries inside the same environment that runs the tests, and make the display setup explicit when the app depends on it. Run a single spec to diagnose startup separately from suite behavior.
- Parallelism: More workers can use more CPU and memory and can expose profile or display collisions. Start with a modest
maxInstancesin constrained containers and increase it only when the environment supports the added load. - Cost: Headless mode changes how the browser is displayed; it does not itself remove CI compute costs. Xvfb also needs an installed package and worker resources. Estimate cost from your CI provider’s actual worker pricing and run duration rather than assuming headless is free.
Or skip the browser setup
If your task is to capture a page image or PDF rather than exercise application interactions, ScreenshotNeo is a website screenshot API and MCP server. One GET request takes a URL and returns an image or PDF. See the API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the capture; bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month, with no card required.
FAQ
Does headless mode change what WebdriverIO tests?
It changes whether a browser window is shown. The tests still run in a browser session, but behavior that depends on visible desktop UI or a window manager may need Xvfb or a headed environment.
Can I run Safari headlessly?
The WebdriverIO capabilities documentation says Safari does not support headless execution.
Should I always add --no-sandbox?
No universal rule follows from the examples. The Docker guide includes it in its container setup, but choose flags based on the image’s security model and browser requirements.
Does enabling xvfbAutoInstall turn on Xvfb?
No. It controls installation when xvfb-run is missing. autoXvfb controls whether the runner uses Xvfb.


