Skip to main content

Playwright wrapper for a patched Firefox with deterministic stealth profile.

Project description

tests License: MIT Python 3.11+ Firefox 151.0 GitHub stars browser launches

invisible_playwright

Undetected Playwright automation on a stealth-patched Firefox.
Python, MIT, and it passes every bot detection test.

invisible_playwright - 5/5 detection suites passed

How it works

Anti-bots ask two questions. invisible_playwright answers yes to both.

1. Is this a real browser? Yes. It is Firefox, patched at the C++ source level.

  • Fingerprint set inside the engine, not injected into the page: Navigator, screen, GPU/WebGL, Canvas, fonts, audio, WebRTC, timezone, network.
  • 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.

Driven by the standard Playwright API. Full breakdown: feder-cr/firefox_antidetect_patch.

If you got here from a search

You are probably looking for one of these, and this is the same category:

  • a stealth Firefox for Playwright, where the fingerprint is in the engine rather than in an injected script
  • an undetected browser automation library for Python, as an alternative to undetected-chromedriver or nodriver
  • an anti-detect browser you can drive from code instead of clicking around a GUI
  • something to try when Playwright is detected as a bot on one site and works everywhere else

The nearest open-source neighbours are Camoufox, which also patches Firefox, and Patchright and nodriver, which take the Chromium side. Three ways to make Playwright undetected explains what each approach can and cannot reach, including the costs of this one.


Still seeing captchas or anti-bot? It's the proxy.

Once the browser is handled it stops being the variable. If you are still getting challenged, the tell is no longer the browser, it is the IP you come from. Around 90% of proxies are public: anyone can rent the same address, so it is already known and sits on the blocked-IP lists sites check. A perfect browser on a known IP still loses.

The fix is the clean 10%, residential IPs that aren't already known. For those we recommend sx.org, who filter for and serve only IPs that aren't already on those lists.


Install

pip install invisible-playwright
python -m invisible_playwright fetch      # one-time ~238 MB download (~544 MB unpacked), sha256-verified

Supported platforms: Windows x86_64, Linux x86_64 / arm64, macOS arm64 / x86_64. On macOS the app is ad-hoc signed (not notarized): if Gatekeeper complains, clear the quarantine flag once with xattr -dr com.apple.quarantine on the cached Firefox.app.


Usage

Random fingerprint per session

100% Playwright-compatible - sync and async, all methods, zero API changes. 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, ~400 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.

Around 90% of proxies are public, so their IPs are already known and blocked. For the clean 10%, residential IPs that aren't already known, we recommend sx.org, who filter for and serve only IPs that aren't already on those lists.

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 (NVIDIA, NVIDIA GeForce RTX 4090 Direct3D11)",
        "gpu.vendor":   "Google Inc. (NVIDIA)",
        "screen.width":  2560,
        "screen.height": 1440,
        "hardware.concurrency": 16,
    },
) as browser:
    ...

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
invisible-playwright fetch --force  # re-download even if cached
invisible-playwright path           # absolute path to the cached engine (downloads it if absent)
invisible-playwright version        # wrapper, core and engine versions
invisible-playwright clear-cache    # remove cached engine trees
invisible-playwright doctor         # check every cached engine against the seal

Where the engine lives, and how big it is

The first launch downloads one archive for your platform - 238 MB on Windows, 217-232 MB elsewhere - and unpacks it to about 544 MB on disk. It is verified against a sha256 shipped inside invisible-core, so a truncated or substituted download is refused rather than used.

It is cached, so this happens once per engine version. Set INVISIBLE_PLAYWRIGHT_CACHE_DIR to put it somewhere else - a different drive, a shared location, a path your CI already caches:

export INVISIBLE_PLAYWRIGHT_CACHE_DIR=/mnt/big/engines

Other environment variables you may want:

variable what it does
INVISIBLE_PLAYWRIGHT_CACHE_DIR where engines are cached
INVPW_BINARY_PATH use a specific binary and skip the download entirely
STEALTHFOX_GITHUB_TOKEN authenticate the download, for rate-limited or corporate networks
INVISIBLE_PLAYWRIGHT_SKEW=allow run a Playwright outside the tested range anyway
INVPW_CURSOR_ENGINE python (default), binary, or off

Guides and explainers

All of it reads better, and is searchable, at feder-cr.github.io/invisible_playwright. Same pages, same repo, with a search box.

Running it inside something else:

How any of this works, whether or not you use this project. Full index:

Related projects

Related projects that cover similar ground:

  • arkenfox/user.js - Firefox privacy hardening via prefs. invisible_playwright patches C++ where prefs are insufficient.
  • LibreWolf - Firefox fork with privacy defaults. LibreWolf ships a configured binary; invisible_playwright ships source patches + automation wrapper.
  • Camoufox - open-source anti-detect Firefox. Patches a wider surface and ships its own fingerprint database; invisible_playwright uses a Bayesian sampler.

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.


Built by Federico Elia  LinkedIn

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

invisible_playwright-0.4.6.tar.gz (421.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

invisible_playwright-0.4.6-py3-none-any.whl (92.0 kB view details)

Uploaded Python 3

File details

Details for the file invisible_playwright-0.4.6.tar.gz.

File metadata

  • Download URL: invisible_playwright-0.4.6.tar.gz
  • Upload date:
  • Size: 421.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.0

File hashes

Hashes for invisible_playwright-0.4.6.tar.gz
Algorithm Hash digest
SHA256 1dfc20726b97da6dd142a31397c9cb14165d50c6899d88e89a9fd3982eba6297
MD5 ec189938abf941fadf70a6cc7071dd1d
BLAKE2b-256 4a835dd3ef561a210d5d60ba8f13bf38d5f94556fc6352da23f10e8d636dfe42

See more details on using hashes here.

File details

Details for the file invisible_playwright-0.4.6-py3-none-any.whl.

File metadata

File hashes

Hashes for invisible_playwright-0.4.6-py3-none-any.whl
Algorithm Hash digest
SHA256 795e62adf2db08a5f824685b2e8c4f009695ebe58b984943dfa7693817b55e64
MD5 6620517b5e69f44c412d27b60a97183e
BLAKE2b-256 d5e4935ed02be54fa6ef1ba588a5ba9b97493d1f9d31b5d3c8f80898c2950577

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page