For authorized browser automation, start with ordinary Puppeteer or Playwright settings, wait for the page’s actual content, and keep each session’s configuration consistent. “Stealth” cannot guarantee a site will accept automation: a site may assess browser, session, and network signals, and attempts to disguise automation can break pages or trigger more failures. If a site blocks you, stop and seek permission or an official data source instead of escalating evasion.
What “stealth mode” can—and cannot—do
In this context, “stealth” is best treated as careful browser configuration, not invisibility. Browserless’s January 23, 2026 vendor article describes detection signals that can include IP or ASN reputation, HTTP and TLS hints, browser fingerprint consistency, behavior timing, and challenges. That is a vendor explanation, not a complete model for every site. Browserless Developer Advocate Alejandro Loyola summarized its practical advice as: “Stealth scraping means your browser signals line up.”
That advice is not a guarantee of access. A site can change its behavior, and a CAPTCHA, explicit denial, or repeated block is a reason to stop and reassess—not a cue to keep changing fingerprints. Puppeteer’s project guidance also places responsibility for safe and intended automation on the code that uses it; that is project guidance, not a legal conclusion.
Check that browser automation is an appropriate access path
- Look for an official route first. Check whether the site offers an API, export, feed, or permissioned integration, and review the terms that apply to your use.
- Read crawler instructions in context. RFC 9309 standardizes the Robots Exclusion Protocol. A robots.txt file communicates crawler guidance; it does not grant access or replace a review of applicable terms and law.
- Define the allowed scope. Limit collection to the pages and data you are authorized to access. Avoid collecting personal or sensitive information unless you have an appropriate basis and safeguards.
- Stop on an explicit block. Do not try to defeat a CAPTCHA, access denial, or other protective challenge. Ask the site for permission, use an approved data source, or abandon the request.
Choose Puppeteer or Playwright for your constraints
There is no evidence here for a universal “stealth winner.” Choose based on your existing codebase and the browser and operational features your authorized task needs.
#1 Best Overall
| Decision point | What to consider |
|---|---|
| Existing stack | Prefer the framework already used and maintained by your team unless a concrete requirement calls for a change. |
| Browser connection | If your integration requires Chrome DevTools Protocol (CDP), Playwright’s BrowserType API describes CDP connections as Chromium-only and lower fidelity than its Playwright-protocol connection. Confirm the protocol and browser requirements of your deployment before choosing. |
| Session boundaries | Playwright browser contexts isolate cookies and local and session storage, which can help make runs reproducible and keep unrelated sessions separate. |
| Debugging and operations | Compare how each fits your existing logging, failure handling, deployment environment, and maintenance workload. Do not infer that a framework feature by itself makes a browser harder to detect. |
Set up a minimal authorized run
These examples use JavaScript with Node.js. They navigate to a page you are allowed to access, wait for a known content selector, extract its text, and report navigation or selector failures. Replace the example URL and selector with values for your permitted task. They do not spoof browser identity or bypass a site’s controls.
Playwright
- Install Playwright with
npm install playwright. Install the browser for your environment withnpx playwright install chromium. - Save this as
scrape-playwright.jsand run it withnode scrape-playwright.js.
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
try {
// A new context keeps this run's cookies and storage separate.
const context = await browser.newContext({
locale: 'en-US',
timezoneId: 'UTC',
viewport: { width: 1280, height: 800 }
});
const page = await context.newPage();
const response = await page.goto('https://example.com', {
waitUntil: 'domcontentloaded',
timeout: 30000
});
if (!response) {
throw new Error('Navigation returned no main-document response.');
}
if (!response.ok()) {
throw new Error(`Main document returned HTTP ${response.status()}.`);
}
// Replace this selector with a stable, task-relevant element.
const content = page.locator('main');
await content.waitFor({ state: 'visible', timeout: 15000 });
console.log((await content.innerText()).trim());
await context.close();
} finally {
await browser.close();
}
})().catch((error) => {
console.error('Authorized page run failed:', error.message);
process.exitCode = 1;
});
A fresh Playwright context is useful when runs should not share cookies or storage. If your authorized workflow requires session continuity, retain only the state needed for that session, protect it, and keep it separate from unrelated work. Do not reuse a signed-in session for a purpose the account owner or site has not authorized.
Puppeteer
- Install Puppeteer with
npm install puppeteer. - Save this as
scrape-puppeteer.jsand run it withnode scrape-puppeteer.js.
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 800 });
const response = await page.goto('https://example.com', {
waitUntil: 'domcontentloaded',
timeout: 30000
});
if (!response) {
throw new Error('Navigation returned no main-document response.');
}
if (!response.ok()) {
throw new Error(`Main document returned HTTP ${response.status()}.`);
}
// Replace this selector with a stable, task-relevant element.
await page.waitForSelector('main', {
visible: true,
timeout: 15000
});
const content = await page.$eval('main', element =>
element.innerText.trim()
);
console.log(content);
} finally {
await browser.close();
}
})().catch((error) => {
console.error('Authorized page run failed:', error.message);
process.exitCode = 1;
});
Adapt the wait to the page, not to a guessed delay
The examples wait for a selector because content may be rendered after the initial document response. Replace main with an element that actually indicates the data you need is ready. If a page exposes a relevant event or state, wait for that instead. Browserless’s BrowserQL documentation notes that an empty extraction can occur when JavaScript is required and suggests waiting for a selector or event. A fixed sleep may waste time on fast runs and still be too short on slow ones.
Keep browser and session settings coherent
Set only the options your task requires. Locale, timezone, viewport, user-agent-related settings, permissions, cookies, and storage should make sense together and persist only as long as intended. Avoid contradictory identity values between browser settings and requests, and do not randomly change properties to imitate other users. These are practical recommendations in Browserless’s vendor-authored guidance, not a way to guarantee acceptance.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchPC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Rank #3
- Locale and timezone: choose values appropriate to the authorized test or workflow rather than changing them on every request.
- Viewport: use a stable size that matches the page behavior you need to inspect.
- Permissions: grant only what the task needs.
- Cookies and storage: use an isolated context for independent runs; retain state only when authorized continuity is necessary.
- User-agent-related settings: do not make them conflict with the rest of the session or use them as a claim that automation is human.
Why is my Playwright scraper still getting detected?
Because Playwright is a browser automation framework, not an access guarantee. A failure might be an ordinary navigation error, a rendering or selector issue, an expired session, a rate limit, or an explicit access denial. Diagnose which kind of failure occurred before changing configuration.
- Navigation failure: record the thrown error, target URL, and elapsed time. Check whether the destination is reachable and whether the failure is a timeout or a redirect issue.
- Unexpected HTTP response: log the main-document response status. Treat a denial or challenge as an access decision, not as a prompt to disguise the client.
- Selector timeout or empty text: verify that the selector exists in the rendered page and wait for the content-bearing selector or an appropriate page event. Do not assume that the initial HTML response contains JavaScript-rendered data.
- Session-related failure: check whether required state has expired or whether a fresh context is appropriate. Preserve session state only for an authorized workflow.
- Rate limit or repeated denial: slow or stop the task and seek permission or an approved source. Do not keep retrying in a way that ignores the site’s response.
- Unexpected layout or broken page: remove unnecessary configuration changes and return to a minimal run. Browserless warns that excessive signal changes can increase risk or break layouts.
When managed browser infrastructure may help
If you need hosted browser infrastructure, Browserless documents managed stealth routes and BrowserQL integrations for Puppeteer and Playwright. Its documentation also warns that stealth routes can have unexpected effects on automation. These are vendor-described product capabilities, not independent performance testing or a guarantee that a target site will allow access. Verify current route availability, product limits, data handling, and pricing directly with the provider before depending on them.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Or skip the browser setup
If the deliverable is an authorized screenshot or PDF rather than extracted page data, ScreenshotNeo is a screenshot API and MCP server—not a substitute for a scraper that returns structured page content. Its one-call API can capture a page without setting up a local browser:
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 request options. Before capture, it accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each of those steps can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and responses identify the page verdict and billing status in headers. Its MCP server offers take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, or another MCP client. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots. Every feature is available on every plan.
Free tools Windows power users keep installed
One-click scans. No signup required.
Sign up for ScreenshotNeo’s free plan to try 1,000 screenshots a month with no card.
Quick Recap
Best Value
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.

