Playwright gets caught by anti-bots and captchas. This one runs on an anti-detect Firefox with an undetected fingerprint, compatible with your existing Playwright code. How it works Anti-bots ask two questions, and reCAPTCHA, hCaptcha and Cloudflare Turnstile score the answers. invisibleplaywright answers yes to both. 1. Is this a real browser? Yes. It is Firefox, patched at the C++ source level. The browser…
Playwright gets caught by anti-bots and captchas.
This one runs on an anti-detect Firefox with an undetected fingerprint, compatible with your existing Playwright code.
How it works
Anti-bots ask two questions, and reCAPTCHA, hCaptcha and Cloudflare Turnstile score the answers. invisible_playwright answers yes to both.
1. Is this a real browser? Yes. It is Firefox, patched at the C++ source level.
The browser fingerprint is set inside the engine, not injected into the page: navigator, screen, GPU/WebGL, canvas, fonts, audio, WebRTC, timezone, network. Headless or headed, the same values either way.
No JS shim, no override, no seam to read.
2. Is a real person using it? Yes. The actions are humanized in the driver.
Every click, hover and drag follows a natural mouse path with human timing, no teleporting cursor.
Each input is byte-identical to a real mouse: real input source, pressure, trusted events.
curl -LsSf https://astral.sh/uv/install.sh | sh
source $HOME/.local/bin/env
uv init --python ">=3.11" my-project
cd my-project
uv add invisible-playwright
uv run invisible-playwright fetch # one-time download, sha256-verified: 262 MB, about 550 MB unpacked
Supported platforms: Windows x86_64, Linux x86_64. Linux arm64 is no longer supported from firefox-56 (firefox-55 is the last engine built for it).
Then save one of the examples below as main.py in my-project and run it with uv run main.py.
Usage
Random fingerprint per session
Playwright’s API, sync and async, with no code changes. The same objects, the same calls, the same return values. A few surfaces are out of scope - tracing, HAR, CDP, the API request context - and each one refuses with a sentence saying why rather than misbehaving quietly. If you already use Playwright, switching is two lines:
- from playwright.sync_api import sync_playwright
- with sync_playwright() as p:
- browser = p.firefox.launch()
+ from invisible_playwright import InvisiblePlaywright
+ with InvisiblePlaywright() as browser:
Every session gets a distinct fingerprint (GPU, audio, fonts, screen, ~200 fields) and Bezier-curve mouse motion.
Sync
from invisible_playwright import InvisiblePlaywright
with InvisiblePlaywright(proxy={"server": "socks5://...", "username": "u", "password": "p"}) as browser:
page = browser.new_page()
page.goto("https://example.com")
page.click("#submit") # mouse arcs to the button on a Bezier curve
Async
from invisible_playwright.async_api import InvisiblePlaywright
async with InvisiblePlaywright(proxy={"server": "socks5://...", "username": "u", "password": "p"}) as browser:
page = await browser.new_page()
await page.goto("https://example.com")
await page.click("#submit")
The browser object is a playwright.sync_api.Browser / playwright.async_api.Browser - every Playwright method works as-is.
Log the seed to replay a run:
sf = InvisiblePlaywright()
with sf as browser:
print("seed =", sf.seed)
# ...
Reproducible fingerprint
with InvisiblePlaywright(seed=42) as browser:
... # same GPU, same canvas hash, same audio context, every run
Proxies
proxy = {
"server": "socks5://gate.example.com:1080",
"username": "user",
"password": "pass",
}
with InvisiblePlaywright(proxy=proxy) as browser:
...
Schemes supported: socks5, socks4, http, https. DNS is routed through the proxy by default, no local leak.
Timezone
The browser timezone follows timezone=:
# default: timezone is auto-derived from the egress IP (proxy egress if a
# proxy is set, otherwise the host's own public IP)
with InvisiblePlaywright(proxy=proxy) as browser:
...
# explicit IANA zone always wins, the only way to force a specific zone
with InvisiblePlaywright(proxy=proxy, timezone="America/New_York") as browser:
...
Pinning specific fingerprint fields
By default everything comes from seed. To force specific values while the rest stays seed-derived:
with InvisiblePlaywright(
seed=42,
pin={
"gpu.renderer": "ANGLE (AMD, Radeon R9 200 Series Direct3D11 vs_5_0 ps_5_0, D3D11)",
"gpu.vendor": "Google Inc. (AMD)",
"screen.width": 2560,
"screen.height": 1440,
"hardware.concurrency": 16,
},
) as browser:
...
A GPU pin picks one of the validated personas rather than setting a free string,
because the ~81 getParameter values and the extension list travel with the
renderer name and a detector cross-checks them against it. A name the pool does
not carry raises, and the error lists the ones it does.
Full list of pinnable keys, how pinning interacts with the Bayesian sampler, and common patterns are in docs/pinning.md.
CLI
The installed command is invisible-playwright, with a hyphen. python -m invisible_playwright works identically and needs nothing on PATH.
invisible-playwright fetch # download the engine if missing, check every cached
# one against the seal, print the path
invisible-playwright version # wrapper, core and engine versions, and where the
# engine is cached
Documentation, guides and comparisons
If you would rather prompt than script. This page is the engine, as a Python
library. Two ways to use it without writing code, both one line: from an MCP client
you already have (Claude Code, Claude Desktop, Cursor),
claude mcp add stealth -- uvx aihawk, see
the MCP server page; or with an
interface and a model included, needing only an OpenRouter key,
uvx aihawk ui --openrouter-key sk-or-..., see
AIHawk. Same engine underneath, and the second
is a client of the first.
All of it reads better, and is searchable, in
the wiki,
organised into four sections instead of one flat list:
Documentation -
installation, the two-line switch from plain Playwright, proxy/timezone
configuration, pinning specific fields, the CLI.
Guides - how
detection actually works, in seven groups: browser identity, canvas/WebGL/fonts/
audio, network and WebRTC, the automation layer, AI agents, the detectors themselves
explained from source, and testing.
Comparisons -
against Camoufox, Patchright, nodriver and playwright-stealth, and the case for
Firefox over Chromium generally.
Integrations -
Scrapy, Crawlee, Robot Framework, CodeceptJS, test runners, Playwright MCP, and the
frameworks it does not fit, by name.
The other pieces of this one. The engine is the middle of three, and the
two around it are how most people reach it:
invisible_core - seed to
fingerprint to preferences, plus proxy and geolocation. This package pins it.
AIHawk - this engine as an MCP
server (uvx aihawk, for any client that brings its own
model) and, in the same package, an interface with a model included, from
one command. The interface is a client of the server, with no private path
to the browser.
The open-source neighbours. Which one fits depends on the layer your problem is
at, and on whether you need Firefox or Chromium:
three ways to make Playwright undetected works
through what each layer can reach, and AI browser agents and stealth
covers the frameworks that pick Chromium over CDP for you.
On the Firefox side
Camoufox - patches C++ too, wider surface, ships a fingerprint database instead of deriving one from a seed. Comparison.
LibreWolf - a privacy-defaults Firefox to browse with, not to automate.
arkenfox/user.js - hardening through preferences. Where a preference is enough, use it.
On the Chromium side
Patchright - a patched Playwright fork: the work lands in the driver, not the binary. Comparison.
nodriver - successor to undetected-chromedriver, driving Chrome over CDP. Comparison.
Two rules hold across everything below. Every value is decided before a page can
ask, inside the browser, so there is no JavaScript shim to find. And every value
comes from one profile, so no two surfaces contradict each other, which is how a
spoofed browser is usually caught. Source:
feder-cr/firefox_antidetect_patch.
The debugger surface does not answer the timing questions that give a driven browser away: debugger timing detection.
Closed-mode shadow roots are reachable, which stock Playwright cannot do in either engine, while the page still reads null: closed shadow roots.
License
MIT - see LICENSE. The patched Firefox binary is distributed under the MPL-2.0 (Firefox upstream license). The C++ patches against mozilla-central that produce that binary are at feder-cr/firefox_antidetect_patch.
Disclaimer
This project is for educational purposes only. It is provided as-is, with no warranties. I take no responsibility for how it is used. Use it at your own risk and in compliance with the laws of your jurisdiction.
评论