Playwright & Puppeteer
Clearcote is just Chromium, so it works as a drop-in browser for the automation tools you already use.
The drop-in way (works today)
Point your launcher at the Clearcote binary with executablePath (Node) or executable_path (Python), and pass identity options as args:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(
executable_path=r"C:\clearcote\chrome.exe",
headless=False,
args=[
"--fingerprint=seed-123",
"--fingerprint-platform=windows",
"--timezone=America/New_York",
],
)
page = browser.new_page()
page.goto("https://abrahamjuliot.github.io/creepjs/")
browser.close()The SDK way (published on npm, PyPI & NuGet)
The clearcote package wraps the above so identity options become named arguments and launch() returns a standard Playwright Browser. It auto-downloads and SHA-256-verifies the Clearcote binary on first use — no manual download.
# pip install clearcote
from clearcote import launch
browser = launch(fingerprint="seed-123", platform="windows", brand="Chrome")
page = browser.new_page()
page.goto("https://example.com")
browser.close()Current SDK: 0.25.0. An async API (clearcote.async_api) mirrors the sync one for use inside an asyncio loop — same options, returns Playwright async objects. It also exposes persistent contexts, saved profiles, prefetch helpers, opt-in auto-update, geo-IP matching, humanized input, render-coherence checks, and the in-browser agent helpers documented below. The .NET SDK (dotnet add package Clearcote) mirrors the core — LaunchAsync, LaunchPersistentContextAsync, ServeAsync, verified download and PRO licensing — and returns a standard Microsoft.Playwright IBrowser; the higher-level helpers (humanize, geoip, saved profiles, render-coherence, agent) are Python & Node only for now. For full copy-paste workflows, see Examples.
Resolve or prefetch the verified binary
The SDK resolves the browser in this order: explicit executable path, CLEARCOTE_BINARY, then the verified release cache. Call download / executable_path when you want to warm the cache without launching, use a custom cache directory, or opt in to resolving the latest GitHub release at runtime.
from clearcote import download, launch
chrome = download(cache_dir=r"C:\clearcote-cache", auto_update=True)
browser = launch(executable_path=chrome, fingerprint="seed-123")Pinned mode verifies the SDK's baked-in SHA-256 values. autoUpdate / auto_update is opt-in and verifies the release checksum manifest; when gpg is available, it also checks the signed manifest against the pinned Clearcote signing-key fingerprint.
Import a real-Chrome profile
Instead of the synthetic, seed-derived persona, you can make Clearcote present a real machine's identity. Capture one from a donor Chrome with the collector at tools/fingerprint-collect (open collect.html, click Capture, and it downloads a JSON profile), or bootstrap from the open-source chrome-fingerprints 10k-record dataset with the bundled convert_dataset.py converter. The capture covers navigator, screen geometry, WebGL vendor/renderer + getParameter limits, Web Audio, speech-synthesis voices, fonts, codecs, and CSS @media characteristics.
Pass the profile to the SDK as a file path, object, or JSON string — it handles the gzip + base64 packing for you. Fields present in the profile override the seed-derived persona; fields that are absent fall back to the --fingerprint seed, so partial profiles stay coherent. The SDK also derives Accept-Language from the profile's navigator.languages when you don't set one explicitly.
from clearcote import launch
browser = launch(fingerprint="seed-123", fingerprint_profile="profile.json")
page = browser.new_page()
page.goto("https://example.com")
browser.close()Coherent by default (no extra flags)
Whatever you launch with, the engine keeps the secondary surfaces in agreement with the persona and with a real Chrome-on-Windows desktop — so there's no mismatch for a detector to catch. WebGL getParameter limits (WebGL1 + WebGL2) report the canonical ANGLE/D3D11 values a real Windows GPU returns, and UNMASKED_RENDERER / UNMASKED_VENDOR are session-constant (the same GPU on every site, tracking the persona). navigator.getBattery() reports a coherent desktop (charging, level 1.0), navigator.connection a residential 4g profile, navigator.keyboard.getLayoutMap() a clean US-QWERTY map, and AudioContext the matching Windows-WASAPI sample-rate / latency. getScreenDetails() reports a single monitor and @media (pointer: fine) / (hover: hover) match a desktop with a mouse.
Validated on real Windows: BrowserScan bot-detection reads Normal (no bot; webdriver/UA/CDP/navigator clean) and CreepJS shows 0% headless / 0% stealth.
Auto-match a proxy's region (geoip)
Pass geoip with a proxy and the SDK resolves the proxy's exit IP — looked up in the offline geoip-all-in-one database — and sets a coherent timezone + primary navigator language + Accept-Language + WebRTC IP for that region. No more hand-matching a timezone to every proxy:
from clearcote import launch
browser = launch(
fingerprint="user-7423",
proxy={"server": "http://host:8080", "username": "u", "password": "p"},
geoip=True, # timezone + language auto-matched to the proxy's region
)Prefer to set it yourself? Use acceptLanguage (Node) / accept_language (Python), e.g. "en-US,en" — it sets the Accept-Language header, the full navigator.languages array, and navigator.language — and Intl / toLocaleString follow it too.
Humanized input (humanize & showCursor)
Pass humanize and all input — moving, clicking, dragging, scrolling, and typing — runs through one humanized standard, at both the page level (page.click / hover / type / fill / mouse.* / keyboard.type) and the locator level (locator.click / type / fill / pressSequentially / dragTo / …). Moves follow a slightly bowed cubic-bézier path built from the last cursor position, walked as a min-jerk sum-of-sub-movements (a ballistic primary + corrective move — the multi-peak velocity of real reaching, not one symmetric bell), and everything is dispatched as real, trusted events (isTrusted === true, no CDP-injection tell, and navigator.webdriver stays false). Add showCursor to draw a dot that follows the motion so you can watch it.
Because moves use native input, a button pressed with mouse.down() stays held across the move — so down → move → up is a real held-button drag (slider captchas work), and locator.dragTo is humanized too. Typing goes key-by-key with gaussian inter-key timing + word-boundary pauses and the occasional fat-finger correction; scrolling uses ease-out inertia with the occasional reading pause. fill focuses the field and types it out (values over ~200 chars stay atomic so bulk fills don't crawl).
from clearcote import launch
browser = launch(fingerprint="seed-123", humanize=True, show_cursor=True)
page = browser.new_page()
page.goto("https://example.com")
page.click("text=Sign in") # eased curve, then a trusted click
page.fill("#email", "you@example.com") # focus + key-by-key human typing
page.locator("#password").type("s3cr3t") # locators are humanized too
# held-button drag (e.g. a slider captcha): the press stays held across the move
page.mouse.move(x0, y0); page.mouse.down()
page.mouse.move(x1, y0); page.mouse.up()
browser.close()Render-backend coherence check (checkRenderCoherence)
A persona can claim a GPU, but if the page is actually painted by a software rasterizer (SwiftShader / llvmpipe — common headless with no GPU) a strict detector can tell. Probe a live page: it reads the (unmasked) WebGL vendor/renderer the page actually sees, flags a software-rasterizer fallback (a fatal headless tell — enable the canvas bridge or run headed on a real GPU) or an incoherent vendor/renderer pair, and returns a structured verdict. Pass the claimed GPU to also assert the rendered family. Available sync, async, and in Node.
from clearcote import launch, check_render_coherence
browser = launch(fingerprint="seed-123")
page = browser.new_page(); page.goto("about:blank")
verdict = check_render_coherence(page) # {'renderer', 'software_suspected', 'coherent', 'warnings'}
if not verdict["coherent"]:
print(verdict["warnings"]) # e.g. software rasterizer / incoherent GPU family
browser.close()Profiles & persistence
Keep a stable identity across runs by reusing the same --fingerprint seed, and persist cookies/storage with a user-data dir:
browser = p.chromium.launch_persistent_context(
user_data_dir=r"C:\clearcote\profiles\acme",
executable_path=r"C:\clearcote\chrome.exe",
headless=False,
args=["--fingerprint=acme-tenant-7"],
)The SDK exposes this directly with launch_persistent_context / launchPersistentContext. It also has saved personas: a Profile stores fingerprint options, proxy settings, canvas-bridge settings, and other launch options as JSON under ~/.clearcote/profiles (override with CLEARCOTE_PROFILE_DIR).
from clearcote import Profile, launch, launch_persistent_context
Profile("acct-1", {
"fingerprint": "acct-1",
"gpu_vendor": "Google Inc. (Intel)",
"gpu_renderer": "ANGLE (Intel, Intel(R) UHD Graphics ... D3D11)",
"canvas_bridge": {"url": "ws://127.0.0.1:8443", "auth": "user:secret"},
}).save()
ctx = launch_persistent_context(r"C:\clearcote\profiles\acct-1", profile="acct-1")
browser = launch(profile="acct-1", headless=False)Saved profiles are plaintext and may contain credentials such as canvasBridge.auth. Treat profile files as trusted input and don't commit or share them.More launch options
extensions— a list of unpacked-extension directory paths (emits--load-extension+--disable-extensions-except).disablePrivacySandbox/disable_privacy_sandbox— disable Privacy Sandbox + intrusive APIs (Topics / FLEDGE / WebUSB / SharedStorage / PrivateAggregation / FencedFrames). Default on — a de-Googled build shouldn't still answerdocument.browsingTopics()or exposenavigator.usb; passfalseto keep them.agentTyping/agent_typing— the agent's keystroke cadence (humandefault /fast/instant). See Agent.tlsProfile— keep the TLS ClientHello coherent with the persona's claimed Chrome version so the network layer follows the UA (not the build's native TLS). Default"match-persona"followsbrandVersion;"native"leaves it untouched;"chrome-<major>"pins a major. See Fingerprint flags.platform: "android"— a best-effort mobile persona (touch, coarse pointer, mobile screen/DPR, Mali/Adreno WebGL, phone viewport). On a desktop engine the GPU render stays desktop — pair with the canvas bridge for render coherence.storageQuota,fingerprintProfile,canvasBridge,webrtcIp,acceptLanguage,disableGpuFingerprint,fingerprintNoise— see Fingerprint flags.
Coherent defaults (overridable)
The SDK applies a few stealth-correct defaults so the obvious tells don't slip through:
- Headed launches default to no emulated viewport (
viewport: null/no_viewport=True) sowindow.innerWidthtracks the real OS window — an emulated 1280×720 sitting on a real window is an impossible-window tell. Pass an explicitviewportto override. - WebRTC defaults to
disable_non_proxied_udpunless you set awebrtcIp/ handling policy, so your machine's own address stays private. - A SOCKS5 proxy with credentials (
socks5://user:pass@host:port) is routed via--proxy-server(Playwright rejects credentials in its SOCKS descriptor). Chromium can't authenticate SOCKS5, so the credentials are dropped with a warning — put the auth on a local relay. humanizeruns an actionability pre-flight before each trusted click (visible / enabled / stable + anelementFromPointcovered-by check) and falls back to the native click, so a trusted click never fires under an overlay or mid-animation.
Tip: derive the seed from your own account/tenant id so each identity is reproducible — same seed, same browser fingerprint, every time. See the full switch list under Fingerprint flags.