a13n
Build AI agents and run them on cloud.
Pooling, admission control, recycling and crash recovery for headless Chrome, with a Playwright-style API.
Developer toolsRust · PythonGitHub @harshit-d3vOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Rust Chromium fleet engine: pooling, admission, recycle, crash recovery
Translation pending
Taken from the repository README
Loading the full README.
Read the full README ↗Run hundreds of headless Chrome jobs on one machine. Fluxwright pools browsers, queues jobs when every slot is busy, restarts browsers before they bloat, and retries jobs when a browser crashes, behind a Playwright-style API for Node, Python and Rust.
It is not a Playwright rewrite. Playwright drives a browser; Fluxwright runs a fleet of them.
Docs: https://fluxwright.vercel.app · Roadmap: ROADMAP.md
npm install fluxwright
import { chromium } from "fluxwright";
const browser = await chromium.launch({ maxBrowsers: 4 });
const page = await browser.newPage(); // a fresh context on a pooled Chrome
await page.goto("https://example.com");
console.log(await page.getByRole("heading").textContent()); // "Example Domain"
console.log(await page.evaluate(() => location.hostname));
await page.close(); // the context is destroyed and the slot is free again
await browser.close();
Fluxwright uses the Chrome on your machine. For headless jobs, install chrome-headless-shell: pages open 5-10x faster than on Chrome's new headless mode in our runs.
npx @puppeteer/browsers install chrome-headless-shell@stable
pip install fluxwright
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"
await browser.close()
asyncio.run(main())
Blocking code uses fluxwright.sync_api and sync_playwright() the same way. The names are Playwright's, so a Playwright script for these features runs after changing its import.
| Area | API |
|---|---|
| Navigate | goto(url, { waitUntil }) with load (default), domcontentloaded, networkidle or commit, event-driven like Playwright |
| Find | getByRole(role, { name }), getByText, getByLabel, getByPlaceholder, getByTestId, locator(css | "text=…" | "role=…"), frameLocator(iframe) for same- and cross-origin iframes. Narrow with nth(i), first(), last(), filter({ hasText }) and chained getBy… calls |
| Act | click() and fill() wait until the element is visible, enabled, stable and not covered; waitFor(), waitForSelector() |
| Read | title(), content(), textContent(), evaluate(fn, arg) or evaluate("expression"), locator.evaluate((el, arg) => …), locator.boundingBox() |
| Capture | screenshot({ fullPage, path }), locator.screenshot({ path }) (also inside scrolling containers and iframes), setViewportSize({ width, height }) |
| Errors | consoleMessages() and pageErrors() from the page, its popups and iframes, so a job can fail on JavaScript errors; page.on('console' | 'pageerror', handler) as they happen |
| Requests | page.route(url, handler) to fulfill, change or abort requests (route.fulfill({ json }), route.continue({ headers }), route.abort(), route.fallback()), with globs, RegExps or functions |
| Downloads | page.waitForDownload() then download.saveAs(path); each job's files stay in its own folder until the page closes |
| Emulate | newPage({ userAgent, locale, timezoneId, geolocation, permissions, viewport, deviceScaleFactor, colorScheme }), Playwright's option names |
| Sessions | page.storageState({ path }) saves cookies and localStorage; starts from them, so you log in once. Playwright's file format |
The full Node API is in bindings/node/README.md and the Python API, with the same features under Playwright's Python names (get_by_role, wait_until, storage_state, ...), in bindings/python/README.md.
expect, fixtures, a trace viewer and a recorder. Fluxwright is an engine, not a test framework.Use Fluxwright when you run many browser jobs, such as scraping, crawling, PDF and screenshot rendering, or automation at volume, and want the fleet handled for you.
Measured on GitHub's ubuntu-latest runner (three runs, 2026-10-27): every tool on the same chrome-headless-shell 156 binary, loading a small local page with the same number of jobs in flight. With 1 browser and 10 pages, Fluxwright ran 34–40 jobs/s, Playwright 27–34 and Puppeteer 27–36; with 1,000 jobs on 4 browsers, 36–44, 30–38 and 34–46. No job failed. A shared cloud machine varies by up to a third from run to run, so rerun it (Actions → benchmarks) and measure your own workload with cargo run --release -p fluxwright-benchmarks (results go to benchmarks/results/).
[dependencies]
fluxwright = { git = "https://github.com/harshit-d3v/fluxwright" }
use fluxwright::{BrowserEngine, JobOptions, Priority};
#[tokio::main]
async fn main() -> fluxwright::Result<()> {
let engine = BrowserEngine::builder()
.max_browsers(50)
.max_contexts_per_browser(10)
.memory_ceiling_mb(8192)
.build()
.await?;
let page = engine.acquire().await?;
page.goto("https://example.com").await?;
println!("{}", page.title().await?);
drop(page); // context destroyed, slot returned
// run() queues, leases, retries after a crash, and always releases the page.
let title = engine
.run(JobOptions::default().priority(Priority::High).retries(2), |page| async move {
page.goto("https://example.com").await?;
page.title().await
})
.await?;
println!("{title}");
Ok(())
}
Requires Rust 1.95+ and Chrome or Chromium (found on the standard install paths, or set FLUXWRIGHT_CHROMIUM, CHROME or CHROMIUM). --no-sandbox is off unless you set FLUXWRIGHT_NO_SANDBOX=1 (logs a warning).
| Crate | Role |
|---|---|
fluxwright-cdp | One connection per browser (DevTools pipe on Linux/macOS, WebSocket on Windows), flat sessions |
fluxwright-core | Pool, scheduler, memory ceiling, recycling, metrics |
fluxwright | Public API |
fluxwright-cli | fluxwright start / stats / browsers / doctor / benchmark |
fluxwright-mcp | MCP server: open, goto, click, fill, evaluate, screenshot, close |
cargo install --git https://github.com/harshit-d3v/fluxwright fluxwright-mcp
Then add { "mcpServers": { "fluxwright": { "command": "fluxwright-mcp" } } } to your client's configuration (Windows: install-mcp.bat).
cargo run -p fluxwright-cli -- start
cargo run -p fluxwright-cli -- stats
cargo run -p fluxwright-cli -- browsers
cargo run -p fluxwright-cli -- doctor
The control channel is a Unix socket, or \\.\pipe\fluxwright on Windows. Nothing listens on TCP.
cargo run --release -p fluxwright-benchmarks
# two-hour soak (memory over time): cargo run --release -p fluxwright-benchmarks -- --soak
The harness talks to benchmark-server (localhost only). Playwright and Puppeteer adapters run if their Node packages are installed.
cd www && npm install && npm run dev (http://localhost:3456). Vercel deploys www/ from main.cd bindings/node && npm install && npm run build && npm test.cd bindings/python && pip install maturin pytest && maturin develop && pytest (in a virtualenv).cargo test --workspace -- --test-threads=1.docs/COMPETITIVE_ANALYSIS.md, docs/CDP_DECISION.md, docs/DECISIONS.md.Licensed under the Apache License, Version 2.0. Versions up to 0.2.0 were released under the MIT license.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project shall be licensed under the Apache License, Version 2.0, without any additional terms or conditions.
newPage({ storageState })| Network | a proxy per page, with a username and password; blocking by resource type or URL (Rust JobOptions) |