Skip to main content

playwright_rs/protocol/
click.rs

1// Click options and related types
2//
3// Provides configuration for click and dblclick actions, matching Playwright's API.
4
5use serde::Serialize;
6
7/// Mouse button for click actions
8///
9/// # Example
10///
11/// ```ignore
12/// use playwright_rs::protocol::click::MouseButton;
13///
14/// let button = MouseButton::Right;
15/// ```
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
17#[serde(rename_all = "lowercase")]
18pub enum MouseButton {
19    /// Left mouse button (default)
20    Left,
21    /// Right mouse button
22    Right,
23    /// Middle mouse button
24    Middle,
25}
26
27/// Keyboard modifier keys
28///
29/// # Example
30///
31/// ```ignore
32/// use playwright_rs::protocol::click::KeyboardModifier;
33///
34/// let modifiers = vec![KeyboardModifier::Shift, KeyboardModifier::Control];
35/// ```
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
37pub enum KeyboardModifier {
38    /// Alt key
39    Alt,
40    /// Control key
41    Control,
42    /// Meta key (Command on macOS, Windows key on Windows)
43    Meta,
44    /// Shift key
45    Shift,
46    /// Control on Windows/Linux, Meta on macOS
47    ControlOrMeta,
48}
49
50/// Position for click actions
51///
52/// Coordinates are relative to the top-left corner of the element's padding box.
53///
54/// # Example
55///
56/// ```ignore
57/// use playwright_rs::protocol::click::Position;
58///
59/// let position = Position { x: 10.0, y: 20.0 };
60/// ```
61#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
62pub struct Position {
63    /// X coordinate
64    pub x: f64,
65    /// Y coordinate
66    pub y: f64,
67}
68
69/// Click options
70///
71/// Configuration options for click and dblclick actions.
72///
73/// Use the builder pattern to construct options:
74///
75/// # Example
76///
77/// ```ignore
78/// use playwright_rs::protocol::click::{ClickOptions, MouseButton, KeyboardModifier, Position};
79///
80/// // Right-click with modifiers
81/// let options = ClickOptions::builder()
82///     .button(MouseButton::Right)
83///     .modifiers(vec![KeyboardModifier::Shift])
84///     .build();
85///
86/// // Click at specific position
87/// let options = ClickOptions::builder()
88///     .position(Position { x: 10.0, y: 20.0 })
89///     .build();
90///
91/// // Trial run (actionability checks only)
92/// let options = ClickOptions::builder()
93///     .trial(true)
94///     .build();
95/// ```
96///
97/// See: <https://playwright.dev/docs/api/class-locator#locator-click>
98#[derive(Debug, Clone, Default)]
99pub struct ClickOptions {
100    /// Mouse button to click (left, right, middle)
101    pub button: Option<MouseButton>,
102    /// Number of clicks (for multi-click)
103    pub click_count: Option<u32>,
104    /// Time to wait between mousedown and mouseup in milliseconds
105    pub delay: Option<f64>,
106    /// Whether to bypass actionability checks
107    pub force: Option<bool>,
108    /// Modifier keys to press during click
109    pub modifiers: Option<Vec<KeyboardModifier>>,
110    /// Don't wait for navigation after click
111    pub no_wait_after: Option<bool>,
112    /// Position to click relative to element top-left corner
113    pub position: Option<Position>,
114    /// Maximum time in milliseconds
115    pub timeout: Option<f64>,
116    /// Perform actionability checks without clicking
117    pub trial: Option<bool>,
118}
119
120impl ClickOptions {
121    /// Create a new builder for ClickOptions
122    pub fn builder() -> ClickOptionsBuilder {
123        ClickOptionsBuilder::default()
124    }
125
126    /// Convert options to JSON value for protocol
127    pub(crate) fn to_json(&self) -> serde_json::Value {
128        let mut json = serde_json::json!({});
129
130        if let Some(button) = &self.button {
131            json["button"] =
132                serde_json::to_value(button).expect("serialization of MouseButton cannot fail");
133        }
134
135        if let Some(click_count) = self.click_count {
136            json["clickCount"] = serde_json::json!(click_count);
137        }
138
139        if let Some(delay) = self.delay {
140            json["delay"] = serde_json::json!(delay);
141        }
142
143        if let Some(force) = self.force {
144            json["force"] = serde_json::json!(force);
145        }
146
147        if let Some(modifiers) = &self.modifiers {
148            json["modifiers"] =
149                serde_json::to_value(modifiers).expect("serialization of modifiers cannot fail");
150        }
151
152        if let Some(no_wait_after) = self.no_wait_after {
153            json["noWaitAfter"] = serde_json::json!(no_wait_after);
154        }
155
156        if let Some(position) = &self.position {
157            json["position"] =
158                serde_json::to_value(position).expect("serialization of position cannot fail");
159        }
160
161        // Timeout is required in Playwright 1.56.1+
162        if let Some(timeout) = self.timeout {
163            json["timeout"] = serde_json::json!(timeout);
164        } else {
165            json["timeout"] = serde_json::json!(crate::DEFAULT_TIMEOUT_MS);
166        }
167
168        if let Some(trial) = self.trial {
169            json["trial"] = serde_json::json!(trial);
170        }
171
172        json
173    }
174}
175
176/// Builder for ClickOptions
177///
178/// Provides a fluent API for constructing click options.
179#[derive(Debug, Clone, Default)]
180pub struct ClickOptionsBuilder {
181    button: Option<MouseButton>,
182    click_count: Option<u32>,
183    delay: Option<f64>,
184    force: Option<bool>,
185    modifiers: Option<Vec<KeyboardModifier>>,
186    no_wait_after: Option<bool>,
187    position: Option<Position>,
188    timeout: Option<f64>,
189    trial: Option<bool>,
190}
191
192impl ClickOptionsBuilder {
193    /// Set the mouse button to click
194    pub fn button(mut self, button: MouseButton) -> Self {
195        self.button = Some(button);
196        self
197    }
198
199    /// Set the number of clicks
200    pub fn click_count(mut self, click_count: u32) -> Self {
201        self.click_count = Some(click_count);
202        self
203    }
204
205    /// Set delay between mousedown and mouseup in milliseconds
206    pub fn delay(mut self, delay: f64) -> Self {
207        self.delay = Some(delay);
208        self
209    }
210
211    /// Bypass actionability checks
212    pub fn force(mut self, force: bool) -> Self {
213        self.force = Some(force);
214        self
215    }
216
217    /// Set modifier keys to press during click
218    pub fn modifiers(mut self, modifiers: Vec<KeyboardModifier>) -> Self {
219        self.modifiers = Some(modifiers);
220        self
221    }
222
223    /// Don't wait for navigation after click
224    pub fn no_wait_after(mut self, no_wait_after: bool) -> Self {
225        self.no_wait_after = Some(no_wait_after);
226        self
227    }
228
229    /// Set position to click relative to element top-left corner
230    pub fn position(mut self, position: Position) -> Self {
231        self.position = Some(position);
232        self
233    }
234
235    /// Set timeout in milliseconds
236    pub fn timeout(mut self, timeout: f64) -> Self {
237        self.timeout = Some(timeout);
238        self
239    }
240
241    /// Perform actionability checks without clicking
242    pub fn trial(mut self, trial: bool) -> Self {
243        self.trial = Some(trial);
244        self
245    }
246
247    /// Build the ClickOptions
248    pub fn build(self) -> ClickOptions {
249        ClickOptions {
250            button: self.button,
251            click_count: self.click_count,
252            delay: self.delay,
253            force: self.force,
254            modifiers: self.modifiers,
255            no_wait_after: self.no_wait_after,
256            position: self.position,
257            timeout: self.timeout,
258            trial: self.trial,
259        }
260    }
261}
262
263#[cfg(test)]
264mod tests {
265    use super::*;
266
267    #[test]
268    fn test_mouse_button_serialization() {
269        assert_eq!(
270            serde_json::to_string(&MouseButton::Left).unwrap(),
271            "\"left\""
272        );
273        assert_eq!(
274            serde_json::to_string(&MouseButton::Right).unwrap(),
275            "\"right\""
276        );
277        assert_eq!(
278            serde_json::to_string(&MouseButton::Middle).unwrap(),
279            "\"middle\""
280        );
281    }
282
283    #[test]
284    fn test_keyboard_modifier_serialization() {
285        assert_eq!(
286            serde_json::to_string(&KeyboardModifier::Alt).unwrap(),
287            "\"Alt\""
288        );
289        assert_eq!(
290            serde_json::to_string(&KeyboardModifier::Control).unwrap(),
291            "\"Control\""
292        );
293        assert_eq!(
294            serde_json::to_string(&KeyboardModifier::Meta).unwrap(),
295            "\"Meta\""
296        );
297        assert_eq!(
298            serde_json::to_string(&KeyboardModifier::Shift).unwrap(),
299            "\"Shift\""
300        );
301        assert_eq!(
302            serde_json::to_string(&KeyboardModifier::ControlOrMeta).unwrap(),
303            "\"ControlOrMeta\""
304        );
305    }
306
307    #[test]
308    fn test_builder_button() {
309        let options = ClickOptions::builder().button(MouseButton::Right).build();
310
311        let json = options.to_json();
312        assert_eq!(json["button"], "right");
313    }
314
315    #[test]
316    fn test_builder_click_count() {
317        let options = ClickOptions::builder().click_count(2).build();
318
319        let json = options.to_json();
320        assert_eq!(json["clickCount"], 2);
321    }
322
323    #[test]
324    fn test_builder_delay() {
325        let options = ClickOptions::builder().delay(100.0).build();
326
327        let json = options.to_json();
328        assert_eq!(json["delay"], 100.0);
329    }
330
331    #[test]
332    fn test_builder_force() {
333        let options = ClickOptions::builder().force(true).build();
334
335        let json = options.to_json();
336        assert_eq!(json["force"], true);
337    }
338
339    #[test]
340    fn test_builder_modifiers() {
341        let options = ClickOptions::builder()
342            .modifiers(vec![KeyboardModifier::Shift, KeyboardModifier::Control])
343            .build();
344
345        let json = options.to_json();
346        assert_eq!(json["modifiers"], serde_json::json!(["Shift", "Control"]));
347    }
348
349    #[test]
350    fn test_builder_position() {
351        let position = Position { x: 10.0, y: 20.0 };
352        let options = ClickOptions::builder().position(position).build();
353
354        let json = options.to_json();
355        assert_eq!(json["position"]["x"], 10.0);
356        assert_eq!(json["position"]["y"], 20.0);
357    }
358
359    #[test]
360    fn test_builder_timeout() {
361        let options = ClickOptions::builder().timeout(5000.0).build();
362
363        let json = options.to_json();
364        assert_eq!(json["timeout"], 5000.0);
365    }
366
367    #[test]
368    fn test_builder_trial() {
369        let options = ClickOptions::builder().trial(true).build();
370
371        let json = options.to_json();
372        assert_eq!(json["trial"], true);
373    }
374
375    #[test]
376    fn test_builder_multiple_options() {
377        let options = ClickOptions::builder()
378            .button(MouseButton::Right)
379            .modifiers(vec![KeyboardModifier::Shift])
380            .position(Position { x: 5.0, y: 10.0 })
381            .force(true)
382            .timeout(3000.0)
383            .build();
384
385        let json = options.to_json();
386        assert_eq!(json["button"], "right");
387        assert_eq!(json["modifiers"], serde_json::json!(["Shift"]));
388        assert_eq!(json["position"]["x"], 5.0);
389        assert_eq!(json["position"]["y"], 10.0);
390        assert_eq!(json["force"], true);
391        assert_eq!(json["timeout"], 3000.0);
392    }
393}