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"] = serde_json::to_value(button).unwrap();
132        }
133
134        if let Some(click_count) = self.click_count {
135            json["clickCount"] = serde_json::json!(click_count);
136        }
137
138        if let Some(delay) = self.delay {
139            json["delay"] = serde_json::json!(delay);
140        }
141
142        if let Some(force) = self.force {
143            json["force"] = serde_json::json!(force);
144        }
145
146        if let Some(modifiers) = &self.modifiers {
147            json["modifiers"] = serde_json::to_value(modifiers).unwrap();
148        }
149
150        if let Some(no_wait_after) = self.no_wait_after {
151            json["noWaitAfter"] = serde_json::json!(no_wait_after);
152        }
153
154        if let Some(position) = &self.position {
155            json["position"] = serde_json::to_value(position).unwrap();
156        }
157
158        // Timeout is required in Playwright 1.56.1+
159        if let Some(timeout) = self.timeout {
160            json["timeout"] = serde_json::json!(timeout);
161        } else {
162            json["timeout"] = serde_json::json!(crate::DEFAULT_TIMEOUT_MS);
163        }
164
165        if let Some(trial) = self.trial {
166            json["trial"] = serde_json::json!(trial);
167        }
168
169        json
170    }
171}
172
173/// Builder for ClickOptions
174///
175/// Provides a fluent API for constructing click options.
176#[derive(Debug, Clone, Default)]
177pub struct ClickOptionsBuilder {
178    button: Option<MouseButton>,
179    click_count: Option<u32>,
180    delay: Option<f64>,
181    force: Option<bool>,
182    modifiers: Option<Vec<KeyboardModifier>>,
183    no_wait_after: Option<bool>,
184    position: Option<Position>,
185    timeout: Option<f64>,
186    trial: Option<bool>,
187}
188
189impl ClickOptionsBuilder {
190    /// Set the mouse button to click
191    pub fn button(mut self, button: MouseButton) -> Self {
192        self.button = Some(button);
193        self
194    }
195
196    /// Set the number of clicks
197    pub fn click_count(mut self, click_count: u32) -> Self {
198        self.click_count = Some(click_count);
199        self
200    }
201
202    /// Set delay between mousedown and mouseup in milliseconds
203    pub fn delay(mut self, delay: f64) -> Self {
204        self.delay = Some(delay);
205        self
206    }
207
208    /// Bypass actionability checks
209    pub fn force(mut self, force: bool) -> Self {
210        self.force = Some(force);
211        self
212    }
213
214    /// Set modifier keys to press during click
215    pub fn modifiers(mut self, modifiers: Vec<KeyboardModifier>) -> Self {
216        self.modifiers = Some(modifiers);
217        self
218    }
219
220    /// Don't wait for navigation after click
221    pub fn no_wait_after(mut self, no_wait_after: bool) -> Self {
222        self.no_wait_after = Some(no_wait_after);
223        self
224    }
225
226    /// Set position to click relative to element top-left corner
227    pub fn position(mut self, position: Position) -> Self {
228        self.position = Some(position);
229        self
230    }
231
232    /// Set timeout in milliseconds
233    pub fn timeout(mut self, timeout: f64) -> Self {
234        self.timeout = Some(timeout);
235        self
236    }
237
238    /// Perform actionability checks without clicking
239    pub fn trial(mut self, trial: bool) -> Self {
240        self.trial = Some(trial);
241        self
242    }
243
244    /// Build the ClickOptions
245    pub fn build(self) -> ClickOptions {
246        ClickOptions {
247            button: self.button,
248            click_count: self.click_count,
249            delay: self.delay,
250            force: self.force,
251            modifiers: self.modifiers,
252            no_wait_after: self.no_wait_after,
253            position: self.position,
254            timeout: self.timeout,
255            trial: self.trial,
256        }
257    }
258}
259
260#[cfg(test)]
261mod tests {
262    use super::*;
263
264    #[test]
265    fn test_mouse_button_serialization() {
266        assert_eq!(
267            serde_json::to_string(&MouseButton::Left).unwrap(),
268            "\"left\""
269        );
270        assert_eq!(
271            serde_json::to_string(&MouseButton::Right).unwrap(),
272            "\"right\""
273        );
274        assert_eq!(
275            serde_json::to_string(&MouseButton::Middle).unwrap(),
276            "\"middle\""
277        );
278    }
279
280    #[test]
281    fn test_keyboard_modifier_serialization() {
282        assert_eq!(
283            serde_json::to_string(&KeyboardModifier::Alt).unwrap(),
284            "\"Alt\""
285        );
286        assert_eq!(
287            serde_json::to_string(&KeyboardModifier::Control).unwrap(),
288            "\"Control\""
289        );
290        assert_eq!(
291            serde_json::to_string(&KeyboardModifier::Meta).unwrap(),
292            "\"Meta\""
293        );
294        assert_eq!(
295            serde_json::to_string(&KeyboardModifier::Shift).unwrap(),
296            "\"Shift\""
297        );
298        assert_eq!(
299            serde_json::to_string(&KeyboardModifier::ControlOrMeta).unwrap(),
300            "\"ControlOrMeta\""
301        );
302    }
303
304    #[test]
305    fn test_builder_button() {
306        let options = ClickOptions::builder().button(MouseButton::Right).build();
307
308        let json = options.to_json();
309        assert_eq!(json["button"], "right");
310    }
311
312    #[test]
313    fn test_builder_click_count() {
314        let options = ClickOptions::builder().click_count(2).build();
315
316        let json = options.to_json();
317        assert_eq!(json["clickCount"], 2);
318    }
319
320    #[test]
321    fn test_builder_delay() {
322        let options = ClickOptions::builder().delay(100.0).build();
323
324        let json = options.to_json();
325        assert_eq!(json["delay"], 100.0);
326    }
327
328    #[test]
329    fn test_builder_force() {
330        let options = ClickOptions::builder().force(true).build();
331
332        let json = options.to_json();
333        assert_eq!(json["force"], true);
334    }
335
336    #[test]
337    fn test_builder_modifiers() {
338        let options = ClickOptions::builder()
339            .modifiers(vec![KeyboardModifier::Shift, KeyboardModifier::Control])
340            .build();
341
342        let json = options.to_json();
343        assert_eq!(json["modifiers"], serde_json::json!(["Shift", "Control"]));
344    }
345
346    #[test]
347    fn test_builder_position() {
348        let position = Position { x: 10.0, y: 20.0 };
349        let options = ClickOptions::builder().position(position).build();
350
351        let json = options.to_json();
352        assert_eq!(json["position"]["x"], 10.0);
353        assert_eq!(json["position"]["y"], 20.0);
354    }
355
356    #[test]
357    fn test_builder_timeout() {
358        let options = ClickOptions::builder().timeout(5000.0).build();
359
360        let json = options.to_json();
361        assert_eq!(json["timeout"], 5000.0);
362    }
363
364    #[test]
365    fn test_builder_trial() {
366        let options = ClickOptions::builder().trial(true).build();
367
368        let json = options.to_json();
369        assert_eq!(json["trial"], true);
370    }
371
372    #[test]
373    fn test_builder_multiple_options() {
374        let options = ClickOptions::builder()
375            .button(MouseButton::Right)
376            .modifiers(vec![KeyboardModifier::Shift])
377            .position(Position { x: 5.0, y: 10.0 })
378            .force(true)
379            .timeout(3000.0)
380            .build();
381
382        let json = options.to_json();
383        assert_eq!(json["button"], "right");
384        assert_eq!(json["modifiers"], serde_json::json!(["Shift"]));
385        assert_eq!(json["position"]["x"], 5.0);
386        assert_eq!(json["position"]["y"], 10.0);
387        assert_eq!(json["force"], true);
388        assert_eq!(json["timeout"], 3000.0);
389    }
390}