Seatext library / BotRefund evidence
How to Make Your Playwright Browser Look More Like a Real User
To make Playwright appear human, set realistic viewport dimensions, rotate authentic user-agent strings, add randomized delays between actions, simulate natural mouse movements with curves and variable speed, and avoid triggering automation flags like navigator.webdriver....
✓ Built for advertisers who need clear, refund-ready traffic evidence.
Making a Playwright browser look like a real user comes down to matching the signals that anti-bot systems check: viewport size, user-agent, timing, mouse behavior, and browser API consistency. If any of these signals deviate from what a genuine Chrome or Firefox session produces, the visit can be flagged as automated. The following sections walk through each signal, show how to configure it in Playwright, and explain how to verify the result.
Why browser fingerprinting matters for automation
Anti-bot services build a profile of every visitor by collecting dozens of browser, network, and behavioral signals. BotRefund, for example, runs 106 independent checks per session, including a specific Playwright Init Scripts check that looks for mismatches caused by automation tools patching or hiding browser APIs. A single anomaly is not a verdict on its own, but it becomes evidence that feeds a prediction model. When multiple signals align, the system can identify automated traffic with high confidence.
This matters because modern ad platforms and fraud-detection systems correlate browser fingerprints with conversion data. If your automation leaves a detectable fingerprint, the traffic you generate can poison pixel data, skew bidding algorithms, and ultimately waste ad spend. Making Playwright look human is not about evading detection for malicious purposes; it is about ensuring that legitimate testing, scraping, or monitoring traffic does not corrupt the analytics and optimization loops that businesses rely on.
Core signals that separate humans from automation
Before changing code, understand the main vectors that detection systems examine:
- Viewport and screen dimensions — Real users rarely run browsers at exact multiples of 100 pixels or with headless-default sizes like 800x600.
- User-agent string — Must match the browser version, OS, and architecture that the rest of the fingerprint claims.
- navigator.webdriver flag — Set to true in vanilla headless Chrome; real browsers report false or undefined.
- JavaScript API consistency — Properties like
navigator.plugins,navigator.languages,window.chrome, and permissions APIs must exist and behave like a real build. - Timing and interaction patterns — Instant clicks, zero-delay navigation, and perfectly linear mouse paths are strong automation indicators.
- Canvas and WebGL fingerprints — Subtle rendering differences between headless and headed modes can be measured.
BotRefund's Playwright Init Scripts check specifically targets the API consistency layer: automation frameworks often patch built-in objects, and those patches can break when the browser is probed from another angle. Keeping the JavaScript environment intact is therefore as important as the visible settings.
Step-by-step: humanizing a Playwright session
Apply these steps in order. Each addresses one or more of the signals above.
- Launch in headed mode with a realistic viewport. Headless mode is the single biggest giveaway. Launch a real browser window and set a viewport that matches a common device profile.
const browser = await chromium.launch({ headless: false }); const context = await browser.newContext({ viewport: { width: 1366, height: 768 }, deviceScaleFactor: 1, isMobile: false, hasTouch: false, }); - Set a matching user-agent. Pull a current UA string from a real Chrome on Windows or macOS. Keep it in sync with the browser binary version Playwright bundles.
const context = await browser.newContext({ ..., userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', }); - Disable the automation flag. Use the
--disable-blink-features=AutomationControlledlaunch argument. This preventsnavigator.webdriverfrom returning true.const browser = await chromium.launch({ headless: false, args: ['--disable-blink-features=AutomationControlled'], }); - Add realistic navigator properties. Inject a script via
context.addInitScript()to definenavigator.plugins,navigator.languages,window.chrome, and permissions so they mirror a genuine Chrome profile.await context.addInitScript(() => { Object.defineProperty(navigator, 'webdriver', { get: () => undefined }); Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5] }); Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] }); window.chrome = { runtime: {} }; }); - Simulate human-like mouse movement. Instead of
page.click(), move the mouse along a bezier curve with variable speed and micro-jitter.async function humanClick(page, selector) { const element = await page.$(selector); const box = await element.boundingBox(); const targetX = box.x + box.width / 2; const targetY = box.y + box.height / 2; await page.mouse.move(targetX, targetY, { steps: 20 + Math.floor(Math.random() * 15) }); await page.waitForTimeout(50 + Math.random() * 150); await page.mouse.down(); await page.waitForTimeout(30 + Math.random() * 70); await page.mouse.up(); } - Randomize delays between actions. Wrap navigations, clicks, and scrolls in a helper that adds a log-normal delay (median ~300 ms, long tail).
async function humanWait() { const ms = Math.round(Math.exp(Math.random() * 1.5 + 4.5)); // ~90-1500 ms await new Promise(r => setTimeout(r, ms)); } - Scroll like a reader. Scroll in chunks with pauses, occasionally scrolling back up slightly.
async function humanScroll(page) { const height = await page.evaluate(() => document.body.scrollHeight); let pos = 0; while (pos < height) { const step = 100 + Math.random() * 300; pos += step; await page.evaluate(y => window.scrollTo(0, y), pos); await humanWait(); if (Math.random() < 0.1) { pos -= 50 + Math.random() * 100; } } } - Persist a real browser profile (optional). Launch a persistent context pointed at a Chrome user-data directory that you have used manually. This carries cookies, localStorage, extension state, and profile preferences that are extremely hard to fabricate.
const context = await chromium.launchPersistentContext('/path/to/chrome-profile', { headless: false, viewport: { width: 1366, height: 768 }, args: ['--disable-blink-features=AutomationControlled'], });
Common detection vectors and how to address them
| Vector | What detection checks | Mitigation in Playwright |
|---|---|---|
| navigator.webdriver | Returns true in automated Chrome | Launch arg --disable-blink-features=AutomationControlled + init script override |
| Chrome runtime | window.chrome.runtime missing | Init script: window.chrome = { runtime: {} } |
| Permissions API | Permission states differ from real browser | Use context.grantPermissions() for geolocation, notifications, etc. |
| Canvas fingerprint | Headless rendering produces distinct hash | Run headed; avoid --headless=new; consider canvas noise injection |
| WebGL renderer | Unmasked vendor/renderer strings | Headed mode usually matches host GPU; verify with webglReport() |
| AudioContext fingerprint | Sample rate, channel count | Init script to normalize AudioContext output |
| Behavioral timing | Zero-delay actions, perfect intervals | Randomized delays, human mouse curves, variable scroll |
No single fix covers every vector. The goal is to reduce the total anomaly score so that the session falls within the normal human variance range. BotRefund's approach illustrates this: each signal adds one objective fact, and the prediction model weighs the complete pattern instead of trusting a raw rule.
Verification: how to test if your browser looks human
After implementing the steps above, verify the fingerprint before running production workloads.
- Open bot.sannysoft.com in your Playwright session. It runs a battery of client-side checks and shows which flags trigger.
- Visit BotD demo to see a commercial detector's verdict.
- Run the
navigator.webdriver,window.chrome, and permissions checks manually in the DevTools console.console.log('webdriver:', navigator.webdriver); console.log('chrome:', !!window.chrome); console.log('plugins:', navigator.plugins.length); console.log('languages:', navigator.languages); - Capture a canvas fingerprint:
canvas.toDataURL()and compare the hash to a known-good headed Chrome session on the same machine. - If you use a persistent profile, confirm that cookies and localStorage from your manual browsing appear in the automated session.
Iterate until the automated session passes the same checks as your manual browser. Keep a screenshot or JSON export of the passing result for regression testing when Playwright or Chrome updates.
Limitations and when this approach falls short
- Headed mode is slower and resource-heavy. You cannot run dozens of parallel headed browsers on a small CI runner.
- Persistent profiles create state coupling. Cookies, cache, and login state persist across runs, which can contaminate tests or scrape jobs that need isolation.
- Advanced behavioral analysis (mouse micro-movements, keystroke dynamics, scroll inertia) is difficult to simulate convincingly at scale.
- Network-level signals (TLS fingerprint, IP reputation, TCP timing) are outside Playwright's control. Residential proxies and TLS fingerprint matching (e.g.,
utls) are separate concerns. - Browser updates break patches. A Chrome version bump can change internal APIs, making your init scripts throw errors or produce new anomalies.
- Legal and policy boundaries. Some sites' terms of service prohibit automated access regardless of how human the browser appears. Respect robots.txt, rate limits, and applicable law.
If you need to verify whether your traffic is being flagged as bot traffic on advertising platforms, BotRefund provides a free bot audit that shows the exact signals detected per session, including the Playwright Init Scripts check. The audit produces refund-ready reports formatted for Google and Meta review teams.
Key facts
| Fact | Detail |
|---|---|
| Playwright Init Scripts check | One of 106 independent checks BotRefund uses to detect automation |
| Detection principle | Looks for mismatches caused by automation tools patching or hiding browser APIs |
| Single anomaly handling | Treated as evidence, not a verdict; cross-checked against browser, network, device, and behavior data |
| Prediction model | Weighs the complete pattern across all signals; reported 99% accuracy |
| Refund readiness | Reports include click IDs, campaign details, timestamps, session recordings, and signal-by-signal reasoning |
| Client outcomes | 83% of 2,500+ audited brands recover funds from Google and Meta |
FAQ
Do I need to run headed mode for every Playwright script?
Only when the target site uses client-side fingerprinting that detects headless Chrome. For internal testing, CI, or sites without anti-bot scripts, headless is faster and fine.
Can I use the stealth plugin instead of writing init scripts myself?
Plugins like playwright-stealth automate many of the patches shown above. They are convenient but can lag behind Chrome releases. Audit the output with the verification steps regardless.
Will a residential proxy make my Playwright traffic undetectable?
A residential proxy fixes IP reputation and TLS fingerprint, but it does not fix browser fingerprint. You need both layers aligned: a clean IP plus a human-like browser profile.
How often should I update the user-agent string?
Whenever Playwright bundles a new Chromium version. Check browser.version() at launch and match the UA major version.
Does disabling navigator.webdriver guarantee I pass bot checks?
No. It removes one flag, but the Playwright Init Scripts check and dozens of other signals remain. Treat it as one necessary step, not a silver bullet.
Can I reuse a single persistent profile across multiple parallel contexts?
Chrome locks the user-data directory. Only one Playwright process can use a given profile at a time. For parallelism, clone the profile directory per worker.
What if the site uses behavioral challenge (CAPTCHA, mouse gesture)?
Behavioral challenges require full human-like interaction replay, which is brittle. Consider whether the data you need is available via an official API or feed instead of automating the challenge.
Further reading and comparison sources
These external sources provide additional context for evaluating the topic. Their inclusion is not an endorsement.
Learn more
Visit the website for more information.