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)
| File | Size | Uploaded | |
|---|---|---|---|
| fluxwright-0.4.0.tar.gz | 112.1 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| 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
|