Playwright rs usage
Skill padamson/mdbook-listings/.claude/skills/playwright-rs-usage
Procedural reference for using playwright-rs in Rust browser-automation code — object model (Browser/Context/Page/Locator), the `locator!()` macro, builder pattern for options, auto-wait semantics, and how to capture / inspect traces for failure diagnosis. Use when writing tests or scripts with playwright-rs as a dependency. Loaded automatically when the current repo has playwright-rs in its Cargo.toml; can also be copied into downstream projects at `.claude/skills/playwright-rs-usage/`.From its SKILL.md
npx -y skills add padamson/mdbook-listings --skill playwright-rs-usageAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
9.2 KB, as published. Nobody here has run it
Using playwright-rs
Rust bindings for Microsoft Playwright. This crate is a thin JSON-RPC client to the upstream Playwright server, so the API mirrors playwright-python / java / .NET semantics. When unsure about a method's behavior, the upstream Playwright docs are the authoritative reference.
Drift discipline. The canonical API tour lives in the crate-level
rustdoc at https://docs.rs/playwright-rs — that's compile-checked
against the actual code. This skill is the "what to reach for, what
to avoid" overlay: durable conventions, not a method-by-method
reference. The one Rust code block below is compile-checked by
cargo xtask verify-agent-docs; the rest is prose that ages well
because it talks in concepts (auto-wait, builder pattern) rather
than specific method names.
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 with page.locator(...) or the
semantic get_by_* helpers (get_by_role, get_by_text, etc. —
see docs.rs for the full list) and chain action / assertion methods.
Conventions to follow
Result<T>andasync/awaitontokio. One error type:playwright_rs::Error. Use?to propagate.- Builders / setters for option-heavy methods.
goto,click,screenshot,fill,tracing().start, etc. take anOptionsstruct. These are#[non_exhaustive](so upstream option additions stay non-breaking) — struct literals won't compile. Construct with the type'sbuilder()where it has one, otherwise chain setters offDefault/new():GetByRoleOptions::default().name("OK").exact(true),Cookie::new(name, value).domain("example.com"). The exact method names live on docs.rs; don't memorize them. - Auto-wait + auto-retry. Locator-based actions wait until the
element is actionable;
expect()assertions retry until they hold or time out. Never inserttokio::time::sleepbetween an action and a check — that's a smell. If a wait feels necessary, you probably wantexpect(locator).to_be_visible().awaitor similar. locator!()macro for literal selectors. Compile-time validation catches typos and structural errors. Fall back to&stronly for selectors computed at runtime.- No reimplemented browser protocols. This crate is intentionally thin over the Playwright server. Anything you can't do via Playwright itself, you can't do here.
Minimal test skeleton
This block is compile-checked by cargo xtask verify-agent-docs —
if the API drifts, the verifier fails:
use anyhow::Result;
use playwright_rs::{Playwright, locator, expect};
#[tokio::test]
async fn login_flow() -> Result<()> {
let pw = Playwright::launch().await?;
let browser = pw.chromium().launch().await?;
let context = browser.new_context().await?;
let page = context.new_page().await?;
page.goto("https://example.com/login", None).await?;
page.locator(locator!("input[name='user']")).fill("alice", None).await?;
page.locator(locator!("input[name='pass']")).fill("hunter2", None).await?;
page.locator(locator!("text=Sign in")).click(None).await?;
expect(page.locator(locator!(".welcome"))).to_be_visible().await?;
browser.close().await?;
Ok(())
}
Capabilities worth reaching for
Concept-level pointers; the exact options live on docs.rs.
- Stable / redacted screenshots.
ScreenshotOptionscarriesanimations(Disabled)for flake-free shots (freeze CSS animations before capture) andmask/mask_colorto overpaint dynamic or sensitive elements. Reach foranimations(Disabled)whenever a screenshot races an animation. - Context-level events. Beyond per-page handlers,
BrowserContextobserves activity across all its pages —on_download,on_page_load/on_page_close,on_frame_attached/_detached/_navigated— andBrowser::on_contextfires for each new context. Use these for multi-tab fixtures instead of wiring every page individually. - HAR network capture.
tracing().start_har(path, ..)/stop_har()records all network traffic to a HAR — inspect it in browser devtools or replay it deterministically withroute_from_har. A sibling to trace capture. - Typed page probes.
page.evaluate::<R, T>(expr, arg)deserializes the JS return value straight into any serdeDeserializetype — define a struct for the shape and skip manual parsing (None::<&()>for the no-argument case).evaluate_value(returnsString) is only for one-off scalar probes; if you catch yourself returning delimited strings from JS and splitting them in Rust, switch toevaluate. Runnable walkthroughs:examples/evaluate_typed.rsandexamples/canvas_pixels.rs(canvas pixel assertions for wasm/canvas frontends). - Drags:
Locator::drag_tocovers everything, including canvas. It drives the realpointerdown→ capture →pointermove→pointerupchain (works againstsetPointerCaptureUIs), andDragToOptions::target_position— an offset from the target's top-left — turns it into "drag to a coordinate": pass the containing canvas/stage as the target. Prefer it over held-buttonMouse::move_tosequences, which hang on headless Linux. Don't hand-roll syntheticPointerEventdispatch via evaluate. - External drag-and-drop.
Locator::dropsimulates dragging files or data in from outside the page (upload zones), distinct fromdrag_to, which drags one element onto another within the page. - File System Access API flows.
page.fake_file_system()installs an opt-in fake ofshowSaveFilePicker/showOpenFilePicker(native dialogs no automation tool can drive): seed opens withset_open_file, assert saves withlast_saved_bytes(), control permission state. Don't hand-roll anadd_init_scriptpicker shim. - Accessibility-tree assertions.
expect_page(&page) .to_match_aria_snapshot(..)(and the locator form) guard the page's ARIA structure as a regression check;aria_snapshotcan emit[box=..]bounding boxes for visual/agent reasoning.
Debugging failures with traces
Rust has no async Drop, so trace cleanup is explicit. The
canonical pattern: capture the test result, run cleanup
unconditionally, pass the trace path only on failure.
See examples/trace_on_failure.rs
for the runnable end-to-end version — it's compiled by
cargo check --examples so it can't silently rot.
To view a captured trace: playwright show-trace trace.zip. The
viewer is language-agnostic — same UI JS / Python users see. The
hosted version is at https://trace.playwright.dev.
Programmatic trace inspection
For CI bots, agent feedback loops, or any code that wants to read what
happened in a trace without re-running the test, add the companion
crate as a [dev-dependencies] entry: playwright-rs-trace = "0.1".
Use TraceReader::actions() to walk the reassembled action stream,
TraceReader::network() for HTTP traffic. The crate's //!
rustdoc on https://docs.rs/playwright-rs-trace has a runnable
example.
Things that look like playwright-python but aren't quite
- No
sync_playwright. Async only; everything awaits ontokio. Result, not exceptions. Use?to propagate.- No keyword arguments. Options come through
Optionsstructs with..Default::default(), notname=valuein method calls. - No async
Drop. Always close browsers / stop tracing explicitly in a cleanup block — don't rely on RAII for I/O. - Locators are values, not lazy proxies.
page.locator(...)returns aLocatoryou can.clone()and re-use cheaply.
Common pitfalls
- Manual sleeps before assertions. Use
expect(..)and let it auto-retry. - Closing the browser before
tracing.stop()— traces are written on stop, so order matters: stop tracing first, then close. - Hardcoding selectors as
&str. Switch tolocator!()for any selector you write as a literal.
References
- Full API: https://docs.rs/playwright-rs
- Runnable examples: https://github.com/padamson/playwright-rust/tree/main/crates/playwright/examples
- Upstream Playwright docs: https://playwright.dev/docs/api
- Trace format / parser: https://docs.rs/playwright-rs-trace
locator!()macro: https://docs.rs/playwright-rs-macros