Rustenium
The most robust, high-performance multi-protocol browser automation library for Rust.
Rustenium provides a powerful and ergonomic API for browser automation using the WebDriver BiDi and Chrome DevTools Protocol (CDP). It offers both low-level control and high-level abstractions for common automation tasks, with the ability to use BiDi and CDP independently or together.
Features
- Dual Protocol Support: Full WebDriver BiDi and Chrome DevTools Protocol (CDP) support
- Multi-Browser: Chrome and Firefox support out of the box
- Flexible Launch Modes: Manage the browser yourself, connect to an existing instance, or let the driver handle it
- Optional Protocols: Enable BiDi, CDP, or both — connect and disconnect at runtime
- Auto-Download: Automatically downloads Chrome, chromedriver, and Firefox if not present
- Flexible Input Methods:
- BidiMouse: Direct, precise mouse movements for fast automation
- HumanMouse: Realistic mouse movements with Bezier curves and jitter to mimic human behavior
- Keyboard: Full keyboard support with modifier keys
- Touchscreen: Multi-touch gesture support for mobile testing
- CSS & XPath Selectors: Convenient macros (
css!(), xpath!()) for element location
- Screenshot Capture: Take screenshots of elements or entire pages
- Network Interception: Monitor and intercept network requests with BiDi
- Event System: Subscribe to browser events in real-time
- Script Evaluation: Execute JavaScript with preload script support
- Timezone Emulation: Emulate different timezones for testing
- Device Emulation: Emulate device metrics via CDP for responsive testing
- Tab Management: Create and manage browser tabs via CDP
- Type-Safe API: Leverages Rust's type system for compile-time safety
Installation
Add Rustenium to your Cargo.toml:
[dependencies]
rustenium = { version = "1.0.1", features = ["macros"] }
tokio = { version = "1", features = ["full"] }
Browser Support
Chrome
Chrome uses chromedriver for BiDi and connects directly for CDP. Both protocols can be used independently or together.
use rustenium::browsers::{chrome, ChromeConfig, ChromeLaunchMode};
let mut browser = chrome(None).await;
let config = ChromeConfig {
launch_mode: ChromeLaunchMode::DriverManaged,
..Default::default()
};
let mut browser = chrome(Some(config)).await;
let config = ChromeConfig {
launch_mode: ChromeLaunchMode::Remote(9222),
..Default::default()
};
let mut browser = chrome(Some(config)).await;
Firefox
Firefox has built-in WebDriver BiDi support — no separate driver needed. Rustenium connects directly to Firefox's BiDi WebSocket.
use rustenium::browsers::{firefox, FirefoxConfig, FirefoxLaunchMode};
let mut browser = firefox(None).await;
let config = FirefoxConfig {
remote_debugging_port: Some(9222),
browser_flags: Some(vec!["--headless".to_string()]),
..Default::default()
};
let mut browser = firefox(Some(config)).await;
let config = FirefoxConfig {
launch_mode: FirefoxLaunchMode::Remote(9222),
..Default::default()
};
let mut browser = firefox(Some(config)).await;
Protocol Selection (Chrome)
BiDi is enabled by default. CDP is opt-in. You can use them independently or together,
though note that CDP can become buggy in the presence of an active BiDi connection.
Connecting BiDi after CDP does not affect the CDP setup.
use rustenium::browsers::ChromeConfig;
let config = ChromeConfig::default();
let config = ChromeConfig {
enable_bidi: false,
enable_cdp: true,
..Default::default()
};
let config = ChromeConfig {
enable_bidi: true,
enable_cdp: true,
..Default::default()
};
You can also connect protocols at runtime:
use rustenium::browsers::{ChromeBrowser, ChromeConfig};
let mut browser = ChromeBrowser::new(ChromeConfig {
enable_bidi: false,
enable_cdp: true,
..Default::default()
}).await;
browser.connect_bidi().await;
Quick Start
use rustenium::browsers::chrome;
use rustenium_macros::css;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut browser = chrome(None).await;
browser.navigate("https://example.com").await?;
let search_box = browser.find_node(css!("input[type='search']")).await?.expect("No node found");
search_box.screenshot().await?;
let mut submit_button = browser.find_node(css!("button[type='submit']")).await?.expect("No node found");
submit_button.mouse_click().await?;
browser.screenshot().await?;
browser.close().await?;
Ok(())
}
Examples
Browser Setup and Navigation (BiDi)
use rustenium::browsers::{chrome, ChromeConfig, NavigateOptionsBuilder};
use rustenium_bidi_definitions::browsing_context::types::ReadinessState;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut config = ChromeConfig::default();
config.capabilities.add_arg("--disable-gpu")
.add_args(["--window-size=1920,1080"])
.accept_insecure_certs(true);
let mut browser = chrome(Some(config)).await;
browser.navigate_with_options("https://example.com",
NavigateOptionsBuilder::default()
.wait(ReadinessState::Interactive)
.build()
).await?;
browser.close().await?;
Ok(())
}
CDP Navigation and Tab Management
use rustenium::browsers::{ChromeConfig, ChromeBrowser};
use rustenium::browsers::cdp_browser::CdpBrowser;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut browser = ChromeBrowser::new(ChromeConfig {
enable_bidi: false,
enable_cdp: true,
..Default::default()
}).await;
CdpBrowser::navigate(&mut browser, "https://example.com").await?;
CdpBrowser::create_tab(&mut browser, "https://example.org").await?;
CdpBrowser::emulate_device_metrics(&mut browser, 375, 812, 3.0, true).await?;
Ok(())
}
Finding Elements
use rustenium::browsers::chrome;
use rustenium_macros::{css, xpath};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut browser = chrome(None).await;
browser.navigate("https://example.com").await?;
let element = browser.find_node(css!("#my-id")).await?;
let buttons = browser.find_nodes(css!(".btn-primary")).await?;
let headers = browser.find_nodes(xpath!("//h1[@class='title']")).await?;
let node = browser.wait_for_node(css!(".dynamic-content")).await?;
browser.close().await?;
Ok(())
}
Mouse Input — Precise Movements
use rustenium::browsers::chrome;
use rustenium::input::{MouseMoveOptions, MouseClickOptions, Point};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut browser = chrome(None).await;
let context_id = browser.get_active_context_id()?;
browser.mouse().move_to(Point { x: 100.0, y: 200.0 }, &context_id, MouseMoveOptions {
steps: Some(5),
..Default::default()
}).await?;
browser.mouse().click(None, &context_id, MouseClickOptions::default()).await?;
browser.close().await?;
Ok(())
}
Mouse Input — Human-Like Movements
use rustenium::browsers::chrome;
use rustenium::input::{MouseMoveOptions, MouseClickOptions, Point};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut browser = chrome(None).await;
let context_id = browser.get_active_context_id()?;
browser.human_mouse().move_to(Point { x: 100.0, y: 200.0 }, &context_id, MouseMoveOptions::default()).await?;
browser.human_mouse().click(None, &context_id, MouseClickOptions::default()).await?;
browser.close().await?;
Ok(())
}
Keyboard Input
use rustenium::browsers::chrome;
use rustenium::input::KeyboardTypeOptions;
use rustenium_macros::css;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut browser = chrome(None).await;
let context_id = browser.get_active_context_id()?;
browser.navigate("https://example.com").await?;
let text = browser.wait_for_node(css!("#text")).await?.expect("No node exists");
browser.keyboard().type_text(
&text.get_inner_text().await,
&context_id,
Some(KeyboardTypeOptions { delay: Some(36) }),
).await?;
browser.keyboard().down("Control", &context_id).await?;
browser.keyboard().press("a", &context_id, None).await?;
browser.keyboard().up("Control", &context_id).await?;
browser.close().await?;
Ok(())
}
Network Interception
use rustenium::browsers::chrome;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut browser = chrome(None).await;
browser.on_request(|request| async move {
println!("Request URL: {}", request.url());
if request.url().contains("ads.example.com") {
let _ = request.abort().await;
return;
}
request.continue_().await;
}).await?;
browser.authenticate("username", "password").await?;
browser.navigate("https://example.com").await?;
browser.close().await?;
Ok(())
}
Script Evaluation & Preload Scripts
use rustenium::browsers::chrome;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut browser = chrome(None).await;
let result = browser.evaluate_script(
"document.title".to_string(),
true,
).await?;
let script_id = browser.add_preload_script(
"() => { window.__injected = true; }".to_string(),
).await?;
browser.remove_preload_script(script_id).await?;
browser.close().await?;
Ok(())
}
Timezone Emulation
use rustenium::browsers::chrome;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut browser = chrome(None).await;
browser.emulate_timezone(Some("Asia/Tokyo".to_string())).await?;
browser.navigate("https://example.com").await?;
browser.close().await?;
Ok(())
}
Firefox Example
use rustenium::browsers::firefox;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut browser = firefox(None).await;
browser.navigate("https://example.com").await?;
let nodes = browser.find_nodes(css!("h1")).await?;
browser.screenshot().await?;
browser.close().await?;
Ok(())
}
Crate Structure
| Crate |
Description |
rustenium |
Main library — browser impls, input devices, node interactions |
rustenium-core |
Protocol transport, sessions, connections, event system |
rustenium-bidi-definitions |
WebDriver BiDi protocol type definitions |
rustenium-cdp-definitions |
Chrome DevTools Protocol type definitions |
rustenium-macros |
Procedural macros (css!, xpath!) |
rustenium-generator |
Code generator for protocol definitions from specs |
Browser Support
| Browser |
BiDi |
CDP |
Auto-Download |
| Chrome/Chromium |
Yes |
Yes |
Yes |
| Firefox |
Yes |
No |
Yes |
Requirements
- Rust 1.85 or later (2024 edition)
- Chrome or Firefox (auto-downloaded if not present)
License
This project is licensed under the MIT License - see the LICENSE file for details.
Acknowledgements
- HumanCursor — the human-like mouse trajectory algorithm (Bezier curves, easing, Gaussian distortion) is ported from this excellent Python library
- Chromium Oxide — inspiration for the protocol definition code generator
- The Ish Bot — some bot automation inspiration was borrowed from this project
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.