What Is Headless Testing and When Should You Use It?
Headless testing runs browser tests without a visible window. Learn when to use it, when headed mode helps, and how to configure reliable CI runs.
Headless testing runs a real browser without displaying its user interface. The browser still loads pages, executes JavaScript, makes network requests, applies cookies and storage, and performs assertions. Use it for unattended automation such as CI pipelines, containers, and server jobs. Switch to headed mode when seeing the browser makes a failure easier to understand.
The right choice depends on your browser engine, automation framework, pinned browser version, CI environment, and debugging workflow. Headless mode is not a promise of a particular speed improvement; measure your own suite if execution time matters.
Headless and headed testing compared
| Question | Headless | Headed |
|---|---|---|
| Is a browser window visible? | No visible UI | Yes |
| Typical use | CI, containers, scheduled jobs, server automation | Local debugging and investigating visual or navigation state |
| Can pages execute normally? | Yes; the browser still performs page work | Yes |
| Linux CI display requirement | Usually no display server | Playwright documents using Xvfb for headed runs |
| Best diagnostics | Logs, traces, screenshots, videos, artifacts | Direct observation, optionally slowed execution |
Chrome for Developers defines the distinction as running Chrome “without any visible UI.” Chrome’s current headless implementation creates platform windows without displaying them and exposes the browser’s other functions. Beginning with Chrome 132.0.6793.0, the older implementation is available only as the standalone chrome-headless-shell binary; check the current Chrome documentation before depending on version-specific behavior.
When headless mode is the right default
- Continuous integration: every commit can run the same unattended checks.
- Containers and servers: jobs do not need a person or desktop session.
- Scheduled monitoring: navigation, smoke, and regression checks can run on a schedule.
- Parallel automation: workers can run browser contexts without opening windows for each job.
- Reproducible releases: a pinned browser binary and automation dependency make environment changes easier to identify.
Headless is appropriate when the test’s result is an assertion or artifact, rather than something a person must watch. Keep the test observable with structured logs, traces, screenshots, and the browser and framework versions used for the run.
When headed mode is better
- You need to see a menu, dialog, redirect, consent banner, or animation state.
- A selector is not found and the page’s actual layout is unclear.
- You are investigating focus, hover, drag-and-drop, or viewport behavior.
- You are diagnosing a difference between a developer laptop and CI.
- You want to slow execution so a person can follow each action.
Playwright runs headless by default and exposes headless: false to show the browser. Its debugging guidance also documents slowing execution for observation. On Linux CI, a headed Playwright run normally needs Xvfb, a virtual display server.
A practical workflow: headless first, headed on failure
- Run the normal suite headlessly in CI and locally.
- Save a trace, screenshot, console log, and relevant network information when a test fails.
- Reproduce the failing test with the same browser binary, viewport, user agent, locale, and data.
- Run that single test headed, optionally with slow motion, to inspect the state.
- Fix the test or application, then rerun headlessly to confirm the automated path.
Playwright: complete Node.js example
Install Playwright and its browser once, then run this script in headless mode.
npm init -y
npm install -D playwright
npx playwright install chromium
// headless-test.mjs
import { chromium } from 'playwright';
const browser = await chromium.launch({
headless: true,
});
const page = await browser.newPage({
viewport: { width: 1280, height: 720 },
});
try {
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.getByRole('heading', { name: 'Example Domain' }).waitFor();
console.log('pass:', await page.title());
} finally {
await browser.close();
}
For a visible debugging run, change headless: true to headless: false. You can also add a delay between actions while investigating a failure, following Playwright’s debugging guidance.
Playwright: complete Python example
python -m pip install playwright
python -m playwright install chromium
# headless_test.py
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page(viewport={"width": 1280, "height": 720})
try:
page.goto("https://example.com", wait_until="domcontentloaded")
page.get_by_role("heading", name="Example Domain").wait_for()
print("pass:", page.title())
finally:
browser.close()
Use headless=False for a visible local run. In a Linux CI job, install and start Xvfb before headed execution.
Chrome and Puppeteer options
Puppeteer automates Chrome and Firefox through Chrome DevTools Protocol or WebDriver BiDi and supports UI testing, screenshots, PDFs, and performance analysis. A minimal Node.js example is:
npm install puppeteer
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
try {
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
console.log(await page.title());
} finally {
await browser.close();
}
For a reproducible Chrome workflow, Chrome for Developers recommends a version-pinned Chrome for Testing binary, Chrome Headless mode, and an automation driver such as Puppeteer or ChromeDriver. Pin the browser and driver together where your build requires reproducibility.
Configuration checklist
- Browser: choose Chromium, Firefox, WebKit, or a branded browser required by the test.
- Version: pin the browser and automation package when diagnosing drift.
- Mode: use headless for routine runs; use headed plus Xvfb on Linux CI only when visual observation is needed.
- Viewport and device scale: set them explicitly for layout-sensitive assertions.
- Navigation waits: choose a condition that matches the app; avoid arbitrary sleeps when a selector or network state is available.
- Authentication: use isolated test accounts and storage state; never print secrets in logs.
- Artifacts: retain traces, screenshots, videos, console errors, and failed URLs.
- Isolation: use a fresh context or profile when cookies, local storage, permissions, or service workers could leak between tests.
- Network: decide whether third-party resources should be available, mocked, or blocked.
- Parallelism: increase workers only after checking CPU, memory, rate limits, and test-data isolation.
CI setup principles
Install the exact browser dependencies required by your framework during the build. Cache downloads only when the cache key includes the browser and dependency versions. Record the operating system, browser version, framework version, viewport, and execution mode in failed artifacts. Keep headed debugging as a separate job or a manually triggered reproduction path so ordinary CI remains unattended.
Common problems and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Browser executable not found | The framework’s browser was not installed or the path is wrong. | Run the framework’s browser install command or configure the intended executable path. |
| Headed run fails with display errors | Linux CI has no display server. | Use headless mode or run the headed job under Xvfb. |
| Test passes locally but fails in CI | Different browser version, viewport, fonts, locale, data, or timing. | Pin versions, set environment values explicitly, and inspect traces and screenshots. |
| Element is missing | The page has not reached the required state, a selector changed, or a consent/login overlay blocks it. | Wait for a meaningful selector, verify the URL and page content, and use a headed reproduction. |
| Intermittent timeout | Unstable network, slow dependency, race condition, or an overly short timeout. | Wait on the actual readiness condition, capture diagnostics, and fix the race before increasing timeouts globally. |
| Different layout or screenshot | Viewport, device scale, fonts, animation, or responsive breakpoints differ. | Set viewport and scale, install required fonts, disable or wait for animations, and compare the same browser build. |
| Tests affect one another | Shared cookies, local storage, files, accounts, or mutable backend data. | Use isolated contexts and test data; clean up resources after each test. |
| CAPTCHA or bot check appears | The target service detected automated traffic. | Use an approved test environment or test hooks; do not attempt to bypass a site’s access controls. |
Performance, reliability, and cost
Headless mode removes the need to display a browser window, which suits unattended environments. The supplied sources do not establish a universal speed advantage, so benchmark the complete workflow in your own CI image if latency matters. Performance is usually affected by browser startup, page resources, network conditions, test data, parallel workers, and application readiness.
- Reuse a browser process when safe, while creating isolated contexts for tests.
- Wait for precise readiness conditions instead of fixed delays.
- Block or mock unnecessary third-party resources only when that matches the behavior under test.
- Keep retries limited and visible; retries can hide a real race or outage.
- Use deterministic data and clean up contexts, pages, and temporary files.
- Budget for browser binaries, CI CPU and memory, test accounts, network traffic, and any hosted browser service. The research does not provide a general cost ranking.
Choosing a framework and browser
Compare candidates by the browser engines and branded browsers they control, framework fit with your existing suite, ability to pin and reproduce browser versions, CI dependencies, and debugging artifacts. The official sources name Playwright, Puppeteer, ChromeDriver, Chrome for Testing, and Chrome Headless; they do not establish an overall ranking or equivalent feature coverage across all frameworks.
Or skip the browser setup
If your goal is to capture a stable page image or PDF rather than maintain browser tests, ScreenshotNeo provides a hosted screenshot API. One GET request returns PNG, JPEG, WebP, or PDF. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be disabled. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and responses identify the result with X-Page-Verdict and X-Billed headers. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
See the ScreenshotNeo API documentation for all options.
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 includes full-page and element captures, device presets and custom viewports, dark mode, retina scale, PDF controls, custom CSS and JavaScript, clicks, waits, hidden selectors, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, TTL caching, signed links, asynchronous webhooks, bulk capture, usage reporting, and an OpenAPI specification. Its parameter names also match those used by other screenshot APIs, which helps when switching.
Create a free ScreenshotNeo account for 1,000 screenshots each month with no card. Paid plans start at $5 for 3,000 screenshots.
FAQ
Does headless mean the browser is not really running?
No. The browser engine still performs navigation, JavaScript execution, rendering, storage, and network activity; only the visible UI is omitted.
Should every test run headlessly?
Use headless for routine unattended runs. Reproduce failures headed when visual observation can reveal the cause.
Do headed tests require Xvfb?
On Linux CI without a physical display, Playwright documents Xvfb for headed execution. Headless runs generally avoid that display requirement.
Is headless always faster?
No universal speed result is established by the cited documentation. Measure your browser version, CI image, page mix, and concurrency.
Which browser version should CI use?
Use the version your application supports and pin it when reproducibility matters. Recheck current Chrome and framework documentation because implementation details change.


