Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

To run a headless browser in JavaScript: install Playwright or Puppeteer, install a compatible browser build, launch it without a visible window, create a page, navigate or interact, collect output such as text or a screenshot, and close the browser in a finally block. Playwright is the better default when you need Chromium, Firefox, and WebKit; Puppeteer is a straightforward Chrome-centered option.

What “headless” means

A headless browser is a real browser engine controlled by code but rendered without a desktop window. It can load HTML, execute JavaScript, apply CSS, wait for network activity, click controls, fill forms, read the DOM, create screenshots, and generate PDFs. It is useful for automated tests, documentation, monitoring, previews, scraping of sites you are authorized to access, and server-side rendering workflows.

Headless does not mean “HTTP requests only.” The browser still has to download an engine and its dependencies, and a page may behave differently depending on the engine and headless mode. Test the same browser family and mode you will use in production.

Choose Playwright or Puppeteer

Question Playwright Puppeteer
Browser coverage Official support for Chromium, Firefox, and WebKit. High-level automation focused on Chrome, with Firefox support documented by the project.
Browser provisioning Install matching browser builds with the Playwright CLI. puppeteer normally downloads a compatible Chrome; puppeteer-core does not.
Default behavior Browsers launch headlessly by default. Headless is the default.
Best fit Cross-engine testing and explicit browser-version management. Chrome-centered scripts or an already managed/remote browser.

Playwright documents its supported browsers and platform requirements in its installation guide. Its browser binaries are version-coupled to the Playwright release. Puppeteer’s package and installation behavior are described in the project documentation.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Install Playwright

Use the starter project

For a new project, run:

npm init playwright@latest

The starter setup creates a test project. For a library script, install the package directly:

npm install playwright
npx playwright install

Install only one engine when appropriate:

npx playwright install webkit

On Linux or CI, install Chromium and its required operating-system packages:

npx playwright install --with-deps chromium

If you need only Playwright’s headless shell, the browser documentation also describes --only-shell. After upgrading Playwright, rerun the browser installer if the required binary is missing or has changed; see Playwright browser installation.

Install Puppeteer

Install the managed package when you want Puppeteer to download Chrome:

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
npm i puppeteer

Some package managers or security settings block install scripts. If Chrome was not downloaded, run:

npx puppeteer browsers install

Alternatively, allow the package’s install script according to your package manager’s policy. Use puppeteer-core when your organization supplies the browser or exposes a remote endpoint:

npm i puppeteer-core

With puppeteer-core, provide an executable path or connection details yourself; it intentionally does not download Chrome.

Minimal Playwright script

This CommonJS example launches WebKit, opens a page, captures a screenshot, and always closes the browser:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const { webkit } = require('playwright');

(async () => {
  const browser = await webkit.launch();
  try {
    const page = await browser.newPage();
    await page.goto('https://playwright.dev/', { waitUntil: 'domcontentloaded' });
    await page.screenshot({ path: 'example.png', fullPage: true });
    console.log(await page.title());
  } finally {
    await browser.close();
  }
})();

The documented Playwright library flow is launch, create a page, navigate, take a screenshot, and close the browser; browsers are headless by default. See the JavaScript library example.

Useful Playwright actions

await page.goto(url, { waitUntil: 'networkidle' });
await page.locator('input[name="q"]').fill('headless browser');
await page.getByRole('button', { name: 'Search' }).click();
await page.waitForSelector('#results');
const text = await page.locator('body').innerText();
await page.screenshot({ path: 'page.webp', type: 'webp' });

Use a selector wait when a specific component matters. Use a bounded timeout rather than waiting forever, and avoid treating networkidle as a universal readiness signal on sites with analytics or long-lived connections.

Minimal Puppeteer script

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
  await page.screenshot({ path: 'example.png', fullPage: true });
  console.log(await page.title());
} finally {
  await browser.close();
}

Puppeteer’s getting-started sequence uses launch(), newPage(), navigation, page operations, and browser.close(); headless operation is the default. See Puppeteer getting started.

Connecting to a separately managed browser

import puppeteer from 'puppeteer-core';

const browser = await puppeteer.launch({
  executablePath: process.env.CHROME_PATH,
  headless: true
});
try {
  const page = await browser.newPage();
  await page.goto('https://example.com');
} finally {
  await browser.close();
}

Set CHROME_PATH to an executable that exists in the runtime image, or use Puppeteer’s documented remote connection APIs when the browser runs elsewhere.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Headless modes and fidelity

Playwright’s regular headless Chromium uses a separate headless shell. Its documentation also describes the newer headless mode through the Chromium channel. If only that mode is required, --no-shell avoids downloading the separate shell. Choose explicitly when visual fidelity or deployment size matters.

Puppeteer documents headless: 'shell' for Chrome’s headless shell. Shell mode can be more performant when the full feature set is unnecessary, but it does not completely match regular Chrome. Test screenshots, PDFs, font rendering, media playback, and browser APIs in the exact mode you deploy. There is no controlled benchmark here that establishes one library as universally faster or more reliable.

// Playwright: request the newer Chromium headless mode
const { chromium } = require('playwright');
const browser = await chromium.launch({ channel: 'chromium' });

// Puppeteer: choose the shell explicitly
const browser = await puppeteer.launch({ headless: 'shell' });

Make scripts reliable

Always clean up

Put the work in try/finally. If navigation, a selector, or an assertion throws, the browser process still needs to close. A leaked process can keep a local script, container, or CI job alive.

Use deterministic readiness checks

  • Prefer waitForSelector or a role/locator assertion for the element your next action needs.
  • Use waitUntil: 'domcontentloaded' for a useful early milestone; wait for a particular application signal before extracting data.
  • Give navigation and actions finite timeouts and log the URL, browser, and failing selector.
  • Set a fixed viewport, timezone, locale, and device scale when pixel output must be repeatable.

Control resources

Reuse a browser process for multiple pages rather than launching one per URL. Create separate contexts when cookies or permissions must be isolated. Block unnecessary images, fonts, ads, or third-party requests only when doing so will not change the behavior you are testing. Limit concurrency so the host has enough CPU, memory, file descriptors, and temporary storage.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Respect access and privacy

Automate only sites and accounts you are authorized to access. Keep credentials out of source code, use environment variables or a secret manager, and avoid recording sensitive page content in logs or screenshots.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If your goal is a dependable website screenshot rather than browser automation, ScreenshotNeo provides a single-request screenshot API and MCP server. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result.

Its API supports PNG, JPEG, WebP, and PDF output, with full-page capture, lazy-image loading, CSS-element capture, device presets, custom viewports, retina scale, dark mode, custom CSS and JavaScript, clicks, selector waits, network-idle or delay waits, request/resource blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, TTL caching, signed image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, usage reporting, and an OpenAPI specification. An MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

See the ScreenshotNeo documentation for parameters and response headers. The same request from Python is:

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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)

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

The Free plan includes 1,000 screenshots per month without a card. Paid plans start at $5 for 3,000 shots; every feature is available on every plan. Create a free ScreenshotNeo account.

Troubleshooting

“Executable doesn’t exist” or browser launch failure

For Playwright, run npx playwright install (or the engine-specific command) after installing or updating the package. For Puppeteer, check whether install scripts were blocked and run npx puppeteer browsers install, or switch to an explicitly managed executable with puppeteer-core.

Linux reports missing shared libraries

Install the documented dependencies with npx playwright install --with-deps chromium, or add equivalent browser libraries to your container image. Do not assume a desktop development machine’s packages exist in CI.

CI output differs from local output

Compare the browser build, headless mode, viewport, fonts, locale, timezone, and installed dependencies. Playwright’s shell and newer Chromium headless modes are distinct; Puppeteer’s shell mode does not fully match regular Chrome.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The process hangs after the script finishes

Ensure every success and error path reaches browser.close(). Also inspect whether an unawaited page task, server, timer, or child process is keeping Node.js alive.

Navigation times out

Check DNS and outbound network access, then capture the failing URL and response status. Increase the timeout only when the site is legitimately slow; otherwise wait for a specific selector, block an unnecessary resource, or handle authentication and consent before the wait.

Quick decision checklist

  • Need Chromium, Firefox, and WebKit coverage? Start with Playwright.
  • Need a Chrome-focused script with an automatically downloaded browser? Start with Puppeteer.
  • Already manage Chrome in a container or remote service? Use puppeteer-core or Playwright’s configured executable/connection.
  • Need only a clean, repeatable screenshot or PDF? Use ScreenshotNeo instead of maintaining browser binaries.
  • Need interactive workflows, assertions, or custom in-page logic? Keep a library script and make readiness, cleanup, and browser mode explicit.

Frequently Asked Questions

Can I run a headless browser on a server without a display?

Yes. Playwright and Puppeteer launch headlessly by default, so a desktop display is not required. Linux environments still need the browser’s shared libraries and fonts.

Should I use headless Chromium or the headless shell?

Use the mode that matches your target. The shell can reduce overhead, but Puppeteer documents that it does not completely match regular Chrome; Playwright also distinguishes its shell from newer Chromium headless mode.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Does Puppeteer install a browser automatically?

The puppeteer package normally downloads a compatible Chrome. puppeteer-core deliberately omits that download and requires your own executable or browser connection.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.