Skip to content

vs pydoll

github-actions[bot] edited this page Nov 13, 2026 · 5 revisions

pydoll vs invisible_playwright: CDP without a driver

pydoll is an actively developed async Python library that automates Chrome by speaking the Chrome DevTools Protocol directly, with no chromedriver or WebDriver binary in the loop. Removing that binary removes a whole category of classic driver tells at their source. This project takes a different route: a Firefox patched at the C++ level, driven by stock Playwright, where every session's canvas, WebGL renderer and font metrics are derived from one seed and kept mutually consistent.

The two tools sit at different layers of the same problem, and the honest short version is that each fixes something the other does not, while neither fixes the part that lives outside the browser. This page is about which layer you are actually short on.

Two answers to the same tell, at different layers

A browser gives itself away in more than one place at once. The driver layer is one of them: the plumbing that connects your script to the browser can leave marks that a page can read. The fingerprint layer is another: the values the browser reports for its GPU, its canvas output, its fonts and its audio pipeline, and whether those values agree with each other and with the platform you claim to be.

pydoll's own engineered fix lives at the driver layer. It removes the intermediary binary and talks CDP straight to Chrome, so the tells that came from that binary are simply not there to find. That is a real and durable fix for a real class of problem, and pydoll layers a fingerprint-injection call on top of it, which a later section covers on its own terms.

invisible_playwright's own engineered fix lives at the fingerprint layer. The engine is a genuine Firefox build, so its TLS handshake and JavaScript engine already read as Firefox, and the values it reports are generated together from a seed so they do not contradict one another. Each tool's own engineering sits at a different layer, which is the failure each one solves without asking anything of you first.

Neither of these is a superset of the other, which is the whole reason a comparison is worth writing instead of just picking the newer tool.

What pydoll removes: the driver, by speaking CDP directly

The classic Selenium stack drives Chrome through a separate chromedriver process, and that process historically left literal marks in the page, including a global variable injected by the driver. That is the same family of tell we describe in the ChromeDriver cdc variable, explained, and it generalises: a mark you rename is still a mark if what gets checked is that something is there at all, not what it is called.

pydoll's answer, by its own documentation, is to drop the driver binary entirely and connect to Chrome over CDP. With no intermediary process there is no injected variable to rename, no extra process in the tree, and no separate port signature to explain away. It is the same architectural move that nodriver makes on the same lineage, applied through an async Python API of pydoll's own rather than through Playwright.

What this buys you is a clean automation layer. The navigator.webdriver flag and its neighbours are mostly solved and mostly not your problem in 2026 precisely because tools like this one remove them at the root. That specific move does not by itself touch the fingerprint layer below it. pydoll addresses that layer too, through a separate call the next section covers.

What a seeded Firefox carries: a fingerprint that agrees with itself

pydoll is Chrome-based, and it does not leave the rendering-layer fingerprint untouched. Its own apply_fingerprint() call overrides the User-Agent and Client Hints, navigator, WebGL, canvas, screen, fonts, timezone and locale together, but pydoll's own documentation says plainly that it does not generate or ship the profile behind that call: you copy a sample file and adapt every field to your own machine, GPU and IP by hand. On one real desktop where you know your own hardware, that adaptation is a cost you pay once. On a headless server, or across a fleet of them, hand-adapting a profile per box is the part that does not scale, and pydoll's own docs warn about exactly that failure mode: getting one layer right while another contradicts it is worse than an unmodified browser.

invisible_playwright generates those same surfaces together from one seed, with no profile file for you to copy or adjust. The canvas hash, the WebGL renderer string and the font metrics are drawn as one coherent identity by the tool itself, so they agree with each other rather than depending on whoever assembled the profile getting every field right. That mutual consistency is what a tampering check like CreepJS actually grades, and it is a harder property to hold one field at a time than it looks, generated or hand-built.

One subtlety worth naming, because it is the most common way a spoof at this layer still fails: a renderer string can claim a real GPU while the pixels are drawn by a software rasterizer, which is a mismatch you cannot patch away with a string edit. Keeping the reported name and the actual draw path consistent is part of what "coherent" has to mean here.

Because it is all derived from a seed, the identity is reproducible:

from invisible_playwright import InvisiblePlaywright

with InvisiblePlaywright(seed=42) as browser:
    page = browser.new_page()
    page.goto("https://example.com")
    # same GPU string, same canvas hash, same font metrics on every run

Pass the same seed tomorrow and you get the same machine, which is what makes a failing run reproducible instead of a guess.

The honest caveat: neither approach touches your IP or your behaviour

This is the part that gets skipped, so it goes in its own section.

Removing the driver tells does not fix your IP reputation. Spoofing the fingerprint does not fix it either. A coherent Firefox on a datacenter address is still on a datacenter address, and a clean CDP Chrome from the same range is in the same spot. Neither tool changes the exit you come from.

Neither one changes your behaviour, either. Per-account quotas, rate limits, the pace of your requests and the shape of your pointer motion are all still yours to supply. A session that fills a form in eighty milliseconds and never moves the mouse reads as automation whatever engine drew the page.

invisible_playwright is designed to look like a real browser driven by a real person, and that is why it passes most in-page detection checks: the fingerprint, the TLS handshake and the driver layer all read as a genuine Firefox. It passes those checks. It does not, on its own, fix IP reputation, quotas, rate limits or timing. You supply those, with a clean exit and human pacing, and the working order for diagnosing which one is biting you is the checklist for being detected on one site.

Using invisible_playwright, and how to think about pydoll alongside it

Switching from stock Playwright is a two-line change, and the returned browser is a real Playwright Browser, so every method you already use works unchanged:

from invisible_playwright import InvisiblePlaywright

proxy = {"server": "socks5://gate.example.com:1080", "username": "u", "password": "p"}

with InvisiblePlaywright(seed=42, proxy=proxy) as browser:
    page = browser.new_page()
    page.goto("https://example.com")
    page.click("#submit")   # mouse arcs to the button on a Bezier curve
uv add invisible-playwright

If your project is committed to Chrome and to an async Python API of its own, and your machines have real GPUs so hand-adapting one fingerprint profile is a cost you pay once, pydoll's driverless CDP approach plus its own injection call is a clean fit. If your problem is that hand-building a profile per headless box does not scale, or you already have Playwright code you do not want to rewrite, an engine that generates a coherent seeded identity itself is the layer you are short on. pydoll gives you the CDP connection and the injection point; here the engine also generates what goes through it.

How to choose

  • Driver tells are your problem and you are on Chrome with real hardware? pydoll removes the driver binary cleanly and gives you an async CDP API.
  • Fingerprint layer gives you away on servers? A seeded Firefox generates canvas, WebGL and font values together so they stay mutually consistent by construction; pydoll's own injection call expects you to supply and adapt that profile yourself.
  • Have existing Playwright code? invisible_playwright is a two-line swap; pydoll is a different API to adopt.
  • Need Firefox specifically, or a Firefox TLS handshake? pydoll is Chrome-based; this project is a real Firefox build.
  • Blocked despite a clean browser? Look at the exit and the behaviour next, because neither tool changes those.

Conclusion

pydoll and invisible_playwright are not the same bet. pydoll removes the driver layer by speaking CDP with no binary, which is a genuine fix for the tells that binary left behind, and its own injection call reaches the fingerprint layer too, on condition that you supply the profile. invisible_playwright ships a real Firefox whose fingerprint is generated coherently from a seed, with no profile left for you to build. Pick the one that matches the work you would rather the tool do for you, and remember that both leave your IP reputation and your behaviour exactly where they were.

Short answers to the questions that lead here

Is pydoll undetectable? No tool is, and pydoll does not claim to be. It removes the driver-layer tells by dropping the chromedriver binary and speaking CDP directly, and its own injection call reaches the fingerprint layer too; what it leaves to you is the profile behind that call, plus IP and behaviour, which no injection call touches.

Does pydoll spoof the browser fingerprint? It can. Its apply_fingerprint() call overrides GPU, canvas, fonts and more, but pydoll's own documentation says it does not generate or ship the profile behind that call, so you supply and adapt the values yourself. That is a one-time cost on a single real machine and a recurring one across a headless fleet.

What does invisible_playwright do that pydoll does not? It generates canvas, WebGL renderer and font metrics together from one seed with no profile for you to hand-adapt, and it runs on a real Firefox whose TLS handshake reads as Firefox. pydoll's own fingerprint call still needs a profile you build and match to the box yourself.

Will either one fix my IP getting blocked? No. Neither changes your exit address. A clean browser on a datacenter IP is still on a datacenter IP, so pair either tool with a clean proxy.

Can I keep my existing Playwright code? With invisible_playwright, yes, it is a two-line launch change. pydoll has its own async API, so adopting it means rewriting the automation layer.

Is pydoll maintained? Yes, it is an actively developed async library at the time of writing; check its own repository for the current state before you depend on it.

Sources

See also: invisible_playwright vs nodriver and undetected-chromedriver for the same driverless-CDP idea on the Selenium lineage; the checklist for being detected on one site for the order to diagnose in; and how to test whether your browser is detected for reading suites as evidence rather than verdicts.


Written while maintaining invisible_playwright, a Firefox patched at the C++ level driven by stock Playwright. pydoll solves the driver layer honestly and says what it does not cover; this project works the fingerprint layer, and neither of us touches your IP or your pacing.

Documentation

Guides

Comparisons

Integrations

docs/ source folder

Clone this wiki locally