What to Know About Running Headless Browsers
Headless browsers run a real browser without a visible window. Learn what they do, when to use them, how to run them, and how to choose a mode.
A headless browser is a browser that runs without displaying a visible window. The browser still loads pages, runs JavaScript, lays out and paints content, and can be controlled by code. You can use it for automated tests, screenshots, PDFs, data extraction, and browser workflows.
Choose the browser and mode based on the job. For high-fidelity tests of Chrome, use Chrome’s current Headless mode or a framework configuration that launches that browser build. For broad engine coverage, Playwright can run Chromium, Firefox, and WebKit. For a small screenshot or PDF task that does not need browser interaction, an API such as ScreenshotNeo can avoid maintaining browser binaries and launch logic.
1. What “headless” means
Headless means there is no visible browser UI for a person to operate. It does not mean that the browser has been replaced by a simple HTML parser. A headless browser runs browser code and renders pages, so it can execute client-side JavaScript, apply CSS, load images, and respond to scripted interactions.
Chrome’s current Headless mode shares code with regular Chrome. It creates platform windows without showing them. Chrome 132 removed the old Headless implementation from the main Chrome binary; that implementation is now distributed separately as chrome-headless-shell. The term can therefore mean either a current browser mode or, in framework configuration, a separate shell build. See Chrome’s Headless documentation and its Chrome 132 migration note.
2. What headless browsers are used for
- Browser tests: verify rendered interfaces and user journeys without manually opening a browser.
- Screenshots: capture a viewport, full page, or selected element for visual review or documentation.
- PDF generation: render print-oriented pages into documents.
- Navigation and interaction: fill forms, click controls, and test multi-step UI flows.
- Extraction: read content that appears after client-side rendering, where an HTTP-only fetch would not see the rendered result.
- Performance analysis: collect browser traces and inspect how a page behaves.
These are browser automation jobs, not a guarantee that every site will permit or serve the same content to automation. Authentication, network access, page design, and site-side checks affect results. Google Cloud documents headless Chrome in Cloud Run for tasks such as scraping, extraction, form submission, UI testing, screenshots, and PDFs; Cloud Run is one deployment option, not a prerequisite for local runs. Google Cloud’s browser automation guide.
3. Pick a browser framework and mode
| Choice | Good fit | Trade-off to understand |
|---|---|---|
| Chrome Headless | Chrome-focused automation where current Chrome behavior matters | Still requires managing the browser executable and runtime environment when self-hosting. |
| Puppeteer | JavaScript automation centered on Chrome or Firefox | Its regular Headless, shell, and headful modes are distinct; shell may not behave exactly like regular Chrome. |
| Playwright | Tests that need Chromium, Firefox, or WebKit and framework-managed browser binaries | Browser binaries must match the Playwright version. Default headless Chromium uses a headless shell; choose a Chromium channel when you want the newer Chrome Headless mode. |
| Hosted screenshot API | Producing screenshot/PDF output without owning a browser lifecycle | It is a service call rather than a locally controlled browser session; check the required capture options and response format. |
Playwright supports Chromium, WebKit, Firefox, and branded Chrome and Edge channels. Its documentation notes that default headless Chromium uses a separate shell build and can differ from the newer Chrome Headless mode. It also recommends keeping Playwright and its matching browser binaries current. Puppeteer documents headless: true, headless: 'shell', and headless: false; shell may be more performant for tasks that do not need the full Chrome feature set, but behavior does not completely match regular Chrome. These are conditional trade-offs, not a universal speed ranking. Read the official Playwright browser guide and Puppeteer headless modes.
A practical selection checklist
- List the engines your users actually use. Choose Playwright when you need its documented Chromium, Firefox, and WebKit coverage; use a Chrome-focused setup when Chrome is the target.
- Decide whether fidelity to visible Chrome matters. Pin the browser mode and binary/channel in CI instead of relying on an implicit default.
- Decide whether the job needs interaction, DOM inspection, or only an output file. Use a framework for journeys and custom browser logic; consider a screenshot API for straightforward capture.
- Record framework and browser versions with test results. Update them deliberately and install the framework-matched browser builds.
- Run representative cases in the same OS/container and mode used in production. A passing local run does not prove the deployed environment has the same fonts, dependencies, network access, or browser build.
4. Run Chrome directly from the command line
On a machine with Chrome installed, invoke it with --headless. This example writes a screenshot of a page to a local file:
google-chrome --headless --screenshot=page.png --window-size=1440,1000 https://example.com
Executable names vary by operating system and installation. On macOS, Chrome can be launched with open -a "Google Chrome" --args --headless; on Windows the Chrome executable is commonly invoked as chrome --headless. Use the actual executable path for your installation. Add a screenshot path and viewport flag as needed. For multi-step interactions, full-page capture, waits, retries, or structured results, a browser automation library is easier to maintain than a long CLI command.
5. Runnable JavaScript with Puppeteer
This example launches current Headless Chrome, waits for navigation, saves a full-page screenshot, and always closes the browser. Create a project and install Puppeteer with npm install puppeteer; save as capture.mjs and run node capture.mjs. Puppeteer downloads a compatible browser as part of its normal setup.
import puppeteer from 'puppeteer';
const url = process.argv[2] ?? 'https://example.com';
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.setViewport({ width: 1440, height: 1000, deviceScaleFactor: 1 });
const response = await page.goto(url, {
waitUntil: 'networkidle2',
timeout: 45_000,
});
if (!response) {
throw new Error('Navigation did not produce an HTTP response');
}
if (!response.ok()) {
throw new Error(`Navigation returned HTTP ${response.status()}`);
}
await page.screenshot({ path: 'page.png', fullPage: true });
console.log(`Saved page.png (HTTP ${response.status()})`);
} finally {
await browser.close();
}
Use headless: false to open a visible browser while debugging. Use headless: 'shell' only when you deliberately want the separate Headless Shell and have checked that its behavior is suitable. Puppeteer’s default Headless mode is true.
6. Runnable Python with Playwright
Install Playwright with python -m pip install playwright, then install its browser build using python -m playwright install chromium. Save this as capture.py and run python capture.py https://example.com. The script uses Playwright’s Python sync API.
import sys
from playwright.sync_api import sync_playwright
url = sys.argv[1] if len(sys.argv) > 1 else "https://example.com"
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
try:
page = browser.new_page(viewport={"width": 1440, "height": 1000})
response = page.goto(url, wait_until="networkidle", timeout=45_000)
if response is None:
raise RuntimeError("Navigation did not produce an HTTP response")
if not response.ok:
raise RuntimeError(f"Navigation returned HTTP {response.status}")
page.screenshot(path="page.png", full_page=True)
print(f"Saved page.png (HTTP {response.status})")
finally:
browser.close()
For cross-browser coverage, install the required Playwright browsers and launch p.firefox or p.webkit instead of p.chromium. Install matching browser binaries again after upgrading Playwright. In Node projects, the equivalent setup commands are npm install -D playwright and npx playwright install chromium.
7. Wait for the right page state
Navigation completion is not always the same as “the content I need is ready.” A page can keep network connections open, render data after navigation, or lazy-load images only after scrolling. Choose a wait condition that reflects the output:
load: wait for the load event; a useful baseline for ordinary pages.domcontentloaded: wait for initial document parsing, then explicitly wait for a target element or application state.networkidle/networkidle2: wait for network activity to settle; this can time out on pages with analytics, polling, or persistent connections.- Selector or app-specific condition: usually the most precise option when a known element marks readiness.
- Fixed delay: simple but often slower than necessary and still not proof of readiness.
For screenshot capture, set the viewport before navigation if responsive layout matters. For full-page images, remember that the image can become very tall and memory-intensive. Lazy-loaded content may need scrolling or a deliberate readiness check before capture. For tests, assert an expected element or state; do not treat a screenshot file existing as proof that the page was correct.
8. Run a headless browser in CI or the cloud
A CI runner needs the browser binary and its operating-system dependencies, plus enough memory and temporary disk for browser processes and artifacts. With Playwright, install browsers using the CLI for the installed package version; on supported Linux CI images, npx playwright install --with-deps chromium installs Chromium and dependencies. Keep the installation step aligned with the package lockfile.
When browser work must run as a service, a container platform such as Cloud Run is one documented option. Google Cloud describes installing Chromium in the container and controlling it with Puppeteer, Playwright, or the Chrome DevTools Protocol. Local runs do not inherently need cloud infrastructure, and the research does not establish a general cost threshold where managed execution becomes preferable.
Operational practices
- Close pages and browsers in
finallyblocks so failures do not leave processes behind. - Limit concurrency to the memory and CPU available; each browser context/page adds work.
- Set navigation and job timeouts, and log the URL, browser/framework versions, response status, and failure stage.
- Retry transient navigation failures selectively. Avoid blindly repeating deterministic errors such as invalid URLs or selector mistakes.
- Keep credentials out of source code and logs. Use scoped credentials and pass them through the runtime’s secret mechanism.
- Capture diagnostic artifacts on failure, such as a screenshot or trace, while considering whether the page contains private data.
9. ScreenshotNeo: skip managing the browser
If the task is to capture a website rather than automate a custom browser journey, ScreenshotNeo provides a screenshot API and MCP server. One GET request returns PNG, JPEG, WebP, or PDF. The API supports full-page capture, selectors, device/viewport options, waits, custom CSS and JavaScript, request blocking, caching, async jobs, and other capture controls; see the ScreenshotNeo API documentation for parameters.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
-o shot.webp
Python
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://stripe.com'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));
For AI agents, ScreenshotNeo also provides an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and MCP clients.
Cookie and consent banners are accepted like a visitor and more than 60 known consent platforms, newsletter popups, and chat widgets are removed before capture; each step can be disabled. Bot checks/CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed. Response headers identify the page verdict and billing status. The free plan includes 1,000 shots a month with no card; paid plans start at $5 for 3,000 shots. All features are on every plan. You can inspect the full options in the docs and start with 1,000 free screenshots per month, no card required.
10. Performance, reliability, and cost
Headless removes the visible interface; it does not remove the cost of running a browser engine. Self-hosted runs consume CPU, memory, storage, and time for browser installation and maintenance. Keep pages focused, avoid unnecessary browser instances, and tune concurrency instead of starting a new browser for every small step. Reuse a browser process for related work when your framework’s lifecycle model allows it, while isolating sessions with separate contexts where needed.
Headless Shell may be more performant for tasks that do not need the complete Chrome feature set, according to Puppeteer’s documentation, but it does not completely match regular Chrome. Choose based on measured behavior for your task; the available sources do not establish a universal benchmark. Reliability depends on matching the browser build, fonts, dependencies, network conditions, page readiness, and timeout settings to the environment that runs the job.
Self-hosting shifts cost into infrastructure and engineering time. Cloud execution adds a managed deployment path but still requires checking the provider’s pricing and resource needs for your workload; no general cost comparison is established here. ScreenshotNeo’s stated plans are 1,000 free shots per month with no card, then Starter $5 for 3,000, Growth $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000, and Business $249 for 1,000,000; yearly billing gives two months free. Only clean shots are billed.
11. Troubleshooting common failures
| Symptom | Likely cause | What to do |
|---|---|---|
| Browser executable not found | The browser was not installed, or the framework expects its managed binary. | Run the framework’s browser install command for the installed version, or configure the actual executable path deliberately. |
| Missing shared library or launch failure in Linux | Required OS dependencies are absent in the container/runner. | Install framework system dependencies or use a supported CI/container base and rebuild the image. |
--headless=old no longer works |
The old mode was removed from the Chrome binary starting with Chrome 132. | Use current --headless, or use the standalone chrome-headless-shell only if that behavior is required. |
| Page navigation times out | Slow resources, persistent network traffic, blocked access, or a wait condition that never occurs. | Use an explicit timeout and a readiness selector; avoid network-idle waits for pages that keep connections open. |
| Screenshot is blank or missing dynamic content | Capture ran before the application rendered, or content depends on client-side state. | Wait for a meaningful selector/state, inspect the response and console, and verify required API calls are reachable. |
| Headless output differs from visible Chrome | Different browser mode/build, viewport, fonts, scale, or environment. | Pin the browser/channel and viewport; compare using the same framework-matched binary. For Playwright, note its default shell distinction. |
| Playwright reports browser revision mismatch | The package was updated without installing its matching browser binaries. | Run playwright install after updating the package and ensure CI uses the locked version. |
| Process hangs or resource use grows | Browser/page not closed, concurrency is too high, or jobs wait indefinitely. | Close resources in cleanup paths, cap parallel jobs, and set navigation and overall job timeouts. |
| Bot check or CAPTCHA appears | The site is presenting an access check. | Do not assume headless mode bypasses it. For ScreenshotNeo, bot checks/CAPTCHAs are identified as non-billable; the API does not promise to defeat them. |
12. FAQ
Does headless mean the browser does not render the page?
No. It renders without displaying a visible browser window.
Can I use a headless browser for a visual test?
Yes. Pin the browser mode, version, viewport, and relevant environment so image comparisons are meaningful.
Is headless always faster?
No universal speed claim follows from the mode alone. Puppeteer describes a possible performance advantage for Headless Shell in jobs that do not need the full feature set.
Do I need cloud hosting to run one?
No. You can run a browser locally or in CI. Cloud hosting is an option when your automation needs a deployed execution environment.
When should I use an API instead?
When the required output is a screenshot or PDF and you do not need to control a custom browser journey, an API can remove browser setup and lifecycle work.
Sources: Chrome Headless mode, Puppeteer Headless modes, Playwright browsers, Puppeteer overview, and Google Cloud Run browser automation.


