What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Use a real browser engine in headless mode, drive an explicit user journey, and assert the result. Headless means the browser has no visible window; it can still navigate, execute JavaScript, render pages and interact with controls. Test quality comes from the actions and assertions you define, not from hiding the UI.
This guide shows a repeatable workflow with Playwright, explains when Puppeteer is a better fit, covers browser installation and CI, and shows how to collect evidence when a run fails.
What headless browser testing actually changes
In headless mode, the browser runs without displaying a desktop window. The same page lifecycle still occurs: navigation, DOM updates, network requests, JavaScript execution and user-like input. Chrome’s current Headless mode shares code with headful Chrome. Chrome documentation says that, since version 132.0.6793.0, the old headless implementation is available as a separate chrome-headless-shell binary; it is not the same choice as running the full Chrome browser without a window. Chrome’s Headless documentation describes the modes and version boundary.
Headless is therefore a launch setting, not a test strategy. A useful test still needs a meaningful journey and an assertion about the expected outcome. A screenshot can show a visual problem, but it cannot replace an assertion that a form submitted, a route changed or a confirmation appeared.
#1 Best Overall
Choose Playwright or Puppeteer
| Choose | What the official documentation establishes | Best fit |
|---|---|---|
| Playwright | Supports Chromium, Firefox and WebKit, plus selected Chrome and Edge channels. Its releases are coupled to browser binaries. | One test workflow across engines, browser projects, device emulation and built-in test diagnostics. |
| Puppeteer | A JavaScript library for Chrome and Firefox automation over CDP or WebDriver BiDi. Documented uses include navigation, interaction, screenshots, PDFs, UI testing and performance analysis. | JavaScript teams focused on Chrome/Firefox automation or browser scripting. |
Neither tool is universally best. Decide using browser-engine coverage, fidelity to the browser your users run, your language and existing test stack, CI installation effort, debugging artifacts, and whether you need end-to-end behavior, visual comparison, PDF output or general automation. See Playwright’s browser guide and Chrome’s Puppeteer documentation for the supported capabilities.
Install Playwright and its browser
Local installation
- Install Node.js and create a project:
mkdir headless-site-tests && cd headless-site-tests && npm init -y. - Install Playwright Test:
npm install -D @playwright/test. - Download the browser binaries:
npx playwright install. - On a Linux CI runner, install the documented operating-system dependencies too:
npx playwright install --with-deps.
Playwright browser versions track Playwright releases. After upgrading @playwright/test, run the install command again rather than relying on an older binary. If you cache browsers in CI, key that cache to the Playwright version so a package update cannot silently reuse an incompatible build. The supported installation choices, including Chromium-only headless shell installation, are documented at playwright.dev/docs/browsers.
Full Chromium versus the headless shell
For a headless-only CI job, Playwright documents installing only its Chromium headless shell. If you need the current Chrome implementation, use the documented Chromium channel or a branded channel explicitly. Do not assume the shell and the full browser have identical behavior. Playwright’s browser guide reproduces Chrome’s statement: “New Headless on the other hand is the real Chrome browser, and is thus more authentic, reliable, and offers more features.” That is Chrome’s characterization, not an independent benchmark.
Write a real user-journey test
Start with a high-value path: load a page, perform an action and verify the resulting state. Prefer locators based on accessible names or stable, user-facing semantics instead of brittle CSS generated by a framework.
Recommended Free Tools
Rank #2
Runnable Playwright test
Create tests/checkout.spec.js:
import { test, expect } from '@playwright/test';
test('user can submit the contact form', async ({ page }) => {
await page.goto('https://example.com/contact', { waitUntil: 'domcontentloaded' });
await page.getByLabel('Name').fill('Ada Lovelace');
await page.getByLabel('Email').fill('ada@example.com');
await page.getByLabel('Message').fill('Please contact me.');
await page.getByRole('button', { name: 'Send' }).click();
await expect(page.getByRole('status')).toContainText('Message sent');
await expect(page).toHaveURL(//thanks/);
});
Replace the URL, labels and expected result with your application’s actual contract. Run it headlessly (the default) with npx playwright test. To see the browser while debugging, use npx playwright test --headed.
Assertions that matter
- Confirmation text or a success status after a submission.
- A changed heading, route or account state after navigation.
- Expected content loaded from the server or API.
- An enabled, disabled or hidden control when the workflow requires that state.
Playwright waits for conditions associated with its assertions. Keep assertions specific enough to detect a regression without coupling the test to incidental markup.
Add visual evidence without confusing it with behavior
Playwright supports page, element and full-page screenshots. Save one when a failure needs visual inspection:
await page.screenshot({ path: 'artifacts/contact-page.png', fullPage: true });
await expect(page).toHaveScreenshot('contact-page.png');
Screenshot comparison waits for stable consecutive screenshots before comparing with the expectation. Use visual assertions for layout, typography and visual regressions; retain behavior assertions for whether the journey actually worked. The APIs and comparison behavior are documented in Playwright’s screenshot guide and PageAssertions.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Rank #3
Use Puppeteer when its model fits your stack
Install it in a JavaScript project with npm install puppeteer. Puppeteer normally downloads a compatible Chrome during installation. If package-manager install scripts are blocked, follow the manual browser-installation approach in the official Puppeteer guide.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 800 });
await page.goto('https://example.com', { waitUntil: 'networkidle0' });
const heading = await page.locator('h1').innerText();
if (!heading) throw new Error('Expected an h1');
await page.screenshot({ path: 'example.png', fullPage: true });
await browser.close();
Puppeteer’s documented controls include navigation, viewport selection, keyboard input, locator interaction and reading page text. Its browser protocol choices are CDP and WebDriver BiDi, so check which protocol and browser versions your project requires.
Run headless tests in continuous integration
- Use a clean CI job and install the exact framework version from your lockfile.
- Install matching browsers and Linux dependencies with
npx playwright install --with-deps, or perform Puppeteer’s documented browser installation. - Run the test command, for example
npx playwright test. - Upload the HTML report, trace files, screenshots and videos as CI artifacts when a test fails.
- If browser binaries are cached, include the Playwright version in the cache key and invalidate the cache after upgrades.
Playwright launches headlessly by default in CI. Its CI guidance is at playwright.dev/docs/next/ci. Keep the browser, framework and operating-system image aligned; a stale binary can create failures that do not reproduce locally.
Diagnose a failed run
Open the trace first
Enable tracing in your Playwright configuration or test command, then open the resulting trace with the Playwright trace viewer. A trace can expose the action sequence, DOM snapshots, action details, console messages, network requests and source around the failure. This is usually more useful than a final screenshot alone. The workflow is documented at Playwright’s debugging guide.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problemsRank #4
- Used Book in Good Condition
Common symptoms and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Browser executable is missing | The package was installed without its browser or the cache is stale. | Run npx playwright install (or --with-deps in CI) and key caches to the framework version. |
| Works locally, fails in CI | Different browser build, OS dependencies, environment variables or timing. | Align versions, install dependencies, inspect the trace and wait for a meaningful UI condition rather than an arbitrary delay. |
| Locator times out | The accessible name changed, the element is inside a frame, or the page has not reached the expected state. | Inspect the DOM snapshot, use a stable role/label, target the correct frame and assert the state that makes the control available. |
| Blank or incomplete screenshot | Lazy content has not loaded, a navigation is still in progress, or the page is blocked. | Wait for the relevant selector or network state, verify console and network errors, and capture after the content is visible. |
| Visual diff is noisy | Animations, fonts, time, viewport or data vary between runs. | Use a fixed viewport and deterministic data, disable animations where appropriate and compare only after the page is stable. |
Debug headed, ship headless
When watching the interaction is useful, run the same test with --headed locally. Do not change the assertions merely to make a headed run pass; headed mode is a diagnostic view of the same journey.
Reliability and performance practices
- Test the critical journeys first; a smaller deterministic suite is more valuable than many flaky scripts.
- Use explicit waits for selectors, URL changes or application states. Fixed sleeps should be reserved for cases where the product itself has a known delay.
- Control external data and feature flags in CI so a changing backend does not masquerade as a browser failure.
- Run a smoke project on every commit and broader browser projects on the schedule appropriate to your release risk.
- Capture traces only on retry or failure if artifact size becomes a concern.
- Test the browser channel that reflects your support policy. Playwright’s bundled Chromium can run ahead of stable branded channels, which is useful for early compatibility checks but is not an exact substitute for testing a production target.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server for developers. It is useful when you need a rendered artifact rather than a full interaction test: one GET request returns a PNG, JPEG, WebP or PDF.
Use the documented API examples below; options include full-page capture with lazy images loaded, CSS-selector element capture, dark mode, device presets or custom viewports, retina scale, custom CSS and JavaScript, click and wait conditions, hidden selectors, request/resource blocking, headers, cookies, user agent, authorization, timezone, geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API and an OpenAPI specification. Parameter names used by other screenshot APIs also work, easing migration. Every feature is on every plan.
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)
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}`);
See the ScreenshotNeo API documentation for parameters and response handling. Before capture, it accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups and chat widgets; each step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits cost nothing, and the response identifies the result with X-Page-Verdict and X-Billed headers. An MCP server provides take_screenshot, get_page_info and capture_pdf tools to Claude, Cursor and other MCP clients.
The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; yearly billing gives two months free. Create a free ScreenshotNeo account to try it with no card.
Best Value
FAQ
Does headless mode test a different website?
It tests the same browser-driven page lifecycle, but browser channel, version, viewport, fonts and operating system can still affect rendering. Test the channels that match your support policy.
Should every test take a screenshot?
No. Save screenshots for visual assertions or failure diagnosis; use explicit assertions for behavior.
Can I use the Chromium headless shell for every test?
Only when its behavior matches your target. The shell is a separately shipped option; use the full Chromium or a branded channel when fidelity to that browser matters.
Quick Recap
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.

