Skip to main content

Crate playwright_rs

Crate playwright_rs 

Source
Expand description

playwright: High-level Rust bindings for Microsoft Playwright

This crate provides the public API for browser automation using Playwright.

§Quick tour

§Object model

Playwright            start here — Playwright::launch().await?
  └── BrowserType     .chromium() / .firefox() / .webkit()
        └── Browser   .launch().await? → owns the browser process
              └── BrowserContext       isolated cookies / storage
                    └── Page            one tab
                          └── Locator   selector with auto-wait

Locator is the workhorse. Build one with Page::locator or one of the semantic helpers (get_by_role, get_by_text, get_by_label, get_by_placeholder, get_by_test_id, get_by_alt_text, get_by_title), then call action / assertion methods on it — they wait for the element to be actionable automatically.

§API conventions

  • Result<T> everywhere. One error type, Error.
  • Builders for option-heavy methods. goto, click, screenshot, tracing.start, etc. take an Options struct (..Default::default()).
  • Auto-wait by default. Locator-based actions wait until the element is actionable; expect assertions auto-retry until the condition holds or times out. You almost never need a manual sleep.
  • Async/await on tokio. Every method that does I/O is async.
  • Selectors validated at compile time. Prefer the locator! macro (re-exported when the default macros feature is on) over raw &str: empty / malformed selectors fail at cargo build, not at runtime.

§Debugging failures

Fastest path to “what happened” is tracing:

  1. Wrap the test body with BrowserContext::tracing — start({ snapshots, screenshots, sources }) → run → stop({ path }).
  2. Open the resulting .trace.zip with playwright show-trace path/to/trace.zip — the trace viewer is language-agnostic, the same UI JS / Python users see.
  3. To inspect a trace programmatically (CI bots, agent feedback loops), pull in playwright-rs-trace as a [dev-dependencies].

See examples/trace_on_failure.rs for the canonical Rust pattern (Rust has no async Drop, so cleanup is explicit).

§Companion crates

  • playwright-rs-macros — compile-time-validated locator! macro. Default-on via the macros feature; surfaced here as playwright_rs::locator!.
  • playwright-rs-trace — pure-Rust parser for .trace.zip files. Standalone; add to [dev-dependencies] for post-mortem analysis.

§For AI coding agents

If you’re using this crate with a coding agent, install the playwright-rs-usage skill with npx skills add padamson/playwright-rust. It works with Claude Code, Codex, Cursor and other skills-compatible agents, and gives the agent this crate’s API model instead of generic Playwright knowledge.

§Examples

§Basic Navigation and Interaction

use playwright_rs::{Playwright, SelectOption};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let playwright = Playwright::launch().await?;
    let browser = playwright.chromium().launch().await?;
    let page = browser.new_page().await?;

    // Navigate using data URL for self-contained test
    let _ = page.goto(
        "data:text/html,<html><body>\
            <h1 id='title'>Welcome</h1>\
            <button id='btn' onclick='this.textContent=\"Clicked\"'>Click me</button>\
        </body></html>",
        None
    ).await;

    // Query elements with locators
    let heading = page.locator("#title");
    let text = heading.text_content().await?;
    assert_eq!(text, Some("Welcome".to_string()));

    // Click button and verify result
    let button = page.locator("#btn");
    button.click(None).await?;
    let button_text = button.text_content().await?;
    assert_eq!(button_text, Some("Clicked".to_string()));

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

§Form Interaction

use playwright_rs::{Playwright, SelectOption};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let playwright = Playwright::launch().await?;
    let browser = playwright.chromium().launch().await?;
    let page = browser.new_page().await?;

    // Create form with data URL
    let _ = page.goto(
        "data:text/html,<html><body>\
            <input type='text' id='name' />\
            <input type='checkbox' id='agree' />\
            <select id='country'>\
                <option value='us'>USA</option>\
                <option value='uk'>UK</option>\
                <option value='ca'>Canada</option>\
            </select>\
        </body></html>",
        None
    ).await;

    // Fill text input
    let name = page.locator("#name");
    name.fill("John Doe", None).await?;
    assert_eq!(name.input_value(None).await?, "John Doe");

    // Check checkbox
    let checkbox = page.locator("#agree");
    checkbox.set_checked(true, None).await?;
    assert!(checkbox.is_checked().await?);

    // Select option
    let select = page.locator("#country");
    select.select_option("uk", None).await?;
    assert_eq!(select.input_value(None).await?, "uk");

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

§Element Screenshots

use playwright_rs::Playwright;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let playwright = Playwright::launch().await?;
    let browser = playwright.chromium().launch().await?;
    let page = browser.new_page().await?;

    // Create element to screenshot
    let _ = page.goto(
        "data:text/html,<html><body>\
            <div id='box' style='width:100px;height:100px;background:blue'></div>\
        </body></html>",
        None
    ).await;

    // Take screenshot of specific element
    let element = page.locator("#box");
    let screenshot = element.screenshot(None).await?;
    assert!(!screenshot.is_empty());

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

§Assertions (expect API)

use playwright_rs::{expect, Playwright};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let playwright = Playwright::launch().await?;
    let browser = playwright.chromium().launch().await?;
    let page = browser.new_page().await?;

    let _ = page.goto(
        "data:text/html,<html><body>\
            <button id='enabled'>Enabled</button>\
            <button id='disabled' disabled>Disabled</button>\
            <input type='checkbox' id='checked' checked />\
        </body></html>",
        None
    ).await;

    // Assert button states with auto-retry
    let enabled_btn = page.locator("#enabled");
    expect(enabled_btn.clone()).to_be_enabled().await?;

    let disabled_btn = page.locator("#disabled");
    expect(disabled_btn).to_be_disabled().await?;

    // Assert checkbox state
    let checkbox = page.locator("#checked");
    expect(checkbox).to_be_checked().await?;

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

§Observability

Every public async method on the user-facing types (Browser, BrowserContext, BrowserType, Page, Frame, Locator, ElementHandle, Tracing, CDPSession, Debugger, Screencast, plus Request, Response, Dialog, Download, Worker, FileChooser) is instrumented with the tracing crate. Wire up any tracing_subscriber and you get spans for free, with cardinality-bounded identifiers (guid, selector, url, name) and selected completion-time fields (status, bytes_len, count, version). Event handlers never run inside the span of the call that registered them or of the operation that caused the event. Events arrive on the connection’s read loop, which dispatches each message inside a trace-level dispatch span. Handler tasks, page-level and context-level alike, are spawned with Instrument::in_current_span() and inherit that span and nothing else, so under an info or debug filter a handler has no parent span. The read loop itself starts with no span, for a launched browser and one from BrowserType::connect alike, so it never holds the caller’s span open, and neither do the driver’s stderr reader and the SIGINT listener. Correlate a handler with the operation that triggered it by guid or url, not by span parentage.

Levels: top-level user operations (goto, click, fill, screenshot, pdf, evaluate, tracing.start/stop, browser_type.launch) are at info; everything else is at debug. Span fields never carry input values, eval expressions or request/response bodies. trace-level events do: the connection logs every outbound request’s full JSON and every inbound message, so a trace filter writes fill text, evaluated expressions and base64 bodies to the log.

use tracing_subscriber::EnvFilter;

tracing_subscriber::fmt()
    .with_env_filter(EnvFilter::new("playwright_rs=info"))
    .init();

Re-exports§

pub use protocol::Browser;
pub use protocol::BrowserContext;
pub use protocol::BrowserType;
pub use protocol::ContentOptions;
pub use protocol::FrameLocator;
pub use protocol::HeaderEntry;
pub use protocol::Page;
pub use protocol::Playwright;
pub use protocol::Response;
pub use protocol::Selectors;
pub use protocol::Keyboard;
pub use protocol::Mouse;
pub use protocol::Touchscreen;
pub use protocol::Request;
pub use protocol::ResourceTiming;
pub use protocol::AriaRole;
pub use protocol::AriaSnapshotMode;
pub use protocol::AriaSnapshotOptions;
pub use protocol::BoundingBox;
pub use protocol::ElementHandle;
pub use protocol::FilterOptions;
pub use protocol::GetByRoleOptions;
pub use protocol::HighlightOptions;
pub use protocol::JSHandle;
pub use protocol::Locator;
pub use protocol::GotoOptions;
pub use protocol::WaitUntil;
pub use protocol::CheckOptions;
pub use protocol::ClickOptions;
pub use protocol::DragToOptions;
pub use protocol::DropOptions;
pub use protocol::DropOptionsBuilder;
pub use protocol::FillOptions;
pub use protocol::HoverOptions;
pub use protocol::PressOptions;
pub use protocol::PressSequentiallyOptions;
pub use protocol::Scroll;
pub use protocol::SelectOptions;
pub use protocol::TapOptions;
pub use protocol::WaitForOptions;
pub use protocol::WaitForState;
pub use protocol::Position;
pub use protocol::FilePayload;
pub use protocol::SelectOption;
pub use protocol::Animations;
pub use protocol::Caret;
pub use protocol::Scale;
pub use protocol::ScreenshotClip;
pub use protocol::ScreenshotOptions;
pub use protocol::ScreenshotType;
pub use protocol::ActionCursor;
pub use protocol::ActionPosition;
pub use protocol::ChapterOptions;
pub use protocol::OverlayId;
pub use protocol::Screencast;
pub use protocol::ScreencastFrame;
pub use protocol::ScreencastSize;
pub use protocol::ScreencastStartOptions;
pub use protocol::ShowActionsOptions;
pub use protocol::ShowActionsStyle;
pub use protocol::ShowOverlayOptions;
pub use protocol::WebStorage;
pub use protocol::WebStorageKind;
pub use protocol::WebMCP;
pub use protocol::WebMCPOptions;
pub use protocol::WebMCPTool;
pub use protocol::WebMCPToolAnnotations;
pub use protocol::Credentials;
pub use protocol::CredentialsCreateOptions;
pub use protocol::CredentialsGetOptions;
pub use protocol::VirtualCredential;
pub use protocol::AddScriptTagOptions;
pub use protocol::ColorScheme;
pub use protocol::EmulateMediaOptions;
pub use protocol::ForcedColors;
pub use protocol::Media;
pub use protocol::PdfMargin;
pub use protocol::PdfOptions;
pub use protocol::ReducedMotion;
pub use protocol::BrowserContextOptions;
pub use protocol::Cookie;
pub use protocol::Geolocation;
pub use protocol::HttpCredentials;
pub use protocol::HttpCredentialsSend;
pub use protocol::LocalStorageItem;
pub use protocol::Origin;
pub use protocol::RecordHar;
pub use protocol::RecordVideo;
pub use protocol::StorageState;
pub use protocol::StorageStateOptions;
pub use protocol::Viewport;
pub use protocol::HarContent;
pub use protocol::HarMode;
pub use protocol::StartHarOptions;
pub use protocol::TraceSnapshots;
pub use protocol::Tracing;
pub use protocol::TracingStartOptions;
pub use protocol::TracingStopOptions;
pub use protocol::EventWaiter;
pub use protocol::EventValue;
pub use protocol::ConsoleMessage;
pub use protocol::ConsoleMessageLocation;
pub use protocol::DeviceDescriptor;
pub use protocol::DeviceViewport;
pub use protocol::WebError;
pub use protocol::WebErrorLocation;
pub use protocol::WebSocketRoute;
pub use protocol::WebSocketRouteCloseOptions;
pub use protocol::FileChooser;
pub use protocol::Accessibility;
pub use protocol::AccessibilitySnapshotOptions;
pub use protocol::CSSCoverageEntry;
pub use protocol::Coverage;
pub use protocol::CoverageRange;
pub use protocol::JSCoverageEntry;
pub use protocol::JSCoverageRange;
pub use protocol::JSFunctionCoverage;
pub use protocol::StartCSSCoverageOptions;
pub use protocol::StartJSCoverageOptions;
pub use protocol::Clock;
pub use protocol::ClockInstallOptions;
pub use protocol::Video;
pub use protocol::FetchOptions;
pub use protocol::FetchResponse;
pub use protocol::FulfillOptions;
pub use protocol::Route;
pub use protocol::UnrouteBehavior;
pub use protocol::APIRequest;
pub use protocol::APIRequestContext;
pub use protocol::APIRequestContextOptions;
pub use protocol::APIResponse;
pub use api::ConnectOverCdpOptions;
pub use api::LaunchOptions;

Modules§

api
protocol
testing
Opt-in test fakes for browser APIs Playwright cannot drive natively.

Macros§

locator
Compile-time-validated Playwright selector. Expands to a &'static str containing the same selector verbatim, with the validation a one-time compile-time check.

Structs§

PageExpectation
Page-level expectation for screenshot assertions.

Enums§

Error
Errors that can occur when using playwright-core

Constants§

DEFAULT_LAUNCH_TIMEOUT_MS
Default timeout in milliseconds for starting a browser: launch, launch_persistent_context, and connect_over_cdp.
DEFAULT_TIMEOUT_MS
Default timeout in milliseconds for Playwright operations.
PLAYWRIGHT_VERSION
Playwright server version bundled with this crate.

Functions§

expect
Creates an expectation for a locator with auto-retry behavior.
expect_page
Creates a page-level expectation for screenshot assertions.
install_browsers
Install Playwright browsers programmatically.
install_browsers_with_deps
Install Playwright browsers and their system dependencies.

Type Aliases§

Result
Result type alias for playwright-core operations