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