Run hundreds of Chrome jobs on one machine.
Fluxwright pools Chrome, gives every job a fresh isolated page, queues work when the fleet is full, restarts browsers before they bloat, and retries when one crashes. Use it from Node, from Rust, or from an AI agent over MCP.
npm install fluxwrightSimulated fleet
- Jobs done
- 1,284
- Browsers recycled
- 3
- Crashes retried
- 1
What it takes care of
Libraries like Playwright and Puppeteer drive one browser well. Running many at once needs a control plane: how many Chromes, when to refuse work, when to restart one, what to do when a renderer dies. That is Fluxwright.
- A fresh page per job
- Every job gets its own browser context on a pooled Chrome. Cookies, storage and cache never leak into the next job.
browser.newPage()- A queue with priorities
- When every slot is busy, jobs wait in a priority queue, or fail fast if you would rather shed load.
QueueFullMode::Wait- A memory ceiling
- No new work starts while the fleet's real memory footprint is over your budget. Shared pages are not counted twice.
memory_ceiling_mb- Recycling
- A browser restarts after a number of jobs, an age, or a memory threshold. It drains its running jobs first.
recycle_after_jobs- Crash recovery
- A dead browser fails its jobs at once and they retry on a healthy one. Chrome is tied to the engine, so it never outlives a crash.
JobOptions::retries- A Playwright-style API
- getByRole, getByLabel, getByTestId, nth and filter, frameLocator for cross-origin iframes, page.route to fake or change requests, downloads, and clicks that wait until the element can take them.
page.getByRole()- Settings per job
- Each job can have its own proxy, user agent, locale, timezone and location, and start from a saved login, on the same browser as jobs that use none.
newPage({ storageState })- Built for agents
- An MCP server lets Claude, Codex or Cursor drive the fleet on your machine.
fluxwright-mcp
How a job runs
The same five steps whether a job comes from Node, Rust or an agent.
Submit
Your code asks for a page with browser.newPage(), or hands a job to engine.run().
Admit
If every slot is busy or memory is over the ceiling, the job waits in the queue by priority.
Lease
The job gets a fresh context on the least busy Chrome. Nothing is shared with other jobs.
Run
Your code drives the page: navigation, locators, evaluate, screenshots.
Release
Closing the page destroys the context. A browser that reached its limits drains and restarts.
The code
A small API on purpose: pages you lease, locators that wait, and an engine that handles the fleet. Node gets a native addon, so there is no driver process in between.
The MCP server exposes open, goto, click, fill, evaluate, screenshot and close. Selectors accept CSS, text= and role=.
import { chromium } from "fluxwright";
const browser = await chromium.launch({ maxBrowsers: 4 });
// A fresh browser context on a pooled Chrome.
const page = await browser.newPage();
await page.goto("https://example.com", { waitUntil: "domcontentloaded" });
console.log(await page.title());
// Locators wait until the element can be used.
await page.getByRole("button", { name: "Accept" }).click();
await page.getByRole("textbox", { name: "Search" }).fill("chromium");
// Cross-origin iframes work the same way.
const payment = page.frameLocator("iframe#payment");
const card = payment.getByRole("textbox", { name: "Card number" });
await card.fill("4242 4242 4242 4242");
await page.close(); // the context is destroyed and the slot is free
await browser.close();use fluxwright::{BrowserEngine, JobOptions, Priority};
let engine = BrowserEngine::builder()
.max_browsers(8)
.max_contexts_per_browser(10)
.memory_ceiling_mb(8192) // admit no new jobs above this footprint
.recycle_after_jobs(200) // restart each Chrome after 200 jobs
.build()
.await?;
// run() queues, leases, retries after a crash, and always releases the page.
let job = JobOptions::default().priority(Priority::High).retries(2);
let title = engine
.run(job, |page| async move {
page.goto("https://example.com").await?;
page.title().await
})
.await?;{
"mcpServers": {
"fluxwright": { "command": "fluxwright-mcp" }
}
}Benchmarks
Jobs per second, higher is better. Both tools ran on the same chrome-headless-shell binary, alternating runs, with 10 jobs in flight loading a small local page.
| Setup | Fluxwright | Playwright |
|---|---|---|
| 5 browsers, 2 pages each | 66–70 | 45–51 |
| 1 browser, 10 pages | 54–58 | 42–46 |
One Windows machine, 30 September 2026. The first Fluxwright run of each series ran cold and slower (30 jobs/s) and is left out of the ranges. On Chrome's regular headless mode the two were level, at about 13 jobs/s. One machine and one page shape say little about yours, so measure your own workload with cargo run --release -p fluxwright-benchmarks.
Install
You need Chrome or Chromium on the machine. Headless runs use chrome-headless-shell when it is installed, which opened pages 5 to 10 times faster in our runs.
npx @puppeteer/browsers install chrome-headless-shell@stablenpm install fluxwrightPrebuilt for Windows x64, macOS on Apple silicon and Intel, and Linux x64 (glibc). Node 18 or later.
[dependencies]
fluxwright = { git = "https://github.com/harshit-d3v/fluxwright" }
tokio = { version = "1", features = ["full"] }Rust 1.85 or later.
cargo install --git https://github.com/harshit-d3v/fluxwright fluxwright-mcpAdd it to Claude Desktop, Codex or Cursor as shown in the code section. Use the full path to fluxwright-mcp if it is not on your PATH. The fleet runs on your machine, so serverless hosts cannot run it.
cargo install --git https://github.com/harshit-d3v/fluxwright fluxwright-cli
fluxwright doctor
fluxwright start --max-browsers 4 --max-contexts 8
fluxwright statsThe daemon listens on a Unix socket, or a named pipe on Windows. Never on TCP.
Know before you choose it
What it does not do
- Firefox or WebKit. Fluxwright is Chromium only.
- Test running: no fixtures, assertions, tracing, HAR or video. Bring your own test framework.
- More than one page per context, downloads and uploads.
- locator.filter, nth and getByLabel. getByRole, getByText and frameLocator are supported.
- Attaching to a browser that is already running. Planned.
Safe by default
- Chrome's sandbox stays on unless you set FLUXWRIGHT_NO_SANDBOX=1.
- On Linux and macOS the engine talks to Chrome over a private pipe, not a debugging port.
- Jobs never share cookies or storage.
- Chrome exits when the engine exits, even after a crash.