Skip to main content

playwright_cdp/
types.rs

1//! Shared value types used across the public API.
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use std::collections::HashMap;
6
7/// Default action timeout in milliseconds (matches Playwright's default).
8pub const DEFAULT_TIMEOUT_MS: f64 = 30_000.0;
9
10/// A viewport size.
11#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
12pub struct Viewport {
13    pub width: u32,
14    pub height: u32,
15}
16
17impl Viewport {
18    pub fn new(width: u32, height: u32) -> Self {
19        Self { width, height }
20    }
21}
22
23/// A point in viewport coordinates.
24#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
25pub struct Position {
26    pub x: f64,
27    pub y: f64,
28}
29
30/// Mouse button for click/hover actions.
31///
32/// Note: the discriminant values are an ordering, **not** the CDP `buttons`
33/// bitmask. Use [`MouseButton::bitmask`] for the `Input.dispatchMouseEvent`
34/// `buttons` field.
35#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
36#[repr(u8)]
37pub enum MouseButton {
38    #[default]
39    Left = 0,
40    Middle = 1,
41    Right = 2,
42    Back = 3,
43    Forward = 4,
44}
45
46impl MouseButton {
47    pub fn as_str(&self) -> &'static str {
48        match self {
49            MouseButton::Left => "left",
50            MouseButton::Middle => "middle",
51            MouseButton::Right => "right",
52            MouseButton::Back => "back",
53            MouseButton::Forward => "forward",
54        }
55    }
56
57    /// CDP `Input.dispatchMouseEvent` `buttons` bitmask for this button:
58    /// Left=1, Right=2, Middle=4, Back=8, Forward=16.
59    pub fn bitmask(&self) -> u8 {
60        match self {
61            MouseButton::Left => 1,
62            MouseButton::Right => 2,
63            MouseButton::Middle => 4,
64            MouseButton::Back => 8,
65            MouseButton::Forward => 16,
66        }
67    }
68}
69
70/// A rectangle in viewport coordinates (element bounds).
71#[derive(Debug, Clone, Copy, Default, PartialEq)]
72pub struct BoundingBox {
73    pub x: f64,
74    pub y: f64,
75    pub width: f64,
76    pub height: f64,
77}
78
79/// A clip rectangle for screenshots.
80#[derive(Debug, Clone, Copy, Default, PartialEq)]
81pub struct ScreenshotClip {
82    pub x: f64,
83    pub y: f64,
84    pub width: f64,
85    pub height: f64,
86}
87
88/// Keyboard modifiers for actions.
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
90pub enum KeyboardModifier {
91    Alt,
92    Control,
93    ControlOrMeta,
94    Meta,
95    Shift,
96}
97
98/// Proxy server configuration.
99#[derive(Debug, Clone, Default)]
100pub struct ProxySettings {
101    pub server: String,
102    pub bypass: Option<String>,
103    pub username: Option<String>,
104    pub password: Option<String>,
105}
106
107/// An ARIA role, used by `get_by_role`.
108///
109/// Mirrors Playwright's `AriaRole` set.
110#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111pub enum AriaRole {
112    Alert,
113    Alertdialog,
114    Application,
115    Article,
116    Banner,
117    Blockquote,
118    Button,
119    Caption,
120    Cell,
121    Checkbox,
122    Code,
123    Columnheader,
124    Combobox,
125    Complementary,
126    Contentinfo,
127    Definition,
128    Deletion,
129    Dialog,
130    Directory,
131    Document,
132    Embed,
133    Figure,
134    Footer,
135    Form,
136    Generic,
137    Grid,
138    Gridcell,
139    Group,
140    Heading,
141    Img,
142    Image,
143    Insertion,
144    Link,
145    List,
146    Listbox,
147    Listitem,
148    Log,
149    Main,
150    Marquee,
151    Math,
152    Menu,
153    Menubar,
154    Menuitem,
155    Menuitemcheckbox,
156    Menuitemradio,
157    Meter,
158    Navigation,
159    None,
160    Note,
161    Option,
162    Paragraph,
163    Presentation,
164    Progressbar,
165    Radio,
166    Radiogroup,
167    Region,
168    Row,
169    Rowgroup,
170    Rowheader,
171    Scrollbar,
172    Search,
173    Searchbox,
174    Separator,
175    Slider,
176    Spinbutton,
177    Status,
178    Strong,
179    Subscript,
180    Superscript,
181    Switch,
182    Tab,
183    Table,
184    Tablist,
185    Tabpanel,
186    Term,
187    Textbox,
188    Time,
189    Timer,
190    Toolbar,
191    Tooltip,
192    Tree,
193    Treegrid,
194    Treeitem,
195}
196
197impl AriaRole {
198    pub fn as_str(&self) -> &'static str {
199        use AriaRole::*;
200        match self {
201            Alert => "alert",
202            Alertdialog => "alertdialog",
203            Application => "application",
204            Article => "article",
205            Banner => "banner",
206            Blockquote => "blockquote",
207            Button => "button",
208            Caption => "caption",
209            Cell => "cell",
210            Checkbox => "checkbox",
211            Code => "code",
212            Columnheader => "columnheader",
213            Combobox => "combobox",
214            Complementary => "complementary",
215            Contentinfo => "contentinfo",
216            Definition => "definition",
217            Deletion => "deletion",
218            Dialog => "dialog",
219            Directory => "directory",
220            Document => "document",
221            Embed => "embed",
222            Figure => "figure",
223            Footer => "footer",
224            Form => "form",
225            Generic => "generic",
226            Grid => "grid",
227            Gridcell => "gridcell",
228            Group => "group",
229            Heading => "heading",
230            Img => "img",
231            Image => "image",
232            Insertion => "insertion",
233            Link => "link",
234            List => "list",
235            Listbox => "listbox",
236            Listitem => "listitem",
237            Log => "log",
238            Main => "main",
239            Marquee => "marquee",
240            Math => "math",
241            Menu => "menu",
242            Menubar => "menubar",
243            Menuitem => "menuitem",
244            Menuitemcheckbox => "menuitemcheckbox",
245            Menuitemradio => "menuitemradio",
246            Meter => "meter",
247            Navigation => "navigation",
248            None => "none",
249            Note => "note",
250            Option => "option",
251            Paragraph => "paragraph",
252            Presentation => "presentation",
253            Progressbar => "progressbar",
254            Radio => "radio",
255            Radiogroup => "radiogroup",
256            Region => "region",
257            Row => "row",
258            Rowgroup => "rowgroup",
259            Rowheader => "rowheader",
260            Scrollbar => "scrollbar",
261            Search => "search",
262            Searchbox => "searchbox",
263            Separator => "separator",
264            Slider => "slider",
265            Spinbutton => "spinbutton",
266            Status => "status",
267            Strong => "strong",
268            Subscript => "subscript",
269            Superscript => "superscript",
270            Switch => "switch",
271            Tab => "tab",
272            Table => "table",
273            Tablist => "tablist",
274            Tabpanel => "tabpanel",
275            Term => "term",
276            Textbox => "textbox",
277            Time => "time",
278            Timer => "timer",
279            Toolbar => "toolbar",
280            Tooltip => "tooltip",
281            Tree => "tree",
282            Treegrid => "treegrid",
283            Treeitem => "treeitem",
284        }
285    }
286}
287
288/// Screenshot image format.
289#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
290pub enum ScreenshotType {
291    #[default]
292    Png,
293    Jpeg,
294    Webp,
295}
296
297/// A cookie, shaped for `add_cookies` / `cookies`.
298#[derive(Debug, Clone, Serialize, Deserialize)]
299pub struct Cookie {
300    pub name: String,
301    pub value: String,
302    /// Either `url` or `domain`+`path` must be supplied.
303    #[serde(skip_serializing_if = "Option::is_none")]
304    pub url: Option<String>,
305    #[serde(skip_serializing_if = "Option::is_none")]
306    pub domain: Option<String>,
307    #[serde(skip_serializing_if = "Option::is_none")]
308    pub path: Option<String>,
309    #[serde(skip_serializing_if = "Option::is_none")]
310    pub expires: Option<f64>,
311    #[serde(skip_serializing_if = "Option::is_none")]
312    pub http_only: Option<bool>,
313    #[serde(skip_serializing_if = "Option::is_none")]
314    pub secure: Option<bool>,
315    #[serde(skip_serializing_if = "Option::is_none")]
316    pub same_site: Option<String>,
317}
318
319/// A name/value pair, e.g. a single localStorage entry.
320#[derive(Debug, Clone, Serialize, Deserialize)]
321pub struct NameValue {
322    pub name: String,
323    pub value: String,
324}
325
326/// localStorage entries grouped by origin.
327#[derive(Debug, Clone, Serialize, Deserialize)]
328#[allow(non_snake_case)]
329pub struct OriginStorage {
330    pub origin: String,
331    // Named to match the JS/Playwright `localStorage` key.
332    pub localStorage: Vec<NameValue>,
333}
334
335/// A serializable snapshot of a context's storage: cookies plus per-origin
336/// localStorage. Mirrors Playwright's `storageState` shape. Cookies stay as
337/// `serde_json::Value` (the raw CDP/`Storage` cookie objects) so they can be
338/// fed straight back to `set_storage_state`.
339#[derive(Debug, Clone, Serialize, Deserialize)]
340pub struct StorageState {
341    pub cookies: Vec<Value>,
342    pub origins: Vec<OriginStorage>,
343}
344
345/// Extra HTTP headers map.
346pub type Headers = HashMap<String, String>;
347
348/// The source location of a [`ConsoleMessage`], mirroring the top frame of a
349/// CDP `Runtime.consoleAPICalled` `stackTrace.callFrames[0]`.
350///
351/// All fields are optional since CDP omits them when no stack trace is
352/// available (e.g. for some console methods or contexts without a script).
353#[derive(Debug, Clone, Default)]
354pub struct ConsoleMessageLocation {
355    /// The script URL where the call originated.
356    pub url: Option<String>,
357    /// 1-based line number.
358    pub line_number: Option<i64>,
359    /// 1-based column number.
360    pub column_number: Option<i64>,
361}
362
363impl ConsoleMessageLocation {
364    pub fn url(&self) -> Option<&str> {
365        self.url.as_deref()
366    }
367    pub fn line_number(&self) -> Option<i64> {
368        self.line_number
369    }
370    pub fn column_number(&self) -> Option<i64> {
371        self.column_number
372    }
373}
374
375/// A console message captured from the page.
376#[derive(Debug, Clone)]
377pub struct ConsoleMessage {
378    pub text: String,
379    pub r#type: String,
380    /// The source location, parsed from the call's `stackTrace.callFrames[0]`.
381    /// `None` when CDP did not report a stack trace.
382    pub location: Option<ConsoleMessageLocation>,
383}
384
385impl ConsoleMessage {
386    pub fn text(&self) -> &str {
387        &self.text
388    }
389    pub fn r#type(&self) -> &str {
390        &self.r#type
391    }
392    pub fn location(&self) -> Option<&ConsoleMessageLocation> {
393        self.location.as_ref()
394    }
395}
396
397/// An error thrown from page JavaScript (e.g. an unhandled exception or a
398/// rejected promise observed via `Runtime.exceptionThrown`).
399///
400/// Self-contained DTO mirroring the useful fields of CDP's
401/// `Runtime.exceptionThrown` payload.
402#[derive(Debug, Clone, Default)]
403pub struct WebError {
404    /// Top-level message, if any.
405    pub message: Option<String>,
406    /// The stack trace, if any.
407    pub stack: Option<String>,
408    /// The script where it originated, if known.
409    pub url: Option<String>,
410    /// 1-based line/column, if known.
411    pub line_number: Option<i64>,
412    pub column_number: Option<i64>,
413}
414
415impl WebError {
416    pub fn new() -> Self {
417        Self::default()
418    }
419}
420
421/// TLS/security details for a response, mirroring CDP's
422/// `Security/securityStateChanged` / `Network.responseReceived` security info.
423///
424/// Self-contained DTO; all fields optional since CDP omits any that don't apply
425/// to a given connection.
426#[derive(Debug, Clone, Default)]
427pub struct SecurityDetails {
428    /// TLS protocol (e.g. `"TLS 1.3"`), if known.
429    pub protocol: Option<String>,
430    /// Cipher name, if known.
431    pub cipher: Option<String>,
432    /// Issuer of the server certificate, if known.
433    pub issuer: Option<String>,
434    /// Subject (CN) of the server certificate, if known.
435    pub subject_name: Option<String>,
436    /// Unix-epoch seconds at which the certificate is valid from, if known.
437    pub valid_from: Option<f64>,
438    /// Unix-epoch seconds at which the certificate expires, if known.
439    pub valid_to: Option<f64>,
440}
441
442impl SecurityDetails {
443    pub fn new() -> Self {
444        Self::default()
445    }
446
447    /// Build a `SecurityDetails` from a CDP `SecurityDetails` JSON object (as
448    /// carried by `Network.responseReceivedExtraInfo` /
449    /// `Network.responseReceived`). Unknown shapes yield all-`None`.
450    pub fn from_cdp(value: &Value) -> Self {
451        let get = |k: &str| value.get(k).and_then(|v| v.as_str()).map(String::from);
452        Self {
453            protocol: get("protocol"),
454            cipher: get("cipher"),
455            issuer: get("issuer"),
456            subject_name: get("subjectName").or_else(|| get("subject_name")),
457            valid_from: value.get("validFrom").and_then(|v| v.as_f64()),
458            valid_to: value.get("validTo").and_then(|v| v.as_f64()),
459        }
460    }
461}