#web-driver #chrome #cdp #browser-automation

rust_drission

Browser automation library for Rust via Chrome DevTools Protocol (CDP). Connect or launch Chrome, control pages/elements, run JS, cookies, screenshots, request/response listening. API inspired by DrissionPage.

15 releases

new 0.2.5 May 11, 2026
0.2.4 May 9, 2026
0.1.9 Apr 29, 2026
0.1.3 Mar 19, 2026

#485 in Hardware support

MIT license

215KB
4.5K SLoC

rust_drission

rust_drission is a Rust browser automation library built on Chrome DevTools Protocol (CDP). Its public API is intentionally close to DrissionPage, and the recommended entry point for most usage is ChromiumPage.

rust_drission 是一个基于 Chrome DevTools Protocol (CDP) 的 Rust 浏览器自动化库。它的公开 API 尽量贴近 DrissionPage,大多数场景建议直接从 ChromiumPage 开始。

Why ChromiumPage

  • One entry point for browser launch, navigation, element lookup, JS execution, screenshots, cookies, and tab access
  • DrissionPage-style locator strings such as css:, xpath:, text:, id:, class:, tag:
  • Supports direct JS/CDP calls when the high-level API is not enough
  • Keeps advanced capabilities available through page.tab() and page.browser()
  • Built-in network traffic listening with URL/resource-type filtering and batch collection
  • Built-in stealth injection by default for the initial tab and new_tab()

Installation

[dependencies]
rust_drission = "0.2"

Quick Start

use rust_drission::{BrowserConfig, ChromiumPage, CdpError};

fn main() -> Result<(), CdpError> {
    let page = ChromiumPage::new(
        BrowserConfig::new()
            .headless(false)
            .set_local_port(9222),
    )?;

    page.get("https://example.com")?;
    println!("title = {}", page.title()?);
    println!("url = {}", page.url()?);

    if let Some(h1) = page.ele("css:h1")? {
        println!("h1 = {}", h1.text()?);
    }

    page.screenshot("example.png")?;
    Ok(())
}

Common Patterns

Launch a new Chrome

ChromiumPage::new(...) starts a fresh browser instance and injects the built-in stealth script into the initial tab.

use rust_drission::{BrowserConfig, ChromiumPage};

let page = ChromiumPage::new(
    BrowserConfig::new()
        .chrome_path("C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe")
        .user_data_dir("./data/profile")
        .set_local_port(9222)
        .headless(false),
)?;

If you want to skip stealth injection for the initial tab, use ChromiumPage::new_without_stealth(...).

Connect to an existing Chrome

Chrome must already be started with remote debugging enabled, for example:

chrome --remote-debugging-port=9222

Then connect:

use rust_drission::ChromiumPage;

let page = ChromiumPage::connect("127.0.0.1:9222")?;

Use connect(...) only when you want to attach to an already-running browser instead of launching a fresh one.

Locate and operate on elements

page.input("css:input[name='q']", "rust cdp")?;
page.click("text:Search")?;

if let Some(button) = page.ele("id:submit")? {
    println!("enabled = {}", button.is_enabled()?);
}

Listen to network traffic

use std::time::Duration;

// Start listener first (blocks until ready), then navigate
let listener = page.listen()?;
page.get("https://example.com")?;

// Receive all packets
while let Some(pkt) = listener.wait(Duration::from_secs(5))? {
    println!("{} {}{}", pkt.request.method, pkt.request.url, pkt.response.status.unwrap_or(0));
}

// Or filter by URL keyword
let url_listener = page.listen_url("api")?;
page.get("https://example.com")?;
if let Some(pkt) = url_listener.wait(Duration::from_secs(10))? {
    println!("API response: {:?}", pkt.body);
}

// Or filter by resource type (XHR, Fetch, Document, Script, etc.)
let fetch_listener = page.listen_resource_type("Fetch")?;

// Or batch collect after navigation
let listener = page.listen()?;
page.get("https://example.com")?;
let packets = page.listen_collect(&listener, Duration::from_secs(5), |pkt| true)?;
println!("collected {} packets", packets.len());

Run JavaScript

let result = page.run_js("document.title")?;
let async_result = page.run_js_await("fetch('https://httpbin.org/get').then(r => r.json())")?;

Use advanced page APIs

use std::time::Duration;

// wait() is on ChromiumPage; all other wait_* methods are on Page (via tab())
if let Ok(el) = page.wait(".item-list", Duration::from_secs(5)) {
    println!("loaded: {}", el.text()?);
}

let tab = page.tab();
tab.wait_visible("css:.result", Duration::from_secs(10))?;
tab.wait_hidden("#spinner", Duration::from_secs(5))?;
tab.wait_network_idle()?;
tab.set_local_storage("token", "demo-value")?;

Documentation

Notes

  • ChromiumPage is the recommended starting point. Use page.tab() or page.browser() only when you need lower-level control.
  • Frame support is aimed at same-origin iframes.
  • listen() is available directly on ChromiumPage, as well as listen_url(), listen_resource_type(), and listen_collect().
  • Always start the listener before navigating, so no network events are missed: let l = page.listen()?; page.get("...")?;.
  • Element lookup methods often return Result<Option<Element>, CdpError>. Handle the None case explicitly.

Changelog

v0.2.5

  • Make ChromiumPage::new(...) always launch a fresh browser instance instead of attaching to an existing remote-debugging session.
  • Inject the built-in stealth script into tabs created by ChromiumPage::new_tab(...), and add new_without_stealth(...) / new_tab_without_stealth(...) for callers that need a clean tab.
  • Add Page::listen_url(...) and Page::listen_resource_type(...) helpers alongside the existing ChromiumPage listener filters.

v0.2.4

  • Improve stealth injection defaults on newly created ChromiumPage instances: suppress common console-based anti-debug hooks, mask navigator.webdriver, block location.replace("about:blank"), and filter suspicious setInterval debugger loops.
  • Add a simple Boss Zhipin example at examples/boss.rs for opening the candidate list page with a persistent user_data_dir.

v0.2.3

  • Fix: XPath and Text locators now query the live DOM via Runtime.evaluate instead of querying CDP's stale internal DOM snapshot (DOM.performSearch). This fixes page.wait() timing out for dynamically-created elements when using XPath / Text locators.
  • Fix: preserve objectId when constructing Element from XPath evaluation results, so that tag() and text() return correct values instead of empty strings.

v0.2.2

  • Fix: when a custom user_data_dir is specified via BrowserConfig::user_data_dir(), the directory is now auto-created before Chrome starts. Previously it was only created for auto-generated temp paths, which could cause Chrome to show a "Cannot perform read/write operations" system dialog on Windows when the directory didn't exist.

v0.2.1

  • Fix: clean up Chrome Singleton lock files (SingletonLock, SingletonCookie, SingletonSocket) in user_data_dir before browser launch, preventing startup failures when a previous browser instance crashed or was killed unexpectedly

v0.2.0

  • All error messages changed from Chinese to English for better internationalization
  • macOS: auto-detect Chrome, Chrome Canary, Chromium, Edge, and Brave browser paths
  • Fixed element lookup fallback logic to avoid false errors when main scan returns no results
  • Added 5 runnable API test examples (navigation, element actions, element queries, browser tabs)
  • Cross-platform example paths (no longer Windows-only)

Dependencies

~4–16MB
~147K SLoC