Skip to main content

fluxwright

Run hundreds of headless Chrome jobs on one machine, with a Playwright-style API. Each new_page is a fresh browser context on a pooled Chrome. The engine queues jobs when every slot is busy, restarts browsers before they bloat, and replaces browsers that crash.

Docs: https://fluxwright.vercel.app

Requires a local Chrome/Chromium binary (FLUXWRIGHT_CHROMIUM, CHROME, CHROMIUM, or a standard install path). For headless jobs, chrome-headless-shell is picked up from PATH or Puppeteer's/Playwright's cache and opens pages much faster: npx @puppeteer/browsers install chrome-headless-shell@stable.

Install

pip install fluxwright

Wheels cover Windows x64, macOS (Intel and Apple Silicon) and Linux x86_64, for Python 3.10 and newer. Anywhere else, pip builds from source, which needs Rust.

Use

asyncio:

import asyncio
from fluxwright.async_api import async_playwright

async def main():
    async with async_playwright() as p:
        browser = await p.chromium.launch(max_browsers=4)
        page = await browser.new_page()
        await page.goto("https://example.com")
        print(await page.get_by_role("heading").text_content())  # "Example Domain"
        print(await page.evaluate("() => location.hostname"))
        png = await page.screenshot(full_page=True)  # bytes
        await page.close()
        await browser.close()

asyncio.run(main())

Blocking code:

from fluxwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.goto("https://example.com")
    print(page.title())
    browser.close()

The names are Playwright's, so a Playwright script for the features below runs after changing playwright.async_api to fluxwright.async_api (or sync_api). chromium can also be imported directly: from fluxwright.async_api import chromium.

Many jobs at once: start them all and let the engine queue them.

async def job(browser, url):
    page = await browser.new_page()
    try:
        await page.goto(url)
        return await page.title()
    finally:
        await page.close()

titles = await asyncio.gather(*(job(browser, url) for url in urls))

The sync API runs every call on one event loop in a background thread, so its objects work from any thread: a ThreadPoolExecutor of jobs sharing one browser is fine (Playwright's sync API does not allow that).

API

Method What it does
chromium.launch(max_browsers=None, executable_path=None, headless=None) Start the pool (4 browsers by default). Headless uses chrome-headless-shell when installed (as Playwright does), else Chrome; FLUXWRIGHT_CHROMIUM or executable_path overrides
browser.new_page(...) A fresh browser context. Every option applies to this page only: proxy={"server", "bypass", "username", "password"}, user_agent, locale, timezone_id, geolocation={"latitude", "longitude", "accuracy"} (with permissions=["geolocation"]), permissions, viewport={"width", "height"}, device_scale_factor, color_scheme, storage_state (a dict or a file path)
page.storage_state(path=None) Every cookie in the context plus localStorage of the current origin, in Playwright's format. With path, also saved as JSON for new_page(storage_state=path)
page.goto(url, wait_until=None) Navigate. wait_until: load (default), domcontentloaded, networkidle, commit
page.title() / page.content() Read
page.click(selector) / page.fill(selector, value) Scrolls into view, waits until enabled, stable, and not covered. Selector: CSS, text=Foo, text="Exact", role=button[name="Save"]
page.get_by_role(role, name=None, exact=None) / get_by_text(text, exact=None) / get_by_label / get_by_placeholder / get_by_test_id(id) / page.locator(selector) A Locator with click(), fill(v), wait_for(), text_content(), bounding_box(), screenshot(path=None) and evaluate("(el, arg) => ...", arg)
locator.nth(i) / .first / .last / filter(has_text=...) / locator.get_by_role(...) and the other finders Narrow a locator: by position (negative counts from the end), by contained text (case-insensitive), or by searching inside its matches
page.frame_locator(iframe_selector) The same finders inside an iframe, same- or cross-origin; nest with .frame_locator()
page.evaluate(expression, arg=None) Runs JavaScript in the page and returns its JSON result, awaiting promises. A function ("(x) => x * 2") is called with arg; anything else is evaluated as is. A thrown error raises fluxwright.Error
page.screenshot(full_page=None, path=None) PNG bytes, also written to path
page.route(url, handler) / page.unroute(url, handler=None) Request interception: url is a glob (**/api/*), a compiled regex or a function of the URL; the handler gets route (and request, if it takes two arguments) and answers with route.fulfill(status, headers, body, json, path, content_type), route.continue_(url, method, headers, post_data), route.abort(error_code) or route.fallback(). The handler added last runs first
page.on("console" | "pageerror", handler) / once / remove_listener Console messages (.type, .text) and uncaught errors (fluxwright.Error with .name, .message, .stack) as they happen
page.expect_download(timeout=None) / page.wait_for_download(timeout=None) The next download the page finished: .url, .suggested_filename, path(), save_as(path). Files are deleted when the page closes
page.console_messages() / page.page_errors() What the page, its popups and iframes logged, and the exceptions nothing caught, the last 1000 of each. Fail a job with assert await page.page_errors() == []
page.set_viewport_size({"width", "height"}) CSS-pixel viewport for this page
page.wait_for_selector(selector) Waits until visible (Playwright's default state)
page.close() / browser.close() Dispose the context / shut down

Timeouts are in milliseconds, as in Playwright. Errors are fluxwright.Error, and fluxwright.TimeoutError when something ran out of time. Close pages when a job is done; a page you forget is cleaned up when Python collects it.

In the sync API, route handlers run on worker threads and event listeners one at a time on a thread of their own, so both can call the page.

Develop

pip install maturin pytest
maturin develop   # needs Rust
pytest            # against a real Chrome

License

Licensed under the Apache License, Version 2.0.

Metadata

Release files for fluxwright 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for fluxwright 0.4.0
File Size Uploaded
fluxwright-0.4.0.tar.gz 112.1 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for fluxwright 0.4.0
File
fluxwright-0.4.0-cp310-abi3-win_amd64.whl CPython 3.10 abi3 Windows x86-64 Details
fluxwright-0.4.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.10 abi3 Linux glibc 2.17+ x86-64 Details
fluxwright-0.4.0-cp310-abi3-macosx_11_0_arm64.whl CPython 3.10 abi3 macOS 11.0+ ARM64 Details
fluxwright-0.4.0-cp310-abi3-macosx_10_12_x86_64.whl CPython 3.10 abi3 macOS 10.12+ x86-64 Details

Total release size: 7.3 MB

Release files / fluxwright-0.4.0.tar.gz

Download URL fluxwright-0.4.0.tar.gz
Size 112.1 kB
Tags Source
SHA-256 checksum
How to use checksums
96606c6641c709daf970beb3886e187bf3c2da706d9f5530dccac03a0b0b48b3
BLAKE2b-256 checksum
How to use checksums
e8e685d551140990dcc771fcc86820cccb277c929f18e75ca97e9e875a374646
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.11

Release files / fluxwright-0.4.0-cp310-abi3-win_amd64.whl

Download URL fluxwright-0.4.0-cp310-abi3-win_amd64.whl
Size 1.8 MB
Tags CPython 3.10 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
ae9dbbc4ffb03a8b09cd265ce8838634aa578cace6d707dbe8e5b117b3a4f334
BLAKE2b-256 checksum
How to use checksums
e5665d548dd00261b311f37a8082cb7f653af6a2d09187c6d4c5b45f6ca7ac6b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.11

Release files / fluxwright-0.4.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL fluxwright-0.4.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 1.9 MB
Tags CPython 3.10 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
5c1eae3f0f92044bdae5d7a5873d596ccb5a5ce8f5e3f44c096cd929e3d9781c
BLAKE2b-256 checksum
How to use checksums
9474d478f0c039139dbf81ddd94d428030e38e694675400a1eb9eb336a71adac
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.11

Release files / fluxwright-0.4.0-cp310-abi3-macosx_11_0_arm64.whl

Download URL fluxwright-0.4.0-cp310-abi3-macosx_11_0_arm64.whl
Size 1.7 MB
Tags CPython 3.10 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
ac0e7adca11f3a71e1e419176dc195544a64574a7e0d50fbe7c383bef59cd633
BLAKE2b-256 checksum
How to use checksums
1b726f02367569793a534c32e885f08a46df17f9e6644fe1aa3c90f0cef95280
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.11

Release files / fluxwright-0.4.0-cp310-abi3-macosx_10_12_x86_64.whl

Download URL fluxwright-0.4.0-cp310-abi3-macosx_10_12_x86_64.whl
Size 1.7 MB
Tags CPython 3.10 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
da67dd8bd6f2ad3411f16b07eb04c5a589b036131a7bd4b805987076f61c0335
BLAKE2b-256 checksum
How to use checksums
0ef38851c23c797258b0f7cb92dc2d714529f73d63f1c5ca6c363a8666a3bc91
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.11

Release history Release notifications | RSS feed

This release

0.4.0 This release

5 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page