Async Firefox browser automation for Python, built on the W3C WebDriver BiDi protocol.
Inspired by Pydoll, built for Firefox.
WebDriver BiDi is the W3C protocol for bidirectional browser automation, natively supported by modern Firefox releases.
pyfox connects to Firefox directly through BiDi over WebSocket. No Selenium, no geckodriver, no Playwright underneath. This keeps the API fully asynchronous and removes the need for any external WebDriver binary.
- Async API built around
asyncio - Direct WebDriver BiDi communication over WebSocket
- Multiple tabs and isolated browser contexts
- CSS, XPath, ID, class, name, tag, text, and attribute selectors
- Iframe and Shadow DOM traversal
- Humanized mouse movement and keyboard input
- Screenshots and PDF generation
- Cookie management
- Network monitoring and interception
- Request and response modification, mocking, and failure
- Response body capture during interception
- HAR 1.2 recording
- Download handling with
expect_download() - Browser dialogs and network logs
- Pydantic-based structured extraction
- Async retry decorator
- ForgeAPI integrated fingerprint controls
- Cloudflare Turnstile interaction support
pip install pyfox-autoRequirements
- Python 3.12+
- Firefox 128+
Firefox 130+ is recommended when using download functionality. Firefox-based browsers with WebDriver BiDi support also work.
import asyncio
from pyfox import Firefox, FirefoxOptions
async def main():
options = FirefoxOptions()
# Point to any Firefox-based browser binary:
# options.binary_path = "/path/to/firefox-based-browser"
async with Firefox(options=options) as browser:
tab = await browser.new_tab()
await tab.navigate("https://www.google.com")
search = await tab.find_or_wait_element("input[name='q']", timeout=10)
await search.type_text("pyfox automation")
await tab.screenshot("screenshot.png")
asyncio.run(main())async with Firefox(options=options) as browser:
tab = await browser.new_tab()
await tab.navigate("https://example.com")
print(await tab.title)For isolated sessions, create a browser context:
context_id = await browser.create_context()
tab = await browser.new_tab(context_id=context_id)Each context maintains its own browser state, allowing multiple independent sessions within the same Firefox instance.
Supported selector types: CSS, XPath, ID, class, name, tag, text, and attributes. Elements can also be searched across iframes.
element = await tab.find_or_wait_element("button.submit", timeout=10)WebElement exposes click, type, scroll, get attribute, screenshot, and Shadow DOM access.
Mouse movement uses cubic Bézier curves, Fitts's Law timing, Gaussian tremor, and overshoot correction.
await tab.mouse.move(500, 300, humanize=True)
await tab.mouse.click(500, 300, humanize=True)Keyboard input supports variable per-character delays and QWERTY-based typo simulation.
await element.type_text("pyfox automation", humanize=True)shadow_roots = await tab.find_shadow_roots(deep=True)
for shadow_root in shadow_roots:
button = await shadow_root.query(".internal-button", raise_exc=False)
if button:
await button.click()Shadow roots expose the same element-finding API used elsewhere in the library.
Monitor and intercept browser network traffic through WebDriver BiDi.
Supported operations: request and response interception, request modification, response handling, mocking, failure, authentication, and network logging.
async with tab.capture_response_body(url_pattern="api/data") as capture:
await tab.navigate("https://example.com")
response = capture.get("api/data")WebDriver BiDi does not provide direct response body access outside an active interception workflow, unlike CDP.
Network activity can be recorded and exported as HAR 1.2. Useful for keeping a complete trace of requests and responses during a session.
cookies = await tab.get_cookies()Setting and deleting cookies is also supported through the BiDi storage API.
async with tab.expect_download() as download:
await element.click()
file = await download.valueFirefox 130+ is recommended for download functionality.
Define a Pydantic model and extract typed data directly from the page:
from pyfox.extractor import ExtractionModel, Field
class Quote(ExtractionModel):
text: str = Field(selector=".text")
author: str = Field(selector=".author")
quote = await tab.extract(Quote)
print(quote.author)
quotes = await tab.extract_all(Quote, scope=".quote")ForgeAPI provides browser fingerprint controls via script.addPreloadScript. It requires no browser extension and no mandatory proxy.
Controllable properties: canvas, WebGL, AudioContext, fonts, navigator, screen, hardware concurrency, timezone, permissions, battery.
from pyfox.antidetect import forge_check
result = await forge_check(tab)ForgeAPI can also be enabled at the browser level so every new tab gets it automatically:
async with Firefox(options=options, antidetect=True) as browser:
tab = await browser.new_tab()async with tab.expect_and_bypass_cloudflare_captcha():
await tab.navigate("https://site-with-turnstile.com")This is not a guaranteed bypass. Results depend on browser environment, network, IP reputation, and the challenge itself.
from pyfox.decorators import retry
@retry(max_retries=3, exponential_backoff=True)
async def scrape():
...Custom recovery logic can be executed between attempts.
pyfox targets Firefox and Firefox-based browsers with WebDriver BiDi support.
Tested or targeted: Firefox, Zen Browser, Floorp, LibreWolf, and Waterfox. Any Firefox-based browser with WebDriver BiDi support should work.
pyfox uses a single WebSocket connection per browser instance, shared across all tabs.
Firefox
│
WebDriver BiDi (WebSocket)
│
ConnectionHandler
├── Tab ── WebElement
├── Tab ── WebElement
└── Tab ── WebElement
pyfox/
├── antidetect/ # ForgeAPI and preload scripts
├── browser/ # Firefox, Tab, FirefoxOptions, downloads, requests
├── connection/ # ConnectionHandler, CommandsManager, EventsManager
├── elements/ # WebElement, ShadowRoot, FindElementsMixin
├── extractor/ # Pydantic structured extraction
├── interactions/ # Mouse, Keyboard, Scroll
└── protocol/ # WebDriver BiDi protocol builders
websockets>=12.0
aiofiles>=23.0
pydantic>=2.0
typing-extensions>=4.9
pyfox follows a similar API philosophy to Pydoll, targeting Firefox via WebDriver BiDi instead of Chromium via CDP.
| Feature | Pydoll | pyfox |
|---|---|---|
| Browser | Chromium | Firefox |
| Protocol | CDP | WebDriver BiDi |
| Async API | ✓ | ✓ |
| WebDriver binary | ✗ | ✗ |
| Multiple tabs | ✓ | ✓ |
| Browser contexts | ✓ | ✓ |
| Shadow DOM | ✓ | ✓ |
| Network interception | ✓ | ✓ |
| HAR recording | ✓ | ✓ |
| Humanized mouse | ✓ | ✓ |
| Humanized keyboard | ✓ | ✓ |
| Structured extraction | ✓ | ✓ |
| Retry decorator | ✓ | ✓ |
| Downloads | ✓ | ✓ |
| Firefox support | ✗ | ✓ |
| Chromium support | ✓ | ✗ |
These are protocol-level differences, not missing wrappers:
- Chromium and Chrome are not supported
navigator.webdriverremainstrueunder Firefox WebDriver BiDi (W3C spec behavior)- No direct equivalent to CDP's file chooser APIs
- Response bodies are only accessible during an active network interception workflow
Version 0.1.0 - 119 unit tests, 204 integration tests (run against real Firefox on Windows).
Issues and pull requests are welcome. When reporting a bug, include enough information to reproduce it - browser version, OS, and a minimal script if possible.
MIT
