playwright-cdp 0.3.0

Drive Chromium directly via the Chrome DevTools Protocol (CDP) — no Playwright driver required.
Documentation

playwright-cdp

中文 | English

直接通过 Chrome DevTools Protocol (CDP) 驱动 Chromium —— 一套 Playwright 风格的 Rust API,无需 Node.js driver

Rust License: Apache-2.0

playwright-cdp 通过单条 WebSocket 原生地讲 CDP 协议。它的 API 形状镜像 Playwright (PlaywrightBrowserTypeBrowserBrowserContextPageLocator, builder 选项结构体、async/Result<T>),但去 spawn Playwright 的 Node driver 再 代理转发,而是直接拉起一个 Chromium 进程并原生驱动。

为什么做这个

playwright-rust(以及上游 Playwright) 会 shell 出一个 Node.js driver 二进制,通过自定义管道协议通信。能用,但代价是:

  • 强依赖 Node.js 运行时 + Playwright 的 node_modules;
  • 多一个 IPC 跳板和需要管理的子进程;
  • Rust crate 与已安装 driver 之间的版本耦合。

playwright-cdp 把这些都去掉了。系统里只要有 Chrome/Chromium 可执行文件,剩下全是纯 Rust 直接讲 CDP。

状态

当前为早期 0.1.x。核心 API 已实现,并对真实 Chrome 做了端到端验证(cargo test 全绿,见验证)。尚未覆盖 Playwright 的全部特性 —— 见 已实现限制

快速开始

[dependencies]
playwright-cdp = "0.1"
tokio = { version = "1", features = ["full"] }
use playwright_cdp::{Playwright, options::LaunchOptions};

#[tokio::main]
async fn main() -> playwright_cdp::Result<()> {
    // 三层入口,镜像 playwright-rust(此处不 spawn 任何 Node driver)。
    let browser = Playwright::launch().await?
        .chromium()
        .launch_with_options(LaunchOptions::default())
        .await?;

    let page = browser.new_page().await?;
    page.goto("https://example.com", None).await?;
    let title: String = page.evaluate("document.title").await?;
    println!("{title}");

    browser.close().await?;
    Ok(())
}

如果你不需要 Playwright/BrowserType 这一层,直接 Browser::launch(opts) 也可以。

定位器与交互

# use playwright_cdp::types::AriaRole;
# // page: playwright_cdp::Page
# # let page: playwright_cdp::Page = unimplemented!();
page.locator("#username").fill("alice", None).await?;
page.get_by_role(AriaRole::Button, None).click(None).await?;
let count: usize = page.locator("li").count().await?;
# playwright_cdp::Result::<()>::Ok(())

网络拦截

# use playwright_cdp::route::RouteFulfillOptions;
# # let page: playwright_cdp::Page = unimplemented!();
page.route("**/api/user", |route| async move {
    route.fulfill(
        RouteFulfillOptions::new()
            .status(200)
            .content_type("application/json")
            .body(r#"{"name":"alice"}"#),
    ).await.ok();
}).await?;
# playwright_cdp::Result::<()>::Ok(())

已实现

以下全部是基于 CDP 的真实实现(非桩函数):

领域 类型 / 能力
入口 PlaywrightBrowserType(chromium/firefox/webkit)、Browser::launch / connect_over_cdp
上下文 BrowserContext —— 页面、cookies、额外 headers、add_init_scriptset_default_timeout / set_default_navigation_timeoutgrant_permissions / clear_permissionsstorage_state(cookies + localStorage)/ set_storage_state、上下文级 route
页面 / 帧 PageFrameFrameLocator —— goto/reload/go_back/go_forward/wait_for_url/wait_for_load_stateevaluate/eval_on_selector/wait_for_functionquery_selector(_all)screenshotpdfset_viewport_sizeemulate_mediaset_offlineset_cache_disabledbring_to_frontset_geolocationdrag_and_dropexpose_function
定位器 Locator —— click/dblclick/fill/clear/check/uncheck/set_checked/press/hover/tap/drag_to/select_option/set_input_files/dispatch_event/scroll_into_view_if_needed,文本/属性/状态查询,nth/first/last/all/filter/and_/or_evaluate(_all)bounding_boxscreenshotwait_foraria_snapshot
元素与输入 ElementHandleKeyboardMouseTouchscreen
网络 RequestResponse(status/headers/body/text/json)、Route(continue_/fulfill/abort/fallback)、on_request/on_response/on_requestfinished/on_requestfailed、基于在途请求计数的 networkidle
事件 on_consoleon_dialog(+ Dialog)、on_pageerroron_download(+ Download)、on_filechooser(+ FileChooser)、on_worker(+ Worker)、on_pageon_close
诊断 Tracing(经 Tracing 域采集 Chrome trace)、aria_snapshot(无障碍树 → Playwright YAML)、ConsoleMessage
API 测试 APIRequestContext / APIResponse —— 基于 reqwest 的独立 HTTP 客户端(page.request()context.request())
断言 expect(locator) / expect_page(page) —— to_be_visible/hidden/enabled/disabled/editable/checkedto_have_text/contain_text/inner_textto_have_count/attribute/valueto_have_title/url,支持 .not().with_timeout(...)

选择器引擎(text=xpath=role=、CSS、>> 链式)是一个按执行上下文注入的小脚本; 同源 <iframe> 遍历可通过 FrameLocator 完成。

示例

examples/ 下有十个可运行示例,每个都是针对真实浏览器的端到端演示:

示例 演示内容
demo 启动、导航、求值、定位器点击、fill、截图
interactions click/fill/check/select/dblclick/hover/press + keyboard/mouse/touchscreen
assertions expect / expect_page 断言
network 请求/响应捕获、route fulfill/abort
api_request 独立的 page.request() HTTP 客户端
frames FrameLocator + 帧树
page_capture 截图、PDF、aria_snapshot
dialogs_downloads on_dialogon_download
contexts 上下文隔离、cookies、storage_state 往返
bindings expose_functionwait_for_function
cargo run --example demo
cargo run --example network
cargo run --example contexts

验证

cargo build --all-targets   # 零 warning
cargo test                  # 50+ 集成 + 单元测试(找不到 Chrome 时自动跳过)
cargo run --example demo

需要浏览器的测试在找不到 Chrome/Chromium 时会优雅跳过(可用 CHROME_PATH 或在 LaunchOptions 里指定 executable_path 来固定一个)。

工作原理

你的应用 ──► playwright-cdp (Rust)
                 │  拉起并拥有 Chrome 进程(--remote-debugging-port)
                 │  打开一条 ws:// 连接(flatten,多路复用)
                 └──► Chrome DevTools Protocol(Target / Page / Runtime / Network
                      / DOM / Input / Fetch / Emulation / Accessibility / Tracing …)
  • BrowserProcess 发现 Chrome 可执行文件(executable_pathCHROME_PATH、 常见安装路径、channel),以 --remote-debugging-port=0 启动,读取 DevToolsActivePort,解析出浏览器 WebSocket 端点。
  • CdpConnection 在单条 socket 上多路复用请求/响应配对与事件分发;flatten 的 auto-attach 让弹窗/worker 复用同一条连接。
  • 页面级求值走默认(主世界)的 Runtime.evaluate,这样导航后不会因缓存的 执行上下文 id 失效而出错。

限制

  • 仅支持 Chromium。 Firefox/WebKit 返回的是一个 Chromium 后端的 BrowserType 并打印警告(CDP 是 Chromium 专有协议)。
  • FrameLocator 仅支持同源(跨源 iframe 无法从父框架主世界访问)。
  • 对 headless Chrome 150 的怪行为做了防御式处理:例如 FileChooserDOM.setFileInputFiles(该版本的 Page.handleFileChooser 不被支持); Download 在收不到 Completed 进度事件时回退到文件系统轮询。
  • 尚未实现:Selectors.register(自定义选择器引擎)、popup-as-Page (opener/on_popup)、recordVideo、旧的 page.accessibility.snapshot() JSON API。aria_snapshot 力求贴近 Playwright 格式,但不保证边缘情况字节级一致。

许可证

Apache-2.0,见 LICENSE