ScreenshotNeo

BlogComparisons

Headless vs. Headful Browsers: Which Should You Use?

Use headless browsers for CI and servers; use headful mode for local debugging and visual diagnosis. Learn the trade-offs and setup details.

By the ScreenshotNeo team1 October 20267 min read

Use headless browsers for unattended CI, containers, servers, scheduled jobs, screenshots, PDFs, and repeatable automation. Use headful (headed) browsers for local development, selector investigation, visual inspection, and interactive debugging. Keep both modes available: run the normal pipeline headless, then reproduce failures headed with the same browser channel, viewport, dependencies, and user context.

Headless means the browser runs without a visible window. Headful means the browser UI is displayed. Playwright and Puppeteer launch headless by default; set headless: false to show the browser. Modern Chrome uses the same browser implementation in both modes, although separate shell binaries are also available.

Headless and headful: the practical difference

Question Headless Headful
Where does it fit? CI, containers, servers, cron jobs, screenshots, PDFs, unattended tests Developer desktop, test authoring, visual diagnosis, interactive debugging
Visible window? No Yes
Linux display server Usually unnecessary Usually required; CI may need Xvfb
Observability Use traces, screenshots, video, logs and inspector tooling Watch actions directly and inspect the page live
Browser fidelity Modern unified Chrome is the same implementation as headed Chrome; a shell binary may have a narrower feature set Full browser UI and normal desktop context

Google documents that “Chrome now has unified Headless and headful modes.” Since Chrome 132, the older implementation is distributed separately as chrome-headless-shell. Playwright also documents a modern headless mode using the real Chrome browser, while Puppeteer exposes both modern headless Chrome and the optional shell mode.

When to choose headless

  • CI and scheduled jobs: no desktop session is needed, so runners are easier to provision.
  • Containers and servers: headless avoids display-management work.
  • Screenshot and PDF services: requests can run without a person watching a browser.
  • High-volume automation: workers can be started, reused and scaled as background processes.
  • Repeatability: pin the browser version, viewport, fonts, locale, timezone and dependencies in the runner.

Headless is often the operational default, but do not assume a universal speed or memory percentage. Measure your exact workload and browser binary. Puppeteer describes chrome-headless-shell as performance-oriented when the complete Chrome feature set is unnecessary; that is a narrower choice than ordinary modern headless Chrome.

When to choose headful

  • Writing a new test: watch navigation, clicks and redirects as you build selectors.
  • Investigating a failure: inspect the DOM, layout, fonts, permissions, extensions and authentication state.
  • Visual diagnosis: determine whether a mismatch comes from viewport size, device scale factor, GPU behavior or missing system dependencies.
  • Interactive workflows: debug flows that require manual intervention or a visible browser profile.

Playwright supports an inspector, slowMo, DEBUG=pw:api and headless: false. A headed reproduction is most useful when every other input matches the failing run.

Playwright: run the same test in both modes

Install Playwright and its browsers:

npm init -y
npm install -D playwright
npx playwright install chromium

Create capture.mjs:

import { chromium } from 'playwright';

const headed = process.argv.includes('--headed');
const browser = await chromium.launch({
  headless: !headed,
  slowMo: headed ? 150 : 0
});

const page = await browser.newPage({
  viewport: { width: 1440, height: 900 },
  deviceScaleFactor: 1
});

await page.goto('https://example.com', { waitUntil: 'networkidle' });
await page.screenshot({ path: headed ? 'headed.png' : 'headless.png', fullPage: true });
await browser.close();

Run the normal path with node capture.mjs. Open a visible browser with node capture.mjs --headed. Keep the URL, browser channel, viewport, device scale factor, locale, timezone and storage state identical while comparing results.

Debugging a Playwright failure

DEBUG=pw:api node capture.mjs --headed

For a richer failure artifact, enable tracing around the smallest reproducer:

import { chromium } from 'playwright';
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext();
await context.tracing.start({ screenshots: true, snapshots: true, sources: true });
const page = await context.newPage();
await page.goto('https://example.com');
await context.tracing.stop({ path: 'trace.zip' });
await browser.close();

Inspect it with npx playwright show-trace trace.zip. Retain traces, screenshots, video, console output and network logs from failed CI jobs.

Puppeteer: headless, headed and shell modes

Install Puppeteer:

npm init -y
npm install puppeteer
import puppeteer from 'puppeteer';

const mode = process.argv.includes('--headed') ? false : true;
const browser = await puppeteer.launch({ headless: mode });
const page = await browser.newPage();
await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
await page.goto('https://example.com', { waitUntil: 'networkidle0' });
await page.screenshot({ path: mode ? 'headless.png' : 'headed.png', fullPage: true });
await browser.close();

Use node capture.mjs --headed for a visible browser. Puppeteer also supports headless: 'shell' for the separate headless-shell binary. Choose it only after measuring that its narrower feature set works for your application.

Running headed browsers in Linux CI

Headed Linux execution needs a display server. Playwright’s CI guidance commonly uses Xvfb, a virtual framebuffer. A typical command is:

xvfb-run --auto-servernum --server-args='-screen 0 1440x900x24' npm test

Your CI image must also contain the browser’s shared libraries, fonts and any codecs your pages need. If the headed job fails before a page opens, check the display server and OS dependencies before changing test code.

Why a test passes headed but fails headless

  1. Different browser binary or channel: select the same Chrome/Chromium channel in both runs.
  2. Different viewport or scale: set explicit width, height and device scale factor.
  3. Missing fonts: install the same fonts in CI and local environments; font reflow can change selectors and screenshots.
  4. Timing assumptions: wait for a specific selector or application state instead of a fixed sleep.
  5. GPU, permissions or extensions: compare launch flags, context permissions and installed extensions.
  6. Authentication state: use the same storage state, cookies, headers and user agent.
  7. Resource blocking: inspect network logs for blocked scripts, fonts, images or API calls.
  8. Environment differences: match locale, timezone, geolocation, proxy and environment variables.

Headless and headful should not be treated as two unrelated browsers. First make the environments equivalent, then investigate mode-specific behavior.

Performance, reliability and cost

  • Performance: benchmark startup time, navigation time, screenshot time, CPU, memory and concurrency on your own pages. Official documentation does not provide a universal headless-versus-headful percentage.
  • Reliability: pin browser versions, use deterministic waits, retry only transient failures, and preserve diagnostic artifacts.
  • Concurrency: reuse browser processes where safe, limit contexts per worker, and watch memory as pages load large images or PDFs.
  • Cost: headed Linux jobs may require additional display infrastructure. Hosted browser or screenshot services can shift browser maintenance out of your application; compare their billing rules with your request volume.

A decision checklist

  • Is this an unattended CI, server, container or scheduled task? Choose headless.
  • Do you need to watch actions or inspect layout while authoring? Choose headful locally.
  • Will headed Linux run in CI? Budget for Xvfb and OS dependencies.
  • Do you need Chrome’s complete feature set? Prefer modern unified headless or headed Chrome over a shell binary.
  • Did a test fail only in one mode? Match browser channel, viewport, fonts, dependencies and user context before changing the test.
  • Do you need repeatable screenshots without maintaining a browser fleet? Consider a screenshot API.

Or skip the browser setup

ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns PNG, JPEG, WebP or PDF, so your service does not need to install or operate Playwright, Puppeteer, Xvfb or browser binaries. See the ScreenshotNeo API documentation for parameters and response details.

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}`);

Cookie banners, newsletter popups and chat widgets are removed before the shot. Bot checks, blank pages and failed loads are never billed, and response headers identify the page verdict and whether the request was billed. An MCP server lets Claude, Cursor and other MCP clients call take_screenshot, get_page_info and capture_pdf. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000.

Create a free ScreenshotNeo account and start with 1,000 shots per month at no charge.

Common errors and fixes

Error Likely cause Fix
Executable doesn't exist Browser binaries were not installed Run npx playwright install chromium or install Puppeteer’s required browser during image build.
Failed to launch on Linux Missing shared libraries, sandbox permissions or display server Install documented CI dependencies; use headless mode, or run headed tests under Xvfb.
Timeout waiting for navigation Slow app, never-ending requests or an incorrect readiness condition Wait for a stable selector or application signal and inspect network logs.
Different screenshots Viewport, fonts, timezone, animations or browser version differ Pin those inputs and disable or wait for animations before capture.
Headful window is not visible Remote session has no desktop display Set DISPLAY correctly or use xvfb-run; use headless for unattended jobs.
Browser crashes under load Too many concurrent pages or insufficient memory Reduce concurrency, reuse contexts carefully and monitor worker memory.

FAQ

Should Playwright run headless or headed?

Run headless in CI and production automation. Use headed locally while authoring or diagnosing failures, then validate the final suite headless.

Is headless Chrome faster?

It can reduce display overhead, but there is no universal official speed or memory percentage. Benchmark your browser version, pages and CI hardware.

Do headless and headful Chrome behave the same?

Modern unified Chrome uses the same browser implementation. Differences can still come from viewport, fonts, GPU, permissions, extensions, dependencies or user context.

When should I use chrome-headless-shell?

Use it only when its narrower feature set is sufficient and measurements show a benefit for your workload. Prefer modern headless Chrome when fidelity and features matter.

Can I run headed tests in a container?

Yes, but provide a display server such as Xvfb and install the browser’s system dependencies. Headless is simpler for unattended containers.

What should I keep from a failed headless run?

Capture a trace, screenshot, video when useful, console output, network logs and the exact browser, viewport and environment configuration.

Sources: Playwright browser documentation, Playwright CI documentation, Playwright debugging documentation, Puppeteer headless modes, Chrome Headless mode, and Chrome automation documentation.