ScreenshotNeo

BlogGuides

What Is Headless Mode in Selenium?

Headless mode runs Selenium-controlled browsers without a visible window. Learn the current Chrome setup, migration details, debugging tips, and runnable examples.

By the ScreenshotNeo team1 October 20268 min read

Headless mode runs a Selenium-controlled browser without displaying its normal browser window. The browser still loads pages, executes JavaScript, applies cookies, and exposes the same WebDriver automation interface. You enable it through the selected browser’s options and command-line arguments; it is not a separate Selenium product.

For current Chrome automation, add --headless=new to ChromeOptions. Selenium deprecated its convenience setHeadless method in version 4.8 and removed it in 4.10, so older examples need updating. The argument is Chrome-specific guidance; Firefox, Edge, and other browsers have their own options and compatibility rules.

How headless mode works

In a headed run, Selenium starts a browser process with a visible window. In a headless run, the browser process starts without presenting that window to the desktop. Your test or scraper still communicates with the browser through WebDriver:

  1. Your program creates a browser options object.
  2. You add the browser’s headless argument.
  3. Selenium starts a local or remote browser session.
  4. The session navigates, renders, and interacts with pages normally from your code.
  5. Your program reads the result, saves a screenshot, or quits the session.

Headless does not automatically mean faster, more reliable, or pixel-identical to headed mode. Rendering can vary with browser versions, fonts, GPU configuration, viewport size, operating-system libraries, and page timing. Measure those properties in the environment where your automation runs.

Current Chrome setup

Selenium’s Chrome documentation shows browser arguments being passed through ChromeOptions. The following Python program is a complete minimal example:

from selenium import webdriver

options = webdriver.ChromeOptions()
options.add_argument("--headless=new")

# Selenium Manager can locate a compatible driver in supported environments.
driver = webdriver.Chrome(options=options)
try:
    driver.get("https://example.com")
    print(driver.title)
finally:
    driver.quit()

Install the Python binding first with python -m pip install selenium. Keep Chrome and ChromeDriver major versions compatible. Selenium Manager has shipped with Selenium releases since 4.6 and can manage drivers under its documented conditions, but an offline or restricted machine may still need a manually installed browser and driver.

Set a deterministic viewport

Headless sessions do not have a physical monitor to establish a useful window size. Set one explicitly when layout, screenshots, or responsive breakpoints matter:

from selenium import webdriver

options = webdriver.ChromeOptions()
options.add_argument("--headless=new")
options.add_argument("--window-size=1440,900")

driver = webdriver.Chrome(options=options)
try:
    driver.get("https://example.com")
    driver.save_screenshot("example.png")
finally:
    driver.quit()

Node.js example

In JavaScript, pass the same Chrome argument through Selenium’s Builder and chrome.Options classes:

const { Builder } = require('selenium-webdriver');
const chrome = require('selenium-webdriver/chrome');

(async function () {
  const options = new chrome.Options();
  options.addArguments('--headless=new', '--window-size=1440,900');

  const driver = await new Builder()
    .forBrowser('chrome')
    .setChromeOptions(options)
    .build();

  try {
    await driver.get('https://example.com');
    console.log(await driver.getTitle());
    await driver.takeScreenshot().then(data => require('fs').writeFileSync('example.png', data, 'base64'));
  } finally {
    await driver.quit();
  }
})();

Install the binding with npm install selenium-webdriver. The browser and driver still need to be available to the machine or remote endpoint.

Java example

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;

public class HeadlessExample {
    public static void main(String[] args) {
        ChromeOptions options = new ChromeOptions();
        options.addArguments("--headless=new", "--window-size=1440,900");

        WebDriver driver = new ChromeDriver(options);
        try {
            driver.get("https://example.com");
            System.out.println(driver.getTitle());
        } finally {
            driver.quit();
        }
    }
}

Headless migration: setHeadless and old flags

Older Selenium examples often use a convenience method such as options.setHeadless(true). Selenium’s project guidance says that method was deprecated in 4.8 and removed in 4.10. Replace it with an explicit browser argument:

# Older style (do not use with current Selenium bindings)
options.set_headless(True)

# Current Chrome style
options.add_argument("--headless=new")

Selenium’s January 2023 migration article describes Chromium’s transition from the traditional --headless mode, to --headless=chrome for Chrome versions 96–108, and then to --headless=new from version 109. Those are historical transition details; check the browser and Selenium documentation that matches the versions you deploy. Selenium 4.18 also documented a Chrome headless naming change and advised switching to --headless=new.

Firefox and other browsers

Do not copy Chrome’s flag blindly to every browser. Selenium documents Firefox-specific options and requires a compatible Firefox and geckodriver combination. A Python Firefox example is:

from selenium import webdriver
from selenium.webdriver.firefox.options import Options

options = Options()
options.add_argument("-headless")

driver = webdriver.Firefox(options=options)
try:
    driver.get("https://example.com")
    print(driver.title)
finally:
    driver.quit()

Verify the current argument, minimum browser version, and driver recommendation for the browser you support. For a remote session, create the appropriate options object and pass it to the remote WebDriver endpoint; the options determine which browser is requested.

Useful options around headless runs

Need Typical configuration Why it matters
Repeatable layout --window-size=1440,900 Controls responsive breakpoints and screenshot dimensions.
High-density output Set the browser/device scale factor where your binding and browser support it Changes CSS pixels versus bitmap pixels; verify the result in your environment.
Debugging Temporarily remove the headless argument Lets you watch navigation and inspect the page interactively.
Remote execution Use a browser options instance with RemoteWebDriver Requests the browser capabilities from a Selenium Grid or other endpoint.
Long pages Scroll or use a page-capture strategy in your code A viewport screenshot is not automatically a full-page screenshot.

Only add flags you understand and can support. A copied collection of server-oriented flags can hide the real cause of a failure and can alter rendering behavior.

Waiting for page state before capture

Headless mode does not change page-loading semantics. Modern pages may continue rendering after the initial navigation completes. Prefer an explicit condition over a fixed sleep when a known element signals readiness:

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

options = webdriver.ChromeOptions()
options.add_argument("--headless=new")
options.add_argument("--window-size=1440,900")

driver = webdriver.Chrome(options=options)
try:
    driver.get("https://example.com")
    WebDriverWait(driver, 20).until(
        EC.visibility_of_element_located((By.TAG_NAME, "body"))
    )
    driver.save_screenshot("ready.png")
finally:
    driver.quit()

For applications with network-driven content, wait for the element or application state your test actually needs. A long arbitrary delay increases runtime without proving that the correct state is present.

Headless versus headed: what changes?

  • Visibility: headless has no visible browser window; headed mode does.
  • Configuration: headless requires a browser-specific option or argument.
  • Debugging: headed mode is often easier to inspect manually, while headless runs need logs, screenshots, page source, and browser console diagnostics.
  • Rendering: do not assume identical pixels across modes or environments. Compare outputs in your target setup.
  • Operations: headless is convenient for CI and servers without a desktop session, but it still needs a compatible browser, driver, libraries, fonts, and enough resources.

Common errors and fixes

Symptom Likely cause Fix
AttributeError or missing set_headless The binding removed the convenience method. Use options.add_argument("--headless=new") for Chrome.
Session not created; version mismatch Chrome and ChromeDriver major versions differ. Check both versions, update the incompatible component, and let Selenium Manager resolve the driver where supported.
Chrome cannot start in CI Missing browser libraries, restricted downloads, or insufficient process permissions. Install the required browser dependencies, verify the browser launches on that host, and inspect the full driver log.
Blank or incomplete screenshot The page is still rendering, content is lazy-loaded, or navigation reached an error page. Wait for a meaningful selector, check the current URL and title, and save page source and a diagnostic screenshot.
Element is outside the viewport The requested element has not been scrolled into view or is hidden by responsive layout. Set the viewport, scroll the element into view, and verify its displayed state before interacting.
Different layout in CI Viewport, device scale, fonts, browser version, or operating-system rendering differs. Pin the browser environment where practical, set the viewport explicitly, install required fonts, and compare captured artifacts.
Firefox flag has no effect A Chrome argument was copied to Firefox. Use Firefox’s options and current Selenium Firefox documentation.

Debugging checklist

  1. Print the Selenium, browser, and driver versions.
  2. Record the requested URL and the final URL after redirects.
  3. Set an explicit window size.
  4. Save a screenshot and page source immediately after a failure.
  5. Check browser console and driver logs when JavaScript or navigation fails.
  6. Run the same code once in headed mode to determine whether the issue is visibility, environment, or page timing.
  7. Replace fixed sleeps with a condition tied to the page state you require.

Performance, reliability, and cost considerations

The supplied Selenium documentation establishes the configuration and compatibility rules, not a universal speed or reliability advantage for headless mode. Treat performance as an environment-specific measurement. Track navigation time, wait time, screenshot time, memory use, and failure rate with the exact browser version, page set, viewport, and host image you deploy.

For reliability, keep browser and driver versions compatible, make page readiness explicit, allocate enough CPU and memory for concurrent sessions, and retain artifacts from failed runs. For cost, account for browser startup, session duration, machine or CI minutes, bandwidth, and concurrency. Headless removes the need for a displayed desktop window; it does not remove the browser process or its resource usage.

Or skip the browser setup

If your goal is a clean website screenshot rather than browser automation itself, ScreenshotNeo provides a GET API and an MCP server. See the ScreenshotNeo API documentation for the available parameters.

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 and consent banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing result. Its MCP server lets Claude, Cursor, and other MCP clients use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

FAQ

Is headless mode a different browser?

No. It is an execution mode for a browser controlled by Selenium.

Can I use --headless=new with Firefox?

Do not assume so. Use the options and arguments documented for the specific browser and binding.

Why did Selenium remove setHeadless?

The convenience method was deprecated in Selenium 4.8 and removed in 4.10. Browser arguments in options are the current configuration pattern.

Does headless guarantee the same screenshot as headed Chrome?

No. Browser version, fonts, viewport, scale factor, operating system, and page timing can change rendering. Validate in the environment you ship.

Do I need a display server for headless Chrome?

Headless Chrome is designed to run without a visible browser window. You still need a working browser installation and its runtime dependencies.

What should I capture when a headless test fails?

Keep the driver log, browser and driver versions, final URL, page source, console output, and a screenshot from the failure point.

Primary references