How to Use Stealth Mode for Browser Automation
Stealth mode is not a universal Playwright switch. Choose browser settings that fit authorized tests, and treat third-party stealth packages as limited, changeable tools.
There is no universal “stealth mode” switch in Playwright. For authorized end-to-end tests, start with the browser engine and headless or headed mode that match the product and test environment. A third-party package such as playwright-stealth can adjust some browser signals, but it does not make automation undetectable or reliably bypass bot checks.
Use automation only on sites and environments you own or are explicitly authorized to test. If a challenge or access control blocks an authorized test, ask the site operator for test access or use an approved API. Do not treat evasion as a routine way to get around a site’s controls.
1. Start with the authorized test goal
Before changing browser signals, identify what the test is meant to represent:
- Your own staging or local app: Prefer a test account, allowlisted test environment, or dedicated test configuration. Keep checks that are part of the product’s security behavior in the test plan.
- Cross-browser regression coverage: Run the supported browser engines directly. A stealth package does not replace browser compatibility testing.
- Authorized measurement or research: Record the target, permission, browser version, and configuration. If the site blocks the run, coordinate with its operator or use an approved data interface.
A few adjusted JavaScript properties cannot establish that a browser is indistinguishable from a person. Detection can use browser, network, and interaction signals, and those signals change over time.
2. Pick a browser and mode that match the test
Playwright supports Chromium, Firefox, and WebKit. It can also use branded Chrome and Edge channels. Choose based on the browser your product supports, the browser release you need to cover, or the engine required for compatibility testing. Playwright’s browser guide documents these options and its Chromium modes: Playwright browser documentation.
| Configuration | When it fits | Trade-off |
|---|---|---|
| Default Chromium headless | Fast repeatable CI runs and ordinary test suites | Uses Playwright’s Chromium headless shell by default; it is not the same configuration as every headed or branded browser. |
Chromium with channel: 'chromium' |
Tests needing Playwright’s newer Chromium headless mode | May behave differently from the default headless shell. Match it to the test target and pin versions. |
| Headed browser | Visual debugging, manual investigation, or a test explicitly targeting headed behavior | Needs a display in many CI environments and is usually less convenient for unattended runs. |
| Firefox or WebKit | Coverage for supported non-Chromium engines | Browser differences are the purpose; do not patch them to imitate Chromium. |
| Branded Chrome or Edge | Regression coverage against a public browser channel, enterprise policy, or browser-specific behavior | Playwright does not install branded browsers by default; enterprise policies can affect automation. |
Playwright’s guide distinguishes its default Chromium headless shell from newer headless mode, selected with the chromium channel. It quotes Chrome’s documentation describing the newer mode as “more authentic, reliable” for high-accuracy end-to-end and extension testing; that statement concerns this mode, not stealth or detection avoidance.
3. Run a baseline Playwright test first
This small Python example uses documented Playwright browser controls and visits a URL you are authorized to test. Install Playwright and its Chromium browser, save the script as capture_page.py, then run it.
python -m pip install playwright
python -m playwright install chromium
import asyncio
from playwright.async_api import async_playwright
async def main():
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
context = await browser.new_context(viewport={"width": 1440, "height": 900})
page = await context.new_page()
response = await page.goto("https://your-authorized-test-site.example", wait_until="domcontentloaded", timeout=30_000)
print({"status": response.status if response else None, "title": await page.title(), "url": page.url})
await page.screenshot(path="page.png", full_page=True)
await browser.close()
asyncio.run(main())
Use a real URL under your control in place of the example hostname. domcontentloaded waits for document parsing, not every image, API request, or application task. If the assertion depends on application readiness, wait for a specific visible element instead of sleeping for an arbitrary interval.
Useful Playwright configuration choices
headless=Trueis the normal unattended setting. Useheadless=Falsewhen you need to inspect the browser visually.p.chromium.launch(channel="chromium")opts into the newer Chromium headless mode. You can usechannel="chrome"orchannel="msedge"when the corresponding branded browser is installed.- Use
p.firefox.launch()orp.webkit.launch()for those engines. - Set viewport, locale, timezone, and test data to values relevant to the application. A consistent test context helps reproduce bugs; arbitrary spoofed values can create a configuration unlike the target.
- Keep credentials in environment variables or a secret store. Do not commit authentication state, cookies, or access tokens.
4. Add a third-party stealth package only when justified
The Python package playwright-stealth documents applying its configuration to a Playwright session or context. Its PyPI page recommends the async wrapper below and warns: “Don’t expect this to bypass anything but the simplest of bot detection methods.” Check the current package documentation before adopting it: its major versions include breaking changes, and the version shown in the dossier may not be the latest. See playwright-stealth on PyPI.
Install the package in a virtual environment, then use the wrapper pattern from its documentation:
python -m pip install playwright playwright-stealth
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)
context = await browser.new_context()
page = await context.new_page()
response = await page.goto("https://your-authorized-test-site.example", wait_until="domcontentloaded", timeout=30_000)
print({"status": response.status if response else None, "title": await page.title()})
await browser.close()
asyncio.run(main())
The package also documents applying configuration to an entire context. Follow the API for the version you have installed; do not assume older snippets still match a newer major version. Keep a baseline run without the package so you can tell whether the package changes the behavior your authorized test is measuring.
Node.js baseline example
The dossier substantiates the Python package above, not a particular JavaScript stealth package. For Node.js, use Playwright’s ordinary documented browser configuration rather than assuming a Python package or its behavior carries over.
npm install playwright
npx playwright install chromium
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({ viewport: { width: 1440, height: 900 } });
const page = await context.newPage();
const response = await page.goto('https://your-authorized-test-site.example', {
waitUntil: 'domcontentloaded',
timeout: 30_000,
});
console.log({ status: response?.status(), title: await page.title(), url: page.url() });
await page.screenshot({ path: 'page.png', fullPage: true });
await browser.close();
})();
5. Understand what stealth can and cannot tell you
“Stealth mode” is a broad label for packages or configuration choices, not a standard Playwright feature with a defined guarantee. A package can change some browser-visible properties, but the target may consider other evidence, and the package may become incompatible as browsers and detection systems evolve. Do not infer that a test passed because a page loaded once, or that a particular package defeats a named bot-management provider.
Two 2026 preprints offer bounded evidence, not universal predictions:
- A University of Bamberg study visited 10,000 websites in four browser configurations, totaling 40,000 page visits. It reported a 15% soft-block rate for Chromium headless versus 7% for the other tested configurations. The authors attributed 82% of observed blocks to bot detection, combining vendor-confirmed and inferred cases. These figures describe the study setup; they are not a test of
playwright-stealthand do not predict a particular site’s response. Read the preprint. - A separate controlled benchmark reported browser-agent behavioral signatures, including missing raw pointer-move and wheel-delta streams, and found that signal remained after trajectory manipulation in that benchmark. That result is specific to the study and does not show that every site or automation stack uses the signal. Read the preprint.
Both studies are preprints. Treat them as scoped research findings, not guarantees about production sites.
6. Keep test runs reproducible
- Pin your Playwright dependency and record its version in the test output.
- Install browser binaries after updating Playwright. The official guide recommends keeping the framework and browser binaries in sync; run
python -m playwright installafter a Python package update, ornpx playwright installfor the Node.js package. - Record the engine, channel, headless setting, operating system or CI image, viewport, locale, and test account type with each run.
- Use a fresh browser context for tests that need isolation. Reuse a context only when session persistence is part of the scenario.
- On failure, save a screenshot, relevant console messages, response status, and trace using your normal test reporting system. Keep secrets and personal data out of artifacts.
7. Troubleshooting
| Symptom | Likely cause | Practical fix |
|---|---|---|
| Browser executable is missing | The Playwright package is installed, but its browser binary is not. | Run python -m playwright install chromium or npx playwright install chromium. After a framework update, reinstall its browser binaries. |
| Works locally, fails in CI | Different browser binaries, OS dependencies, display configuration, network access, or environment variables. | Pin the framework and CI image, install the matching browsers and dependencies, and compare recorded versions and launch settings. |
| Page loads but content is missing | The chosen navigation event occurs before the app’s data or lazy content is ready. | Wait for a meaningful selector or application-ready condition. Inspect failed requests and console errors; avoid treating a fixed sleep as a readiness guarantee. |
| Navigation times out | The site is slow, long-polling, waiting on third-party resources, or unreachable from the runner. | Check network access and the specific navigation stage. Use a suitable timeout and wait condition for the app. Do not retry indefinitely. |
| A challenge, CAPTCHA, or access denial appears | The site is applying an access control or automation policy. | Stop attempts to evade it. For authorized testing, ask the operator to allow the test environment or account, or use an approved API. |
| Stealth import or method fails | The installed package version does not match the code example, or a major-version API changed. | Check the installed version and current PyPI documentation, then use the API for that version. Reproduce the issue without the package to isolate it. |
| Results differ between headless and headed | These modes can use different browser implementations or rendering conditions. | Run the mode that matches the test goal and document it. For Chromium, compare the default headless shell with channel="chromium" only when the newer mode is relevant. |
8. Performance, reliability, and cost
Headless runs are generally the practical default for CI. The headless shell can avoid downloading the full Chromium browser when that is all a test needs; Playwright documents the --only-shell install option. The newer Chromium headless mode can be installed without the shell using --no-shell. These are installation choices, not stealth settings. A headed run may be useful for visual investigation, but it requires an available display in many environments.
For reliability, favor explicit readiness checks, bounded timeouts, isolated test data, and recorded versions. A stealth package adds another dependency and another version to maintain; budget time to review package changes and verify the authorized test still represents its intended browser. Retrying a blocked request can add load without fixing an access-policy issue.
Playwright is an open-source automation framework; this guide makes no claim about hosting, proxy, or infrastructure prices. Account for the CI minutes, browser installation, and maintenance your own environment uses. Do not calculate a bypass success rate from studies that tested different sites or configurations.
Or skip the browser setup
If the job is to capture a page image or PDF rather than exercise browser interactions, ScreenshotNeo is a website screenshot API and MCP server. A single GET request can return PNG, JPEG, WebP, or PDF. It is not a substitute for an interactive Playwright test, and it does not promise to bypass a site’s access controls.
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}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const image = Buffer.from(await res.arrayBuffer());
require('node:fs').writeFileSync('shot.webp', image);
See the ScreenshotNeo API documentation for request options and response details. Cookie and consent banners are accepted and removed before capture, along with supported newsletter popups and chat widgets; each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers say which page verdict and billing outcome applied. Its MCP server gives AI agents the take_screenshot, get_page_info, and capture_pdf tools. 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 to start with 1,000 screenshots a month and no card.
FAQ
Does Playwright have a built-in stealth option?
No universal stealth switch is described in its browser documentation. Playwright lets you choose browsers, channels, and headless behavior; third-party packages are separate projects.
Should I use a stealth package for my own staging site?
Usually begin with a representative browser configuration and a staging test policy. Consider a package only when you have an authorized, specific test requirement and can maintain the added dependency.
Does headed mode make automation undetectable?
No. It changes how the browser runs, but neither headed mode nor any one configuration establishes that automation cannot be identified.
Can ScreenshotNeo run an interactive browser test?
No. It captures page images or PDFs through an API and offers MCP tools for agents to capture and inspect pages. Use Playwright when your test needs browser interaction and assertions.


