playwright_rs/lib.rs
1//! playwright: High-level Rust bindings for Microsoft Playwright
2//!
3//! This crate provides the public API for browser automation using Playwright.
4//!
5//! # Quick tour
6//!
7//! ## Object model
8//!
9//! ```text
10//! Playwright start here — Playwright::launch().await?
11//! └── BrowserType .chromium() / .firefox() / .webkit()
12//! └── Browser .launch().await? → owns the browser process
13//! └── BrowserContext isolated cookies / storage
14//! └── Page one tab
15//! └── Locator selector with auto-wait
16//! ```
17//!
18//! [`Locator`] is the workhorse. Build one with [`Page::locator`] or one
19//! of the semantic helpers (`get_by_role`, `get_by_text`, `get_by_label`,
20//! `get_by_placeholder`, `get_by_test_id`, `get_by_alt_text`,
21//! `get_by_title`), then call action / assertion methods on it — they
22//! wait for the element to be actionable automatically.
23//!
24//! ## API conventions
25//!
26//! - **`Result<T>` everywhere.** One error type, [`Error`].
27//! - **Builders for option-heavy methods.** `goto`, `click`, `screenshot`,
28//! `tracing.start`, etc. take an `Options` struct (`..Default::default()`).
29//! - **Auto-wait by default.** Locator-based actions wait until the
30//! element is actionable; [`expect`] assertions auto-retry until the
31//! condition holds or times out. You almost never need a manual `sleep`.
32//! - **Async/await on `tokio`.** Every method that does I/O is `async`.
33//! - **Selectors validated at compile time.** Prefer the [`locator!`]
34//! macro (re-exported when the default `macros` feature is on) over
35//! raw `&str`: empty / malformed selectors fail at `cargo build`, not
36//! at runtime.
37//!
38//! ## Debugging failures
39//!
40//! Fastest path to "what happened" is tracing:
41//!
42//! 1. Wrap the test body with [`BrowserContext::tracing`] —
43//! `start({ snapshots, screenshots, sources })` → run → `stop({ path })`.
44//! 2. Open the resulting `.trace.zip` with
45//! `playwright show-trace path/to/trace.zip` — the trace viewer is
46//! language-agnostic, the same UI JS / Python users see.
47//! 3. To inspect a trace programmatically (CI bots, agent feedback
48//! loops), pull in [`playwright-rs-trace`](https://docs.rs/playwright-rs-trace)
49//! as a `[dev-dependencies]`.
50//!
51//! See [`examples/trace_on_failure.rs`](https://github.com/padamson/playwright-rust/blob/main/crates/playwright/examples/trace_on_failure.rs)
52//! for the canonical Rust pattern (Rust has no async `Drop`, so cleanup
53//! is explicit).
54//!
55//! ## Companion crates
56//!
57//! - [`playwright-rs-macros`](https://docs.rs/playwright-rs-macros) —
58//! compile-time-validated [`locator!`] macro. Default-on via the
59//! `macros` feature; surfaced here as `playwright_rs::locator!`.
60//! - [`playwright-rs-trace`](https://docs.rs/playwright-rs-trace) —
61//! pure-Rust parser for `.trace.zip` files. Standalone; add to
62//! `[dev-dependencies]` for post-mortem analysis.
63//!
64//! ## For AI coding agents
65//!
66//! If you're using this crate with a coding agent, install the
67//! [`playwright-rs-usage` skill](https://github.com/padamson/playwright-rust/tree/main/skills/playwright-rs-usage)
68//! with `npx skills add padamson/playwright-rust`. It works with Claude
69//! Code, Codex, Cursor and other skills-compatible agents, and gives the
70//! agent this crate's API model instead of generic Playwright knowledge.
71//!
72//! # Examples
73//!
74//! ## Basic Navigation and Interaction
75//!
76//! ```no_run
77//! use playwright_rs::{Playwright, SelectOption};
78//!
79//! #[tokio::main]
80//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
81//! let playwright = Playwright::launch().await?;
82//! let browser = playwright.chromium().launch().await?;
83//! let page = browser.new_page().await?;
84//!
85//! // Navigate using data URL for self-contained test
86//! let _ = page.goto(
87//! "data:text/html,<html><body>\
88//! <h1 id='title'>Welcome</h1>\
89//! <button id='btn' onclick='this.textContent=\"Clicked\"'>Click me</button>\
90//! </body></html>",
91//! None
92//! ).await;
93//!
94//! // Query elements with locators
95//! let heading = page.locator("#title");
96//! let text = heading.text_content().await?;
97//! assert_eq!(text, Some("Welcome".to_string()));
98//!
99//! // Click button and verify result
100//! let button = page.locator("#btn");
101//! button.click(None).await?;
102//! let button_text = button.text_content().await?;
103//! assert_eq!(button_text, Some("Clicked".to_string()));
104//!
105//! browser.close().await?;
106//! Ok(())
107//! }
108//! ```
109//!
110//! ## Form Interaction
111//!
112//! ```no_run
113//! use playwright_rs::{Playwright, SelectOption};
114//!
115//! #[tokio::main]
116//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
117//! let playwright = Playwright::launch().await?;
118//! let browser = playwright.chromium().launch().await?;
119//! let page = browser.new_page().await?;
120//!
121//! // Create form with data URL
122//! let _ = page.goto(
123//! "data:text/html,<html><body>\
124//! <input type='text' id='name' />\
125//! <input type='checkbox' id='agree' />\
126//! <select id='country'>\
127//! <option value='us'>USA</option>\
128//! <option value='uk'>UK</option>\
129//! <option value='ca'>Canada</option>\
130//! </select>\
131//! </body></html>",
132//! None
133//! ).await;
134//!
135//! // Fill text input
136//! let name = page.locator("#name");
137//! name.fill("John Doe", None).await?;
138//! assert_eq!(name.input_value(None).await?, "John Doe");
139//!
140//! // Check checkbox
141//! let checkbox = page.locator("#agree");
142//! checkbox.set_checked(true, None).await?;
143//! assert!(checkbox.is_checked().await?);
144//!
145//! // Select option
146//! let select = page.locator("#country");
147//! select.select_option("uk", None).await?;
148//! assert_eq!(select.input_value(None).await?, "uk");
149//!
150//! browser.close().await?;
151//! Ok(())
152//! }
153//! ```
154//!
155//! ## Element Screenshots
156//!
157//! ```no_run
158//! use playwright_rs::Playwright;
159//!
160//! #[tokio::main]
161//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
162//! let playwright = Playwright::launch().await?;
163//! let browser = playwright.chromium().launch().await?;
164//! let page = browser.new_page().await?;
165//!
166//! // Create element to screenshot
167//! let _ = page.goto(
168//! "data:text/html,<html><body>\
169//! <div id='box' style='width:100px;height:100px;background:blue'></div>\
170//! </body></html>",
171//! None
172//! ).await;
173//!
174//! // Take screenshot of specific element
175//! let element = page.locator("#box");
176//! let screenshot = element.screenshot(None).await?;
177//! assert!(!screenshot.is_empty());
178//!
179//! browser.close().await?;
180//! Ok(())
181//! }
182//! ```
183//!
184//! ## Assertions (expect API)
185//!
186//! ```no_run
187//! use playwright_rs::{expect, Playwright};
188//!
189//! #[tokio::main]
190//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
191//! let playwright = Playwright::launch().await?;
192//! let browser = playwright.chromium().launch().await?;
193//! let page = browser.new_page().await?;
194//!
195//! let _ = page.goto(
196//! "data:text/html,<html><body>\
197//! <button id='enabled'>Enabled</button>\
198//! <button id='disabled' disabled>Disabled</button>\
199//! <input type='checkbox' id='checked' checked />\
200//! </body></html>",
201//! None
202//! ).await;
203//!
204//! // Assert button states with auto-retry
205//! let enabled_btn = page.locator("#enabled");
206//! expect(enabled_btn.clone()).to_be_enabled().await?;
207//!
208//! let disabled_btn = page.locator("#disabled");
209//! expect(disabled_btn).to_be_disabled().await?;
210//!
211//! // Assert checkbox state
212//! let checkbox = page.locator("#checked");
213//! expect(checkbox).to_be_checked().await?;
214//!
215//! browser.close().await?;
216//! Ok(())
217//! }
218//! ```
219//!
220//! ## Observability
221//!
222//! Every public async method on the user-facing types
223//! (`Browser`, `BrowserContext`, `BrowserType`, `Page`, `Frame`, `Locator`,
224//! `ElementHandle`, `Tracing`, `CDPSession`, `Debugger`, `Screencast`, plus
225//! `Request`, `Response`, `Dialog`, `Download`, `Worker`, `FileChooser`)
226//! is instrumented with the [`tracing`] crate. Wire up any
227//! `tracing_subscriber` and you get spans for free, with cardinality-bounded
228//! identifiers (`guid`, `selector`, `url`, `name`) and selected
229//! completion-time fields (`status`, `bytes_len`, `count`, `version`).
230//! Event handlers never run inside the span of the call that registered
231//! them or of the operation that caused the event. Events arrive on the
232//! connection's read loop, which dispatches each message inside a
233//! `trace`-level `dispatch` span. Handler tasks, page-level and
234//! context-level alike, are spawned with `Instrument::in_current_span()`
235//! and inherit that span and nothing else, so under an `info` or `debug`
236//! filter a handler has no parent span. The read loop itself starts with no
237//! span, for a launched browser and one from `BrowserType::connect` alike,
238//! so it never holds the caller's span open, and neither do the driver's
239//! stderr reader and the SIGINT listener. Correlate a handler with the
240//! operation that triggered it by `guid` or `url`, not by span parentage.
241//!
242//! Levels: top-level user operations (`goto`, `click`, `fill`, `screenshot`,
243//! `pdf`, `evaluate`, `tracing.start/stop`, `browser_type.launch`) are at
244//! `info`; everything else is at `debug`. Span fields never carry input
245//! values, eval expressions or request/response bodies. `trace`-level
246//! events do: the connection logs every outbound request's full JSON and
247//! every inbound message, so a `trace` filter writes fill text, evaluated
248//! expressions and base64 bodies to the log.
249//!
250//! ```no_run
251//! use tracing_subscriber::EnvFilter;
252//!
253//! tracing_subscriber::fmt()
254//! .with_env_filter(EnvFilter::new("playwright_rs=info"))
255//! .init();
256//! ```
257
258// Internal modules (exposed for integration tests)
259#[doc(hidden)]
260pub mod server;
261
262pub mod api;
263mod assertions;
264mod error;
265pub mod protocol;
266mod task;
267pub mod testing;
268mod tty_guard;
269
270/// Playwright server version bundled with this crate.
271///
272/// This version determines which browser builds are compatible. Install
273/// browsers through [`install_browsers`] / [`install_browsers_with_deps`]
274/// (or the `cli` feature's `playwright-rs install` binary), which always
275/// match this version. An `npx playwright@<version> install` works only
276/// while `<version>` equals this constant, and goes stale when the crate
277/// bumps.
278///
279/// See: <https://playwright.dev/docs/browsers>
280pub const PLAYWRIGHT_VERSION: &str = env!("PLAYWRIGHT_DRIVER_VERSION");
281
282/// Default timeout in milliseconds for Playwright operations.
283///
284/// This matches Playwright's standard default across all language implementations (Python, Java, .NET, JS).
285/// Required in Playwright 1.56.1+ when timeout parameter is not explicitly provided.
286///
287/// See: <https://playwright.dev/docs/test-timeouts>
288pub const DEFAULT_TIMEOUT_MS: f64 = 30000.0;
289
290/// Default timeout in milliseconds for starting a browser: `launch`,
291/// `launch_persistent_context`, and `connect_over_cdp`.
292///
293/// Three minutes, the default Playwright documents for launching and the
294/// value playwright-python sends, since a cold browser start on a loaded CI
295/// runner can take longer than [`DEFAULT_TIMEOUT_MS`].
296///
297/// See: <https://playwright.dev/docs/api/class-browsertype#browser-type-launch-option-timeout>
298pub const DEFAULT_LAUNCH_TIMEOUT_MS: f64 = 180000.0;
299
300// Re-export error types
301pub use error::{Error, Result};
302
303// Re-export assertions API
304pub use assertions::{PageExpectation, expect, expect_page};
305
306// Screenshot-diff types are gated on the optional feature. (`Animations` is
307// always available via the protocol re-export below; it is shared with
308// ScreenshotOptions.)
309#[cfg(feature = "screenshot-diff")]
310pub use assertions::{ScreenshotAssertionOptions, ScreenshotAssertionOptionsBuilder};
311
312// Re-export Playwright main entry point and browser API
313pub use protocol::{
314 Browser, BrowserContext, BrowserType, ContentOptions, FrameLocator, HeaderEntry, Page,
315 Playwright, Response, Selectors,
316};
317
318// Re-export input device types
319pub use protocol::{Keyboard, Mouse, Touchscreen};
320
321// Re-export Request and related types
322pub use protocol::{Request, ResourceTiming};
323
324// Re-export Locator and element APIs
325pub use protocol::{
326 AriaRole, AriaSnapshotMode, AriaSnapshotOptions, BoundingBox, ElementHandle, FilterOptions,
327 GetByRoleOptions, HighlightOptions, JSHandle, Locator,
328};
329
330// Re-export navigation and page options
331pub use protocol::{GotoOptions, WaitUntil};
332
333// Re-export action options
334pub use protocol::{
335 CheckOptions, ClickOptions, DragToOptions, DropOptions, DropOptionsBuilder, FillOptions,
336 HoverOptions, PressOptions, PressSequentiallyOptions, Scroll, SelectOptions, TapOptions,
337 WaitForOptions, WaitForState,
338};
339
340// Re-export Position (needed for DragToOptions and other options)
341pub use protocol::Position;
342
343// Re-export form and input types
344pub use protocol::{FilePayload, SelectOption};
345
346// Re-export screenshot types
347pub use protocol::{Animations, Caret, Scale, ScreenshotClip, ScreenshotOptions, ScreenshotType};
348
349// Re-export screencast types
350pub use protocol::{
351 ActionCursor, ActionPosition, ChapterOptions, OverlayId, Screencast, ScreencastFrame,
352 ScreencastSize, ScreencastStartOptions, ShowActionsOptions, ShowActionsStyle,
353 ShowOverlayOptions,
354};
355
356// Re-export WebStorage (page.local_storage / session_storage)
357pub use protocol::{WebStorage, WebStorageKind};
358
359// Re-export WebMCP (page.webmcp / frame.webmcp)
360pub use protocol::{WebMCP, WebMCPOptions, WebMCPTool, WebMCPToolAnnotations};
361
362// Re-export WebAuthn credentials (browser_context.credentials)
363pub use protocol::{
364 Credentials, CredentialsCreateOptions, CredentialsGetOptions, VirtualCredential,
365};
366
367// Re-export new page method types
368pub use protocol::{
369 AddScriptTagOptions, ColorScheme, EmulateMediaOptions, ForcedColors, Media, PdfMargin,
370 PdfOptions, ReducedMotion,
371};
372
373// Re-export browser context options and storage state types
374pub use protocol::{
375 BrowserContextOptions, Cookie, Geolocation, HttpCredentials, HttpCredentialsSend,
376 LocalStorageItem, Origin, RecordHar, RecordVideo, StorageState, StorageStateOptions, Viewport,
377};
378
379// Re-export the tracing + HAR API. These were the last consumer-facing
380// cohort still reachable only via `playwright_rs::protocol::*`; bringing
381// them to the crate root matches every other type a consumer constructs
382// (the `protocol::` paths still work).
383pub use protocol::{
384 HarContent, HarMode, StartHarOptions, TraceSnapshots, Tracing, TracingStartOptions,
385 TracingStopOptions,
386};
387
388// Re-export EventWaiter for use with expect_page() / expect_close()
389pub use protocol::EventWaiter;
390
391// Re-export EventValue for use with expect_event()
392pub use protocol::EventValue;
393
394// Re-export ConsoleMessage types
395pub use protocol::{ConsoleMessage, ConsoleMessageLocation};
396
397// Re-export device descriptor types
398pub use protocol::{DeviceDescriptor, DeviceViewport};
399
400// Re-export WebError
401pub use protocol::{WebError, WebErrorLocation};
402
403// Re-export WebSocketRoute
404pub use protocol::{WebSocketRoute, WebSocketRouteCloseOptions};
405
406// Re-export FileChooser
407pub use protocol::FileChooser;
408
409// Re-export Accessibility and Coverage types
410pub use protocol::{
411 Accessibility, AccessibilitySnapshotOptions, CSSCoverageEntry, Coverage, CoverageRange,
412 JSCoverageEntry, JSCoverageRange, JSFunctionCoverage, StartCSSCoverageOptions,
413 StartJSCoverageOptions,
414};
415
416// Re-export Clock types
417pub use protocol::{Clock, ClockInstallOptions};
418
419// Re-export Video
420pub use protocol::Video;
421
422// Re-export routing types
423pub use protocol::{FetchOptions, FetchResponse, FulfillOptions, Route, UnrouteBehavior};
424
425// Re-export APIRequest public API
426pub use protocol::{APIRequest, APIRequestContext, APIRequestContextOptions, APIResponse};
427
428// Re-export launch and connection options
429pub use api::{ConnectOverCdpOptions, LaunchOptions};
430
431// Re-export browser installation helpers
432pub use server::driver::{install_browsers, install_browsers_with_deps};
433
434// Re-export the `locator!` compile-time-validated selector macro from
435// the companion `playwright-rs-macros` crate. Gated on the `macros`
436// feature (default-on) so users without the proc-macro toolchain
437// available can opt out.
438#[cfg(feature = "macros")]
439pub use playwright_rs_macros::locator;
440
441/// Parser for the trace zips [`Tracing`](crate::protocol::Tracing) writes.
442///
443/// Re-exported from the companion `playwright-rs-trace` crate, so a
444/// project that records and reads its own traces pins one version rather
445/// than two. Gated on the `trace` feature (off by default, since writing
446/// traces needs none of it); depend on the parser crate directly to read
447/// traces without this one.
448///
449/// ```no_run
450/// # #[cfg(feature = "trace")]
451/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
452/// let mut reader = playwright_rs::trace::open("trace.zip")?;
453/// for action in reader.actions()? {
454/// let action = action?;
455/// println!("{}.{}", action.class, action.method);
456/// }
457/// # Ok(())
458/// # }
459/// ```
460#[cfg(feature = "trace")]
461pub use playwright_rs_trace as trace;
462
463// The pure driver-acquisition mapping (platform → Node triple, download URLs,
464// the cached-driver layout) that build.rs and the cli binary `include!`.
465// std-only and compiled into the lib as well: the runtime's user-cache lookup
466// shares `cached_driver_dir` with the build script so the two cannot name
467// different paths, and its unit tests ride the always-run `cargo nextest`
468// pass. The rest is dead code here, which the file allows.
469#[path = "build_support/driver_urls.rs"]
470mod driver_urls;
471
472// Tests read the vendored protocol spec to check the names the crate sends
473// against the names the driver declares; see the module docs.
474#[cfg(test)]
475mod protocol_spec;