-
Notifications
You must be signed in to change notification settings - Fork 221
webrtc leak proxy
A SOCKS5 or HTTP proxy does not stop a WebRTC leak: proxies carry TCP, but WebRTC's STUN step needs UDP, so the browser asks a STUN server directly and the real IP comes back. Disabling WebRTC avoids that leak, but a browser with zero ICE candidates is its own signature.
"My browser leaks my real IP while using a proxy." "Playwright leaks IP with proxy."
"How do I prevent WebRTC leaks in Selenium?" Every answer to those questions ends the
same way: disable WebRTC, or set
--force-webrtc-ip-handling-policy=disable_non_proxied_udp.
That advice passes every WebRTC leak test and fails at the only thing that matters.
This page covers what actually leaks, why the standard fix creates a bigger problem than it solves, and what we learned getting it wrong ourselves, including a release gate of ours that printed green over a browser whose WebRTC was completely dead.
A real Firefox behind NAT emits exactly two ICE candidates: one host candidate carrying a random mDNS name instead of the LAN address, and one srflx candidate carrying the public IP learned from a STUN server. Start here, because almost nobody states this baseline explicitly.
Open a RTCPeerConnection on an ordinary consumer machine and you get, at minimum:
- one host candidate, whose address is not your LAN IP but a random
<uuid>.localname - one srflx (server reflexive) candidate, whose address is your public IP, learned from a STUN server
Two candidates. One meaningless, one your public address. That is the shape.
The .local part surprises people. Firefox sets
media.peerconnection.ice.obfuscate_host_addresses to true by default, so the LAN
address is replaced with an mDNS name. It has been the default for years.
1. You leak the LAN IP. The host candidate is 192.168.x.x. This is what leak
tests are built to catch, and it is the least interesting failure.
2. You leak the real public IP while claiming another one. You are on a proxy, your HTTP traffic exits from one address, and the srflx candidate names a different one. STUN runs over UDP; a SOCKS5 proxy carries TCP. So the browser asks a STUN server directly, from the real interface, and the STUN server truthfully answers with the real address. Two addresses for one session, and the contradiction is the signal, not either address on its own.
3. You emit nothing at all. No candidates, or the connection never completes. This is the one people create on purpose, and it is the worst of the three.
Almost nobody browses with WebRTC broken. It is on by default and it stays on, because video calls are normal. A session with zero ICE candidates has told the page something much more specific than an IP address: it has said this browser has been modified. An unusual IP is a weak signal. A missing capability is a strong one.
These are the two answers you will find, and they are worth taking separately.
--force-webrtc-ip-handling-policy=disable_non_proxied_udp is a Chromium flag.
It stops WebRTC from using any network path that does not go through the proxy. Since
a SOCKS5 proxy carries TCP and STUN wants UDP, in practice this means no srflx
candidate is gathered. You have converted failure state 2 into failure state 3.
It does prevent the leak. It also produces a browser that cannot gather a normal
candidate set, which is a condition a real Chrome almost never ends up in.
media.peerconnection.enabled=false is the Firefox equivalent and it is blunter:
RTCPeerConnection stops existing. A page can detect that in one line, without
setting up a connection at all. Firefox has no equivalent of the Chromium handling
policy, which is part of why this preference gets recommended so often.
Both work as leak prevention. Neither is invisible, and which one you want depends entirely on whether anything is looking. If you are protecting your own privacy while browsing, disabling WebRTC is a reasonable trade and you should stop reading here. If you are trying to look like an ordinary browser, you have swapped a leak for a signature.
The third option is the harder one: let WebRTC run, and make the address it reports be the address you want it to report.
We had a WebRTC gate. It asserted the sensible things: the host candidate does not expose the LAN address, no IPv6 candidate appears. Both assertions passed, run after run.
Behind a proxy, on real pages, WebRTC was returning blocked. Both the host and the STUN section. No candidates at all, failure state 3, shipped.
The gate passed because a dead WebRTC leaks nothing. Every negative assertion is trivially satisfied by a feature that does not run. We had written a test that could not distinguish "working correctly" from "not working".
The fix to the test was to assert the positive shape: the page has to complete,
the host candidate has to be a .local name, the srflx has to be present and has to
equal the proxy's exit address. An empty result is now a failure.
This is the general rule, and it is the most useful thing on this page:
A check that only asserts the absence of a bad signal will pass when the feature is broken. Assert the presence of the right signal instead.
The same shape shows up in fonts, in canvas, in audio. Anything you can suppress will pass a suppression-based test.
Fixing the assertion closed one hole and opened up a more uncomfortable question: what config had the passing test actually been running against?
The answer was that the test suite set the real-address handling, the derived public IP, and the IPv6 filter itself, through explicit overrides, and called that combination "the intended production config" in its own comments. A default session - the one an actual user gets with no extra arguments - had none of those three switched on in the wrapper's own shipped baseline. The gate had been green for every run because it never ran the shipped defaults at all; it ran a configuration nobody's real session used.
That is a different failure from the one above, and worth naming separately: the first was a test whose assertions couldn't tell working from broken; this one had assertions that were fine, aimed at a target that wasn't the one being shipped. Both produce the same outward symptom - a green gate over a feature real users don't actually get - for opposite reasons. The fix was to make the shipped baseline match what the test already assumed, not the other way around: derive the public IP from the proxy's own exit address automatically, turn the real-address handling on by default, and only then treat the gate as validating a real session instead of a hypothesis about one.
This one is worth stating plainly, because the preference is still recommended in a lot of places.
We searched for it in the ICE gathering path of the Firefox 151 source. It is not
read there. What exists is RESOLVE_DISABLE_IPV6 for DNS and a DISABLE_IPV6 flag
on TCP sockets. Neither is consulted when candidates are gathered.
The consequence, if your machine has a routable IPv6 address: WebRTC emits a host candidate carrying that real global IPv6, and a SOCKS5 proxy does nothing about it because it only carries TCP. Your IPv4 can be perfectly proxied while your IPv6 sits in the SDP in plain text.
Set the preference, verify it changed nothing, and then go looking for an actual fix. We ended up filtering IPv6 addresses in the gathering code itself and proving causality with a toggle: flag on, no IPv6 candidate; flag off, the global IPv6 comes straight back.
This took the longest to find and it is not intuitive.
A page that has not been granted camera or microphone permission causes Firefox to gather in a restricted mode: only the address of the default route, instead of every local interface. That mode is a privacy feature and it is the normal case, since most pages never ask for a camera.
Finding the default route works by opening a UDP socket "connected" to the remote address of the document and reading back which local address the OS picked. Behind a SOCKS proxy that remote host is not directly reachable, so the probe fails, so gathering fails, so you get zero candidates on every proxied page.
That was the real cause of the blocked state. Not a missing configuration value, not the STUN server, not the proxy credentials. A privacy path in the browser that assumes direct connectivity, meeting a setup that does not have it.
Worth knowing even if you never touch browser source: behind a proxy, WebRTC can fail for reasons that have nothing to do with WebRTC, and the error surfaces as silence.
Suppose you decide to supply the srflx candidate yourself, so it names your proxy's exit address. The address is the easy part. The candidate carries several other fields, and each of them is checkable:
-
Priority is computed by a documented formula
from the candidate type, the interface preference and the component id. Our first attempt hardcoded the local
preference to
0xFFFF. Real candidates on this stack fall in a range around 32256 to 32704, so65535is a value no genuine candidate ever has. One number, and it was enough on its own. - Foundation is an identifier shared by candidates of the same type from the same base. Ours drifted between runs, taking a different value each time and sometimes colliding with the TCP host candidate's. A real one is stable.
-
The
raddrandrportfields, and the relationship between a candidate and the base it was derived from.
A synthesised candidate is not one value to get right. It is a small structure that has to be internally consistent and consistent with the other candidates in the same SDP. This is the same lesson as every other fingerprint surface, arriving in a different costume. The full mechanics of replacing a candidate correctly, including the arrival-timing tell that catches a candidate injected too fast, are their own page.
Run a WebRTC test page through the proxy you actually deploy with. Not localhost: localhost bypasses the proxy and tests nothing. BrowserLeaks lists the full candidate set, and CreepJS shows what a real detector extracts from it.
What you want to see:
- The page completes. A stuck or empty result is a failure, not a pass.
- Host candidate is a
<uuid>.localname. Not a LAN IP, and not absent. - An srflx candidate exists, and its address equals the exit address of your proxy.
- No IPv6 candidate, if your proxy is IPv4.
- The same page in a stock Firefox, side by side, differing only in the IP.
That last one is the check that catches everything else. If your browser's output differs from a normal Firefox in any field except the address, that field is the problem.
The engine emits an srflx candidate carrying the proxy's exit address, with priority
and foundation rebuilt to match the shape the real code produces, and the host
candidate is left as the mDNS .local name Firefox produces by default. IPv6
candidates are filtered in the gathering path when a proxy is configured. The exit
address is resolved at launch by the wrapper, in the same lookup that resolves the
timezone, so the two cannot disagree.
Nothing here is turned off. The goal throughout is a browser that emits what a real one emits, from an address you chose.
Does a SOCKS5 proxy stop a WebRTC leak? No. SOCKS5 carries TCP, STUN uses UDP, so the STUN request leaves from the real interface and the answer names the real address. The proxy is working correctly; WebRTC is simply not going through it.
Does an HTTP proxy stop it? No, for the same reason.
Does Playwright's proxy= option stop it? No. It configures the browser's HTTP
path. ICE gathering is a separate mechanism.
Why does my IP leak in Firefox but not in Chrome, or the other way round? The two engines gather candidates differently and expose different controls. Chromium has a handling policy flag; Firefox does not. Test the engine you actually ship.
Is media.peerconnection.ice.disableIPv6 the fix for an IPv6 leak? Not in current
Firefox. See the section above: the preference is not read during ICE gathering.
Will a VPN fix it? Usually yes, because a VPN moves the whole interface rather than just the browser's TCP traffic, so STUN leaves through the tunnel too. That is the actual difference between a VPN and a proxy here.
Is it enough to check a leak test page? Only if the page completes and shows a full candidate set. A leak test that reports "no leak" over a browser with WebRTC disabled is reporting the absence of a feature, not the presence of protection.
See also: when the timezone does not match the proxy, the other half of "the browser and the network disagree", what privacy.resistFingerprinting really does, another case where the protective setting is itself visible, and the checklist for being detected on one site.
From the notes of invisible_playwright. The dead-gate story is ours, and the rule it produced now applies to every gate we run.
Documentation
Guides
-
Browser Identity
- navigator.webdriver is not the tell you think it is
- hardwareConcurrency, deviceMemory and storage quota
- Screen size and viewport tells in headless browsers
- Playwright headless vs headed: what detectors see
- Playwright User Agent: Why You Should Not Set It
- Client Hints and Sec-Fetch: headers that must agree
- Codec fingerprinting: canPlayType and MediaCapabilities
- Permissions API: the two answers that must agree
- CSS fingerprinting: what media queries reveal
- What privacy.resistFingerprinting actually does
- speechSynthesis.getVoices() returns an empty array
- Browser extensions are a fingerprint surface
- BFCache and pageshow.persisted under browser automation
- Service workers, storage partitioning and automation
- Web Workers: where page-level fingerprint patches fail
- fake-useragent is archived: what changes and what doesn't
- navigator.buildID and the stale build date tell
- navigator.maxTouchPoints and pointer consistency
- navigator.platform and oscpu on a spoofed OS
- navigator.vendor and productSub: the Firefox tells
- Accept-Language header vs navigator.languages
- window.devicePixelRatio: the pref that spoofs it
- Can you be fingerprinted in incognito mode?
- Is changing the user agent enough to avoid detection?
- Can a website tell you are running on a server?
- Can two devices share a browser fingerprint?
- Does clearing cookies stop fingerprint tracking?
- Color-gamut and HDR media queries as a fingerprint
- Battery API fingerprint: does Firefox expose it?
- Is navigator.connection a fingerprint in Firefox?
- Can the Gamepad API fingerprint or detect a bot?
- Do accelerometer and gyroscope APIs leak on desktop?
- prefers-reduced-motion and other OS-setting tells
- Does storage quota estimate reveal disk size?
- Can scrollbar width reveal my operating system?
-
Canvas, WebGL, Fonts and Audio
- Canvas fingerprint noise: why per-call randomising fails
- Firefox WebGL renderer strings: what ANGLE reports
- WebGL parameters: the numbers are the same on every GPU
- Your renderer string says NVIDIA. Your pixels say software.
- Why headless browsers render different fonts
- How to make Linux and macOS report real Windows fonts
- measureText and TextMetrics as a fingerprinting surface
- AudioContext fingerprinting, and why adding noise backfired
- Canvas and WebGL fingerprints, identical across OSes
- Emoji fingerprinting: why emoji look the same on any OS
- Detecting installed fonts in JavaScript by width
- WebGL shader precision as a fingerprint surface
- AudioContext sampleRate and latency as a fingerprint
- Is WebGPU a browser fingerprint?
-
Network, Proxy and WebRTC
- WebRTC leak with a proxy in Playwright and Selenium
- WebRTC ICE candidate spoofing: the fields that give it away
- Playwright proxy in Python: per-context, and what leaks
- Playwright proxy not working? SOCKS5 auth in Python
- Playwright timezone does not match the proxy IP
- JA3 and JA4: why a TLS fingerprint cannot be patched
- Playwright in Docker: it runs, and still gets blocked
- Web scraping keeps getting blocked with good proxies
- Python web scraping blocked? The TLS fingerprint reason
- SOCKS5 vs HTTP proxy: what each does in the browser
- WebRTC IPv6 leak: why a proxy does not stop it
- HTTP/2 fingerprint: the layer above the TLS handshake
- TLS fingerprint vs User-Agent: the contradiction
- WebRTC has no ICE candidates behind a proxy
- WebRTC IP that matches the proxy exit, by design
- How to check if a proxy leaks your real IP
- about:webrtc: read your real ICE candidates
- Offline timezone resolution from a proxy exit IP
- Residential vs datacenter vs mobile proxies explained
- Sticky vs rotating proxy sessions: which to use
- Does a proxy leak DNS? DoH and DNS leaks explained
- HTTP/3 and QUIC fingerprint: what a site sees
- What is ASN and IP reputation in bot detection?
- What does a mobile carrier IP look like to a site?
- IPv6 vs IPv4: which does your proxy expose?
- Geolocation API vs IP location: keep them consistent
- Does chaining two proxies help avoid detection?
-
The Automation Layer
- Function.prototype.toString and the [native code] check
- The ChromeDriver
cdc_variable, and why renaming it fails - Why an attached debugger makes automation detectable
- Execution context was destroyed, and when it means detection
- Human-like mouse movement: Bezier curves are the easy part
- Why a Playwright upgrade broke 97 of 133 tests overnight
- Playwright persistent profile: what it fixes and breaks
- Why humanized mouse movement can fail on hover()
- Why content_frame() returns None for a cross-origin iframe
- Orphaned Firefox processes on Windows: the killed-runner leak
- Firefox launches but Playwright can't drive it: packaging gap
- Why automating login is riskier than reusing a session
- Playwright new_page vs new_context: the viewport tell
- Playwright dialog and popup handling without a tell
- Playwright download files with Firefox and the tell
- Playwright connect_over_cdp does not work with Firefox
- Playwright mobile emulation on Firefox and isMobile
- Playwright isTrusted: are automated clicks real?
- Playwright set_input_files uploads and the tell
- Can websites detect Playwright? What is actually visible
- Does Playwright Set navigator.webdriver to True?
- Does Playwright Leave Traces a Website Can See?
- Does Playwright Change My Browser Fingerprint?
- Can I Use My Real Browser Profile With Playwright?
- Does Playwright Support Firefox Stealth?
- Is Playwright Firefox Harder to Detect Than Chromium?
- Does Playwright Get Detected on the First Request?
- Why Playwright's bundled Firefox is easy to detect
- ghost-cursor human mouse paths with Playwright
- Stock Playwright, patched Firefox: how they connect
- Intercept and mock network requests with page.route
- Record and replay HTTP traffic with HAR in Playwright
- Record a Playwright trace to debug a failed scrape
- Record a video of a Playwright browser session
- Save and reuse login with storage_state in Playwright
- Read and set cookies in a Playwright context
- Set geolocation and permissions per Playwright context
- Handle HTTP basic auth in Playwright (http_credentials)
- Isolate identities with a browser context per session
- Drag and drop elements in Playwright with drag_to
- When to use an HTTP client vs a real browser
- Migrating from requests + BeautifulSoup to a browser
-
AI Agents and Frameworks
- AI browser agents and stealth: what fits and what does not
- browser-use gets detected: what you can and cannot change
- crawl4ai stealth mode and custom browser engines
- Give a LangChain agent an invisible_playwright browser
- Feed invisible_playwright pages into a RAG index
- Computer-use agents and browser fingerprint detection
- Give an MCP browser server a stealth Firefox engine
- Give each AI agent a reproducible browser identity
- Run parallel browser agents with distinct fingerprints
- Why AI browser agents have their own timing signal
- Running an AI browser agent headless on a server
- Give a browser agent a persistent logged-in session
- smolagents: hand the agent an invisible_playwright tool
- Stagehand and stealth: why a Firefox engine won't drop in
- DOM-reading vs screenshot agents: which stealth helps
- Back a computer-use agent with a real browser engine
- AI agent retry loops trip rate limits, not fingerprints
-
Detectors, Explained
- What bot.sannysoft.com actually checks, row by row
- How CreepJS decides you are lying
- What BotD actually detects, and what it does not
- Why a FingerprintJS visitor ID changes
- reCAPTCHA v3 score: why a fresh browser scores badly
- BrowserLeaks canvas and WebGL hash, explained
- What BrowserLeaks actually tests, surface by surface
- Browser trust scores explained: what the number means
- How do websites detect bots?
- What is a browser fingerprint?
- What data does a website collect about your browser?
- Does a VPN stop browser fingerprinting?
- Do websites know you are using a script?
- How accurate is browser fingerprinting?
- Can a website detect a virtual machine?
- Can websites detect a datacenter or proxy IP?
- getClientRects fingerprinting: subpixel geometry as ID
- Notification.permission as a bot-detection signal
- speechSynthesis voices as a cross-platform fingerprint
- Can a website detect typing by keystroke timing?
- Can a website detect Clipboard API access?
- What are mouse-dynamics behavioural biometrics?
-
Testing and Troubleshooting
- How to test bot detection without a false pass
- Playwright detected as a bot: the checklist to fix it
- Firefox preferences that silently do nothing
- Slow browser launch: a per-request timeout is not a budget
- Playwright screenshot returns noise: readback fix
- Canvas fingerprint changes every run: use a seed
- Playwright TargetClosedError: the causes and the fixes
- Why am I blocked with a clean fingerprint?
- Why Does My Playwright Script Get Blocked?
- Is Playwright headless detectable? What sites check
- Can You Run Playwright Without Being Detected?
- Why Playwright Works Locally but Fails in the Cloud
- Does Playwright Trigger reCAPTCHA More Often?
-
Scraping with Playwright
- How to scrape without getting blocked
- How to scrape a site that blocks headless browsers
- How to scrape infinite scroll pages with Playwright
- How to rotate proxies when scraping with Playwright
- How to scrape data behind a login with Playwright
- How to run Playwright in Docker without getting detected
- How to use invisible_playwright in Docker
- Playwright bot detection: how to avoid it in Python
- How to scrape paginated pages with Playwright
- How to download files with Playwright
- How to upload files with Playwright, and verify it landed
- How to handle cookie consent banners in Playwright
- How to handle popups and modals in Playwright
- How to take full-page screenshots with Playwright
- How to generate a PDF with Playwright and Firefox
- How to wait for content to load in Playwright
- How to retry failed requests when scraping Playwright
- How to scrape pages in parallel with Playwright
- How to rate limit your own Playwright scraper
- How to scrape HTML tables with Playwright
- How to scrape iframe content with Playwright
- How to scrape shadow DOM content with Playwright
- How to capture XHR and API responses in Playwright
- How to scrape geotargeted content with Playwright
- How to scrape real estate listings with Playwright
- How to scrape job postings with Playwright
- How to scrape e-commerce product pages with Playwright
- How to track product prices with Playwright
- How to scrape hotel room prices with Playwright
- How to scrape flight prices with Playwright
- How to scrape classifieds listings with Playwright
- How to scrape vacation rental listings with Playwright
- How to scrape car listings with Playwright
- How to scrape apartment rentals with Playwright
- How to track product stock and restocks with Playwright
- How to scrape location-based store prices with Playwright
- How to scrape flexible-date fare calendars with Playwright
- How to scrape product reviews with Playwright
- How to scrape reviews and ratings with Playwright
- How to scrape news article text with Playwright
- How to scrape business directory listings with Playwright
- How to scrape event and ticket listings with Playwright
- How to scrape restaurant menu data with Playwright
- How to scrape stock and financial data with Playwright
- How to scrape social media profiles with Playwright
- How to scrape forum and community threads with Playwright
- How to scrape image galleries with Playwright
- How to scrape video listings and metadata with Playwright
- How to scrape map-based local results with Playwright
- How to scrape sports scores and stats with Playwright
- How to scrape cryptocurrency prices with Playwright
- How to scrape deals and coupon codes with Playwright
- How to scrape to CSV with Playwright
- How to scrape to JSON Lines with Playwright
- How to scrape into a SQLite database with Playwright
- How to export scraped data to Excel with Playwright
- How to extract JSON-LD structured data with Playwright
- How to extract Open Graph and meta tags with Playwright
- How to extract links and build a crawl frontier in Playwright
- How to scrape RSS and Atom feeds with Playwright
- How to download images in bulk with Playwright
- How to extract clean article text with Playwright
- How to scrape a sitemap.xml with Playwright
- How to scrape into a pandas DataFrame with Playwright
- How to clean scraped prices and dates with Playwright
- Scrape search results by driving a form in Playwright
- Scrape a map-based search with Playwright
- Scrape autocomplete and typeahead inputs with Playwright
- Scrape date-picker calendars with Playwright
- Crawl list pages to detail pages with Playwright
- Scrape lazy-loaded images with Playwright
- Extract data from canvas charts with Playwright
- Scrape a multi-step wizard flow with Playwright
- How to resume an interrupted scrape with Playwright
- Incremental scraping: only new items since last run
- Handle 403 and 429 backoff mid-scrape in Playwright
- Scrape load-more button pages with Playwright
- Scrape nested pagination with Playwright
- Scrape an SPA that changes URL via history API
- Use BeautifulSoup with invisible_playwright
- Run stealth Playwright tests with pytest fixtures
- Run invisible_playwright concurrently with asyncio
- Run invisible_playwright in GitHub Actions CI
- Can you run invisible_playwright serverless?
- Run invisible_playwright in Celery task workers
- Schedule invisible_playwright scrapes with cron
- Run invisible_playwright headful on a server with Xvfb
- Use invisible_playwright in an Airflow DAG
- Combine invisible_playwright with httpx for speed
- Wrap invisible_playwright in a FastAPI service
- Run invisible_playwright in a Jupyter notebook
- Block images to speed up scraping (and when not to)
- Wait for a specific API response in Playwright
Comparisons
- Playwright stealth in Python: three levels that work
- Firefox or Chromium for anti-detect automation
- Chromium is not Chrome, and detectors know the difference
- Playwright stealth vs Camoufox: two patched Firefoxes
- Playwright stealth vs Patchright: driver vs engine
- Playwright stealth vs undetected-chromedriver and nodriver
- playwright-stealth vs a patched engine: page vs browser
- puppeteer-extra-plugin-stealth: unmaintained since 2024
- selenium-stealth hasn't been updated since December 2021
- pyppeteer's own maintainer says to switch to Playwright
- invisible_playwright vs rebrowser-patches: the same CDP fix
- invisible_playwright vs fingerprint-suite: injection vs engine
- invisible_playwright vs playwright-with-fingerprints
- invisible_playwright vs Scrapling
- invisible_playwright vs Ulixee Hero
- invisible_playwright vs SeleniumBase UC Mode
- Splash is unmaintained, and it was never a real browser
- invisible_playwright vs DrissionPage
- WebDriver BiDi vs CDP: does the new protocol hide you
- invisible_playwright vs hrequests
- zendriver vs invisible_playwright: Chrome CDP vs Firefox
- botasaurus vs invisible_playwright: framework vs library
- curl_cffi vs invisible_playwright: TLS client vs browser
- pydoll vs invisible_playwright: CDP without a driver
- selenium-driverless vs invisible_playwright stealth
- puppeteer-real-browser vs invisible_playwright
- Migrating from Selenium to Playwright for stealth
- Migrating from Puppeteer to Playwright for stealth
- undetected-chromedriver vs a patched Firefox browser
- scrapy-playwright vs a patched Firefox for stealth
- playwright-extra stealth plugins vs a patched browser
- tls-client vs a real browser: when TLS is enough
- Anti-detect browser or Playwright stealth: which you need
- undetected-playwright vs a patched Firefox binary
Integrations
- Using invisible_playwright with CodeceptJS
- Using invisible_playwright with Crawlee for Python
- Using invisible_playwright with Crawlee for JavaScript
- Using invisible_playwright with scrapy-playwright
- Using invisible_playwright with Robot Framework Browser
- Cypress, WebdriverIO, TestCafe and Nightwatch integration
- Using invisible_playwright with Microsoft's Playwright MCP
- Using the engine from Go, Java, C#, Ruby and Rust
docs/ source folder