I built a pure-Rust browser automation library, no Node.js, no wrappers, just CDP over Tokio
Source: Dev.to
⚠️ Collection Error: Content refinement error: Error: 429 “you (bkperio) have reached your weekly usage limit, upgrade for higher limits: https://ollama.com/upgrade (ref: c0fc44fb-1bd2-49e1-ad97-f85875fb0e4b)”
I got tired of every Rust browser automation library either being a thin wrapper around Node.js (slow, heavy) or completely unmaintained and archived. So I built ferrous-browser a pure-Rust, async-first Chrome DevTools Protocol client that ships as a single binary. Zero Node.js. It talks directly to Chrome over CDP using Tokio WebSockets. No npm, no subprocess bridges, nothing. Race-condition-free event handling. Event handlers are registered before the commands that trigger them, not after. Sounds obvious, but most implementations don’t do this. A Playwright-inspired API. locator(), evaluate(), WaitUntil — familiar if you’ve used Playwright or Puppeteer, but idiomatic Rust. ferrous-browser = “0.1” tokio = { version = “1”, features = [“full”] }
Requires Chrome or Chromium installed locally. That’s it. use ferrous_browser::{Browser, WaitUntil}; #[tokio::main] async fn main() -> Result> { let browser = Browser::launch_chrome(None).await?; let page = browser.new_page().await?; page.goto(“https://example.com”, WaitUntil::Load).await?; let heading = page.locator(“h1”).inner_text().await?; println!(“Heading: {heading}”);
let png = page.screenshot().await?; std::fs::write(“screenshot.png”, png)?;
Ok(()) }
The locator API covers click, type_text, wait_for, inner_text, and get_attribute. evaluate() is generic — you pass a JS expression and it deserializes into whatever Rust type you specify. match page.goto(“https://bad-url”, WaitUntil::Load).await { Err(BrowserError::NavigationFailed { url, reason }) => eprintln!(“Navigation to {url} failed: {reason}”), Err(BrowserError::Timeout { operation, secs }) => eprintln!(“{operation} timed out after {secs}s”), Err(e) => eprintln!(“{e}”), Ok(_) => {} }
You can also chain context onto any Result with .context(“loading homepage”)?. The benchmarks are honest: raw page creation is slower than Puppeteer right now (Chrome’s session routing is heavily optimized on their end), but the gap closes fast when the workload is real scraping or E2E testing rather than micro-benchmarks. What’s on the roadmap: cookie management, PDF export, evaluate_handle for remote object references, HAR/trace capture, and full Windows support. Would love feedback, especially from anyone who’s hit the multi-page session isolation bugs in other libraries, that was the main itch I was scratching. GitHub: https://github.com/theoxfaber/ferrous-browser https://crates.io/crates/ferrous-browser