Skip to content

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 downmoveup 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:

python
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 answer document.browsingTopics() or expose navigator.usb; pass false to keep them.
  • agentTyping / agent_typing — the agent's keystroke cadence (human default / 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" follows brandVersion; "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) so window.innerWidth tracks the real OS window — an emulated 1280×720 sitting on a real window is an impossible-window tell. Pass an explicit viewport to override.
  • WebRTC defaults to disable_non_proxied_udp unless you set a webrtcIp / 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.
  • humanize runs an actionability pre-flight before each trusted click (visible / enabled / stable + an elementFromPoint covered-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.