Skip to content

playwright-rs 0.20.0

Choose a tag to compare

@github-actions github-actions released this 11 Oct 22:10
· 37 commits to main since this release

Added

  • WebMCP: Page::webmcp and Frame::webmcp, which list the tools a page registers through the experimental WebMCP browser API (tools, as WebMCPTools with their input schema and hints) and call them (call_tool, with the result deserialized as any serde type), so a test can exercise them as an agent would. Chromium needs --enable-features=WebMCP in args and Firefox two dom.modelcontext preferences; WebKit does not support it. The webmcp_agent example shows the agent loop it makes possible: hand a page's tools to a model (their name, description, and input schema are the fields a model's tool list takes), call the one it picks, and fall back to an ai snapshot and get_by_ref on a page that publishes none.
  • Locator::within, which scopes a locator to each element another locator matches: cells.nth(1).within(&rows) is the second cell of every row, where rows.locator(..) would number the cells across the whole table.
  • Page::get_by_ref, which finds the element an ai-mode aria snapshot names by its ref (button "Save" [ref=e2]), so an agent can act on what a snapshot reported.
  • Page::content_with_options and Frame::content_with_options, taking ContentOptions::include_shadow, which serializes open shadow roots as declarative shadow DOM (<template shadowrootmode="open">) where content() leaves them out. Closed shadow roots are never included. content() itself is unchanged.
  • Screen emulation: BrowserContextOptions::screen and DeviceDescriptor::screen. The screen size window.screen and media queries report is separate from the viewport, and Playwright's context options and device descriptors both carry it, but this crate had neither: devices() dropped the screen 115 of its descriptors define, and a context could not set one. Pass a descriptor's screen with its viewport to emulate the device fully.
  • Cookies on APIRequestContext: add_cookies, cookies, and clear_cookies, mirroring BrowserContext. On the request context a BrowserContext exposes through request(), they act on the browser context's own jar.
  • Regular-expression cookie filters: ClearCookiesOptions::name_regex, domain_regex, and path_regex, each taking a JavaScript regex source and flags, for both clear_cookies methods. Each filter is a string or a regex, whichever was set last. The string filters are unchanged.
  • Cookie::url, so a cookie being added can name the URL it applies to and let the driver derive its domain, path, and secure flag, as every other binding allows. An empty domain or path is no longer sent, which is what lets a URL cookie through the driver's check that a cookie has a URL or a domain and path, not both.
  • Frame rate and action styling for recordings. RecordVideo::fps and ScreencastStartOptions::fps set the frame rate of a context's videos and of a recorded screencast (Firefox and WebKit capture at most 25 per second; the driver refuses 0). ShowActionsOptions::style, a ShowActionsStyle of CSS for the action-point marker, the target highlight, and the title, restyles the action decorations, and RecordVideo::show_actions puts those decorations into a context's videos, which the crate had no way to ask for.
  • Passkey signature counters: VirtualCredential::sign_count and CredentialsCreateOptions::sign_count. The driver tracks the counter each WebAuthn assertion increments and saves it in a storage state, but VirtualCredential had no field for it, so a state saved and restored through this crate restarted every counter at 0, which a relying party that checks counters rejects. The field is read and written now (a state saved without one loads as 0), and create can seed a starting value.
  • AccessibilitySnapshotOptions::interesting_only and ::root setters. 0.19.0 left this struct without them because the emulated snapshot ignores both fields; it still does, and the struct docs say so, but an option struct with public Option fields and no setters was the odd one out.

Changed

  • Bundled Playwright driver is now 1.64.0 (was 1.63.0). Browser builds: Chromium 156, Firefox 157, WebKit 27.2; videos are now encoded as VP9. One upstream behavior change reaches Rust callers: an element inside an iframe that is itself hidden (for example visibility: hidden) now counts as hidden for actions, is_visible() and to_be_visible() when it is reached through frame_locator. A Frame taken from content_frame() does not know its iframe is hidden, so a locator on it still reports the element visible. The protocol spec was diffed across both tags: no command or parameter this crate sends was renamed, and the additive surface is listed in the v1.0 gap analysis.
  • Starting a browser waits up to three minutes by default (was 30 seconds). launch, launch_persistent_context, and connect_over_cdp without a timeout now send the new DEFAULT_LAUNCH_TIMEOUT_MS, 180,000 ms, which is the default Playwright documents for launching and the value playwright-python sends, so a cold browser start on a slow CI runner no longer fails here where the other bindings succeed. On Windows CI the crate used to substitute 60 seconds for a missing launch timeout, a bump over the old default that would now be a cut, so that override is gone and Windows gets the same three minutes. A launch that hangs takes correspondingly longer to fail; pass timeout to keep the old bound. connect keeps its 30 seconds, and every other operation keeps DEFAULT_TIMEOUT_MS.
  • The bundled Node runtime is 24.21.0 (was 24.17.0), the version playwright-python pins for this driver. The assembled driver's cache directory now names its Node version (playwright-1.64.0-mac-arm64-node24.21.0), so a new runtime pin takes effect on a machine that already has the driver assembled instead of reusing the old one; the previous directory is left in place and can be deleted.
  • Requests carry string call ids in the abcd@7 shape of the other clients. 1.64 takes a request's id verbatim as the call id it records in traces, where it used to prefix it as call@7, so integer ids put numeric call ids into every trace, which the trace format types as strings.
  • Every declared span field is recorded now. ElementHandle::screenshot records bytes_len and BrowserContext::cookies records count, both declared since the tracing work landed and never filled; Locator::aria_snapshot drops its never-filled mode field, and ResponseObject::body and ::http_version drop duplicates of fields the public Response already records. The crate docs now say which span an event handler inherits: the connection read loop's, never the caller's.

Deprecated

  • ShowActionsOptions::font_size, field and setter. Playwright 1.64 deprecated it in favor of style, which takes CSS for the title, so font_size(16) becomes style(ShowActionsStyle::default().title("font-size: 16px")). The driver still accepts it, so it is still sent. It will be removed in a later breaking release.
  • devtools on LaunchOptions and BrowserContextOptions. Playwright removed the option in 1.58 and the driver drops it without an error, so .devtools(true) has opened no DevTools panel since 0.8.7, which bundled driver 1.58.2. The field and its builder now carry #[deprecated] and are no longer sent. It does not make the browser headed either. For Chromium, pass --auto-open-devtools-for-tabs in args and set headless(false), as the other language bindings do; on BrowserContextOptions that works only for launch_persistent_context, so for new_context set them on the LaunchOptions the browser was launched with. Both will be removed in a later breaking release.

Removed

  • BREAKING: BrowserContext::deserialize_binding_args_pub. A wire-format helper that was public only so Page could call it; it corresponds to no Playwright API and its output shape is the driver's, not the crate's. The shared function is crate-private now.

Fixed

  • Locator::screenshot waits for its element. It looked the element up once and failed at once with ElementNotFound when it was not there yet, where every other locator action, and Playwright's own locator.screenshot, waits up to the timeout. An element rendered a moment after the call is now captured, and one that never appears fails with Error::Timeout. The wait and the capture share one timeout. Like every locator action, it is now strict: a locator matching several elements fails instead of capturing the first.
  • A regular expression returned from evaluate arrives as its literal ("/ab+c/gi"). The driver sends it as a pattern and flags, a form the result parser did not know, so it came back as null.
  • Context-level event handlers run in the read loop's dispatch span, as page-level ones already did. Handlers registered on a BrowserContext, Browser, Debugger, WebSocket or WebSocketRoute, and a Page's load and frame-navigated handlers, ran in tasks spawned with no span, so they had no parent span where a page-level handler nested under dispatch.
  • Long-lived crate tasks no longer hold the caller's span open. A span entered around Playwright::launch stayed open until the driver exited, because the driver's stderr reader and the SIGINT listener inherited it; BrowserType::connect kept its own connect span open for the life of the connection, with every handler nested under it. These long-lived tasks now start with no span, so the caller's span closes when the caller drops it.
  • BrowserContextOptions::record_har records a HAR. It was sent to the driver as a context parameter the driver does not have, so it was dropped without an error and no file was ever written. The recording now starts once the context exists and is written when the context closes, as in the other language bindings, for Browser::new_context_with_options and BrowserType::launch_persistent_context_with_options alike. Close the context to get the file: a browser closed without closing the context first writes nothing. An unknown content or mode string now fails context creation with Error::InvalidArgument, before any context is created, instead of being ignored. If the HAR cannot be written, close returns the error and leaves the context open, and a second close closes it. A context-level HAR and one started with Tracing::start_har record side by side.
  • Options whose names hold an acronym reach the driver. javascript_enabled, bypass_csp, ignore_https_errors, extra_http_headers and base_url on BrowserContextOptions, and handle_sigint, handle_sigterm and handle_sighup on LaunchOptions, were sent under names the driver does not recognize (javascriptEnabled rather than javaScriptEnabled, baseUrl rather than baseURL, handleSigint rather than handleSIGINT, and so on), and the driver drops an unknown option without an error, so each had no effect: a context built with .javascript_enabled(false) still ran page script, a relative goto under base_url failed, and .handle_sigint(false) left the default signal handling in place. This applies to Browser::new_context_with_options, BrowserType::launch_persistent_context and BrowserType::launch_with_options alike. extra_http_headers is also sent as the list of name and value pairs the driver expects rather than as a map. The runtime setters such as BrowserContext::set_extra_http_headers were not affected.
  • Action and navigation timeouts (e.g. locator.click exceeding its timeout) map to Error::Timeout instead of Error::ProtocolError. The server reports these with ErrorPayload.name == "TimeoutError", which parse_protocol_error previously ignored, so callers had to string-match "TimeoutError" inside a ProtocolError message to tell a timeout from any other protocol failure.
  • unroute_all honors its behavior argument on Page and BrowserContext. UnrouteBehavior::Wait now returns only after the handler invocations already running have finished, and UnrouteBehavior::IgnoreErrors lets those invocations fail without a log line, matching upstream. Both were accepted and ignored: the call cleared the handler list and returned at once, so a test tearing down after unroute_all(Some(Wait)) could race a handler still fulfilling a route.
  • Context-level page-event handlers are wired to each page exactly once. on_download, on_frame_attached, on_frame_detached, on_frame_navigated, on_page_load and on_page_close on a BrowserContext forward the matching page event, and the forwarding used to be installed by whichever registration or page arrival found the handler list empty. Two registrations racing each other double-wired every open page, so each event reached each handler twice; a page arriving while a handler was being registered could be wired by neither, so its events never reached the context at all. Every page is now wired for all six events when the context learns of it, and registering a handler only appends to the list. An error returned by one of these handlers is now logged at warn instead of being dropped.
  • expect_event("serviceworker") on a BrowserContext and expect_event("worker") on a Page no longer lose the event to a waiter that already timed out. Their waiters were popped newest-first and a caller whose timeout had passed still held its slot, so the next event went to the dead waiter and a live one waited out its own timeout. Waiters are served oldest-first and one whose caller has gone is skipped, as the other expect_event names already did.