ScreenshotNeo

BlogHow-to

How to Use Playwright Stealth for Browser Automation

Set up Playwright Stealth in Node.js, understand the Python option and its limits, and make authorized browser tests reproducible.

By the ScreenshotNeo team4 October 20269 min read

Short answer: In Node.js, use playwright-extra with puppeteer-extra-plugin-stealth. Register the plugin on chromium before calling launch(). It applies changes intended to reduce some common automation signals; it cannot guarantee that a browser will avoid detection. Use it only for sites and applications you own or have permission to assess.

This guide covers a minimal runnable setup, configuration choices, a separate Python package, reproducibility, limitations, and common failures. The examples navigate to https://example.com; for meaningful tests, replace it with an authorized target you control.

1. What “Playwright Stealth” means

“Playwright Stealth” usually means running Playwright through the playwright-extra wrapper and adding the puppeteer-extra-plugin-stealth plugin. playwright-extra adds plugin support around Playwright; the stealth plugin is a separate package originally associated with Puppeteer. The plugin applies a collection of browser changes intended to make some automation signals less obvious. See the playwright-extra README and the stealth plugin README.

This is useful when testing how your own application behaves under a browser configuration with fewer common automation indicators. It is not an authorization mechanism, CAPTCHA solver, or guarantee of access. Detection may also use network, session, request-pattern, and behavioral information that the plugin does not control.

2. Node.js setup with playwright-extra

Install

Use Node.js with npm. In a new project, install the wrapper, plugin, and Playwright:

npm init -y
npm install playwright playwright-extra puppeteer-extra-plugin-stealth
npx playwright install chromium

The explicit browser installation step downloads Playwright’s Chromium build. If the environment already has a compatible browser installed, follow Playwright’s browser documentation for its browser and channel options.

Runnable CommonJS example

Save this as stealth-example.cjs and run node stealth-example.cjs. The plugin registration must happen before the browser launches.

const { chromium } = require('playwright-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');

chromium.use(StealthPlugin());

(async () => {
  let browser;
  try {
    browser = await chromium.launch({ headless: true });
    const page = await browser.newPage();
    const response = await page.goto('https://example.com', {
      waitUntil: 'domcontentloaded',
      timeout: 30000,
    });

    console.log({
      status: response?.status(),
      title: await page.title(),
      url: page.url(),
    });
  } finally {
    await browser?.close();
  }
})().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

ES modules

For an ES module project, set "type": "module" in package.json, then use the documented import pattern:

import { chromium } from 'playwright-extra';
import StealthPlugin from 'puppeteer-extra-plugin-stealth';

chromium.use(StealthPlugin());

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

Keep plugin setup in one shared module if your project has multiple test files. Register it once before launching any browser through that configured chromium object.

3. Configuration and browser choices

The compact setup deliberately uses the plugin defaults and Playwright’s default Chromium headless mode. Start there when your goal is repeatable application testing. Change one variable at a time and record it so a changed result can be traced to a changed browser or setting.

Choice What it affects Practical guidance
Plugin defaults Enables the plugin’s bundled evasion techniques. Use defaults for the initial test. The plugin README describes selecting individual evasions, but their availability and behavior are package-version dependent; consult the installed version’s documentation before customizing.
headless Whether the browser displays a window. Use the same mode as the environment you are trying to reproduce. Headed and headless runs can behave differently.
channel Which browser distribution Playwright launches. Playwright supports Chromium and branded Chrome or Edge channels where installed. Its default headless Chromium uses a headless shell; Chrome and Edge have a newer headless implementation closer to headed mode. See Playwright browser documentation.
Viewport and context options Page dimensions and browser context configuration. Set values to match the test scenario. Avoid changing many environment details at once; the result becomes harder to reproduce and diagnose.
Navigation wait and timeout When the script proceeds and how long it waits. Use domcontentloaded for a basic document-ready check. Use a longer timeout or a target-specific locator when the application needs more time; do not treat a timeout increase as a detection fix.

For example, Playwright can launch an installed stable Chrome channel with chromium.launch({ channel: 'chrome', headless: true }), or use channel: 'chromium' to opt into its newer headless mode. The browser must be available in the environment. Playwright notes that headed and headless browser modes can differ; choose based on the browser behavior your test needs to cover, then pin and record that choice.

Do not add random user-agent, locale, viewport, or identity changes as a general remedy. In owned-system testing, use values representative of the test case and log them. Arbitrary changes make failures less reproducible and can create combinations your application never needs to support.

4. Python is a separate package

There is a Python package named playwright-stealth; it is not the same package or API as the Node.js combination above. Its Python interface is version-sensitive. The package page documents the Stealth().use_async(async_playwright()) pattern, and explicitly cautions against expecting it to bypass anything beyond the simplest detection. Check the current PyPI project page and pin a version that your project has reviewed.

Example for the documented 2.0.0 API:

python -m pip install "playwright-stealth==2.0.0" playwright
python -m playwright install chromium
import asyncio
from playwright.async_api import async_playwright
from playwright_stealth import Stealth

async def main():
    async with Stealth().use_async(async_playwright()) as p:
        browser = await p.chromium.launch(headless=True)
        try:
            page = await browser.new_page()
            response = await page.goto(
                "https://example.com",
                wait_until="domcontentloaded",
                timeout=30_000,
            )
            print({
                "status": response.status if response else None,
                "title": await page.title(),
                "url": page.url,
            })
        finally:
            await browser.close()

asyncio.run(main())

Do not assume this Python call works unchanged across package versions. Review that version’s API and changelog, pin it, and keep the Playwright package and browser installation aligned with the project’s supported setup.

5. Test responsibly and make results reproducible

  1. Use a staging system, local application, or other target you own or have explicit permission to assess.
  2. Define the application behavior under test, such as whether a login flow, page rendering, or test fixture behaves consistently.
  3. Run controlled cases. Change only one relevant factor at a time, such as browser channel or headless mode.
  4. Record the result as an observation for that exact detector, target, browser build, and configuration. A result on one diagnostic page does not establish how another site will respond.
  5. Keep logs useful and safe: capture package/browser versions, status and relevant error messages, while avoiding secrets and personal data.

A useful run note includes the Node.js or Python version, Playwright version, stealth package version, browser build or channel, headed/headless mode, operating environment, target environment, and test date. For regression testing, keep the target and test procedure stable and compare changes over time.

The plugin maintainer describes this area as a “cat and mouse game” and says it is probably impossible to prevent every way of detecting headless Chromium. Those are maintainer statements, not a guarantee or independent current benchmark. Playwright browser choice also matters: its browser documentation explains that the default headless shell and branded Chrome or Edge headless implementations may behave differently. A controlled 2026 preprint further studies behavioral signals in a specific benchmark; it does not establish that every site or detector uses those signals. See the arXiv research listing for the paper referenced in this research pass.

6. Troubleshooting

Symptom Likely cause What to check
Cannot find module or import error Dependency missing, wrong working directory, or CommonJS/ESM mismatch. Run the script from the project where packages were installed. Check package.json module mode and use the matching CommonJS or ESM example.
Browser executable missing Playwright’s browser binary has not been installed in this environment. Run npx playwright install chromium. In CI, ensure installation happens in the same environment or image where the script runs.
Browser fails to launch in CI/container Missing browser dependencies, incompatible OS image, or execution restrictions. Use Playwright’s documented installation steps for your operating system and CI image. Reproduce locally with the same browser build where possible.
Plugin appears to have no effect Registration occurred after launch, a different browser object was launched, or the target uses signals outside the plugin’s scope. Ensure chromium.use(StealthPlugin()) runs before chromium.launch(). Then inspect the owned target’s diagnostics; do not infer universal detection results.
Python reports missing use_async or import names Installed playwright-stealth version differs from the example. Check the installed version and that version’s PyPI documentation. Pin a version instead of assuming APIs are interchangeable.
Navigation times out Slow application, network problem, redirect, or page waiting condition that never occurs. Check network access and response status. Try domcontentloaded for basic navigation, increase timeout only when justified, and wait for a known application selector if needed.
Test differs between local and CI Browser version, headless implementation, OS, fonts, policies, or dependencies differ. Record the environment and browser channel; align versions and run a controlled comparison. Chrome and Edge may use a different headless implementation than Playwright’s default Chromium headless shell.
Target still blocks or challenges the browser The site may use signals or access policies the plugin cannot change. For your own application, inspect server-side logs and test policy configuration. For someone else’s service, use its documented access route and obtain authorization; do not treat a stealth plugin as a way around access controls.

7. Performance, reliability, and cost

The plugin adds browser-side setup, and launching a browser has its own startup and memory costs. For a test suite you control, reuse a browser process where appropriate, create isolated contexts for independent cases, and close pages, contexts, and browsers when finished. Bound parallel runs according to available CPU and memory. These are general browser automation practices, not a performance benchmark for the stealth plugin.

For reliability, pin package versions, install the intended browser in CI, and treat plugin or browser updates as changes that can affect test behavior. Avoid relying on a single detector page or a single passing run as proof that a configuration is universally undetectable. There is no broadly applicable success rate established by the research for this guide, so no detection percentage or performance number is claimed here.

The Node.js and Python packages are software dependencies; the cited package pages do not establish a service price. Include the browser runtime, CI minutes, storage for artifacts, and maintenance time in your own project’s cost estimate. Use screenshots, traces, or logs only as needed for debugging, and handle any captured data according to your organization’s policies.

8. Or skip the browser setup

If your goal is to get a clean screenshot rather than run browser automation, ScreenshotNeo is a website screenshot API and MCP server. A single GET request takes a URL and returns an image or PDF. Its capture flow accepts cookie/consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before the shot; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server offers take_screenshot, get_page_info, and capture_pdf for AI agents using Claude, Cursor, or another MCP client.

For an authorized page, the following cURL request saves a WebP screenshot:

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

See the ScreenshotNeo API documentation for authentication and options. Plans include 1,000 screenshots per month free with no card; paid plans start at $5 for 3,000 screenshots. Create a free ScreenshotNeo account.

9. FAQ

Does Playwright Stealth work?

It applies techniques intended to reduce some common automation indicators. Whether a specific target accepts a browser depends on that target, the browser configuration, and signals beyond the plugin. There is no universal guarantee.

Is puppeteer-extra-plugin-stealth the same as Playwright?

No. It is a separate plugin used with the playwright-extra wrapper to add plugin support to Playwright’s Chromium interface.

Can I use it with Firefox or WebKit?

The documented integration in this guide registers the plugin on chromium. Do not assume the same plugin behavior or support for other Playwright browser engines.

Should I use a detector test site?

Only when you have permission and a clear testing purpose. Treat its output as a diagnostic observation for that particular test, not a certification or prediction for other sites.

What should I try first when a test is flaky?

Record and align the browser build, channel, headless mode, operating environment, and package versions. Then isolate one variable and inspect the application’s own logs and diagnostics.