ManagedCode.Playwright.Stealth applies a curated set of init scripts to Microsoft.Playwright contexts to reduce common bot-detection signals. It adapts the original playwright_stealth scripts for .NET with ManagedCode conventions.
dotnet add package ManagedCode.Playwright.StealthusingMicrosoft.Playwright;usingManagedCode.Playwright.Stealth;usingvarplaywright=awaitPlaywright.CreateAsync();// One-call launch with stealth args + context pre-configured:var(browser,context)=awaitplaywright.Chromium.LaunchStealthAsync();varpage=awaitcontext.NewPageAsync();awaitpage.GotoAsync("https://www.browserscan.net/bot-detection");If you need more control over launch options:
usingvarplaywright=awaitPlaywright.CreateAsync();awaitusingvarbrowser=awaitplaywright.Chromium.LaunchAsync(newBrowserTypeLaunchOptions{Headless=true,Args=PlaywrightStealthExtensions.StealthArgs// recommended Chrome flags});varcontext=awaitbrowser.NewContextAsync();// Apply stealth before creating pages.awaitcontext.ApplyStealthAsync();varpage=awaitcontext.NewPageAsync();awaitpage.GotoAsync("https://www.browserscan.net/bot-detection");varconfig=newStealthConfig{NavigatorHardwareConcurrency=8,NavigatorDeviceMemory=16,NavigatorMaxTouchPoints=1,NavigatorUserAgentValue="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",Vendor="Intel Inc.",Renderer="Intel Iris OpenGL Engine"};awaitcontext.ApplyStealthAsync(config);Apply stealth on a page (if you already have a page instance):
varpage=awaitcontext.NewPageAsync();awaitpage.ApplyStealthAsync();awaitpage.GotoAsync("https://www.browserscan.net/bot-detection");Disable individual patches:
varconfig=newStealthConfig{WebDriver=false,WebglVendor=false,CanvasFingerprint=false,AudioContext=false,PerformanceJitter=false};awaitcontext.ApplyStealthAsync(config);The configuration can patch 31 detection vectors across these categories. Defaults preserve native navigator values where current Chromium already exposes a normal value.
| Patch | Description | Config |
|---|---|---|
navigator.webdriver | Returns false instead of true | WebDriver |
navigator.plugins / mimeTypes | Fake plugin array (Chrome PDF Plugin, etc.) | NavigatorPlugins |
navigator.languages | Preserves native values by default; configurable language array when supplied | NavigatorLanguages |
navigator.userAgent | Strips headless markers from UA string | NavigatorUserAgent |
navigator.vendor | Preserves native value when already "Google Inc."; configurable otherwise | NavigatorVendor |
navigator.platform | Configurable platform string | NavigatorPlatform |
navigator.hardwareConcurrency | Preserves native value by default; configurable CPU core count when supplied | NavigatorHardwareConcurrency |
navigator.deviceMemory | Preserves native value by default; configurable device memory in GB when supplied | NavigatorDeviceMemory |
navigator.connection | Preserves native value when present; mocks NetworkInformation when missing | NavigatorConnection |
navigator.permissions | Normalizes Notification permission state | NavigatorPermissions |
navigator.maxTouchPoints | Preserves native value by default; configurable touch point count when supplied | NavigatorMaxTouchPoints |
navigator.pdfViewerEnabled | Preserves native value when present; mocks it when missing | NavigatorPdfViewer |
| Patch | Description | Config |
|---|---|---|
window.chrome / chrome.runtime | Full Chrome extension API mock | ChromeRuntime |
chrome.app | Chrome App API mock | ChromeApp |
chrome.csi | Chrome CSI timing mock | ChromeCsi |
chrome.loadTimes | Preserves the native headful API when present; mocks it when missing | ChromeLoadTimes |
| Patch | Description | Config |
|---|---|---|
| WebGL vendor/renderer | Spoofs UNMASKED and standard WebGL params; hides ANGLE/SwiftShader | WebglVendor |
| Canvas fingerprint | Adds session-stable noise to canvas rendering | CanvasFingerprint |
| Broken image dimensions | Fixes 16x16 headless artifact to 0x0 | BrokenImage |
| Patch | Description | Config |
|---|---|---|
| AudioContext fingerprint | Adds noise to audio frequency/channel data | AudioContext |
| Media codecs | Correct codec support responses | MediaCodecs |
| Speech synthesis | Mock voice list for getVoices() | SpeechSynthesis |
| Patch | Description | Config |
|---|---|---|
window.outerWidth/Height | Realistic outer dimensions | OuterDimensions |
screen.* dimensions | Consistent screen width/height/colorDepth | ScreenDimensions |
| Patch | Description | Config |
|---|---|---|
| CDP detection | Masks Chrome DevTools Protocol traces | CdpDetection |
| Automation properties | Removes cdc_*, $cdc_*, domAutomationController | AutomationProperties |
| Performance jitter | Adds realistic timing noise to performance.now() and requestAnimationFrame | PerformanceJitter |
| Patch | Description | Config |
|---|---|---|
iframe contentWindow | Fixes iframe proxy behavior | IframeContentWindow |
| Hairline detection | Fixes Modernizr offsetHeight check | Hairline |
// Apply to browser context (recommended)awaitcontext.ApplyStealthAsync();awaitcontext.ApplyStealthAsync(customConfig);// Apply to individual pageawaitpage.ApplyStealthAsync();awaitpage.ApplyStealthAsync(customConfig);// One-call launch with stealth pre-configuredvar(browser,context)=awaitplaywright.Chromium.LaunchStealthAsync();var(browser,context)=awaitplaywright.Chromium.LaunchStealthAsync(config,launchOptions,contextOptions);// Access recommended Chrome args for manual launch setupstring[]args=PlaywrightStealthExtensions.StealthArgs;WebDriver, WebglVendor, ChromeApp, ChromeCsi, ChromeLoadTimes, ChromeRuntime,
IframeContentWindow, MediaCodecs, Hairline, OuterDimensions,
NavigatorLanguages, NavigatorPermissions, NavigatorPlatform, NavigatorPlugins,
NavigatorUserAgent, NavigatorVendor, NavigatorConnection, NavigatorPdfViewer,
BrokenImage, SpeechSynthesis, ScreenDimensions,
CdpDetection, AutomationProperties, CanvasFingerprint, PerformanceJitter, AudioContext
NavigatorHardwareConcurrency(default: 0 to preserve native value, set >0 to spoof)NavigatorDeviceMemory(default: 0 to preserve native value, set >0 to spoof)NavigatorMaxTouchPoints(default: -1 to preserve native value, set >=0 to spoof)
NavigatorUserAgentValue- Custom user agent stringNavigatorPlatformValue- Custom platform (e.g., "Win32")NavigatorVendorValue- Vendor name (default: "Google Inc.")Vendor- WebGL vendor (default: "Intel Inc.")Renderer- WebGL renderer (default: "Intel Iris OpenGL Engine")Languages- Language array (default: empty to preserve native values)RunOnInsecureOrigins- Allow stealth on http:// origins
Integration tests target 9 bot-detection sites:
- https://www.browserscan.net/bot-detection
- https://bot.sannysoft.com/
- https://www.intoli.com/blog/not-possible-to-block-chrome-headless/chrome-headless-test.html
- https://fingerprint.com/demo
- https://arh.antoinevastel.com/bots/areyouheadless/
- https://pixelscan.net/bot-check
- https://bot.incolumitas.com/
- https://abrahamjuliot.github.io/creepjs/
- https://deviceandbrowserinfo.com/info_device
These sites can change at any time. If a site changes, update the corresponding test assertions.
Run tests (Playwright browsers install automatically on first run):
dotnet test --solution ManagedCode.Playwright.Stealth.slnx -c ReleaseOn Linux CI runners, set PLAYWRIGHT_INSTALL_DEPS=1 to install system dependencies
(playwright install --with-deps) when tests start.
Google search verification is optional (Google may block automated traffic). Enable it with:
RUN_GOOGLE_SEARCH_TESTS=1 dotnet test --solution ManagedCode.Playwright.Stealth.slnx -c ReleaseThis project uses code from the original playwright_stealth repository and adapts it for .NET:
https://github.com/AtuboDad/playwright_stealth
MIT. See LICENSE.