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, serde::Serialize)]
101#[serde(rename_all = "camelCase")]
102#[non_exhaustive]
103pub struct ClickOptions {
104    /// Mouse button to click (left, right, middle)
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub button: Option<MouseButton>,
107    /// Number of clicks (for multi-click)
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub click_count: Option<u32>,
110    /// Time to wait between mousedown and mouseup in milliseconds
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub delay: Option<f64>,
113    /// Whether to bypass actionability checks
114    #[serde(skip_serializing_if = "Option::is_none")]
115    pub force: Option<bool>,
116    /// Modifier keys to press during click
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub modifiers: Option<Vec<KeyboardModifier>>,
119    /// Don't wait for navigation after click
120    #[serde(skip_serializing_if = "Option::is_none")]
121    pub no_wait_after: Option<bool>,
122    /// Position to click relative to element top-left corner
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub position: Option<Position>,
125    /// Maximum time in milliseconds. Serializes to the default timeout when
126    /// unset (Playwright 1.56.1+ requires the field to be present).
127    #[serde(serialize_with = "crate::protocol::serialize_timeout_or_default")]
128    pub timeout: Option<f64>,
129    /// Perform actionability checks without clicking
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub trial: Option<bool>,
132}
133
134impl ClickOptions {
135    /// Create a new builder for ClickOptions
136    pub fn builder() -> ClickOptionsBuilder {
137        ClickOptionsBuilder::default()
138    }
139
140    /// Convert options to JSON value for protocol
141    pub(crate) fn to_json(&self) -> serde_json::Value {
142        serde_json::to_value(self).expect("ClickOptions serialization cannot fail")
143    }
144}
145
146/// Builder for ClickOptions
147///
148/// Provides a fluent API for constructing click options.
149#[derive(Debug, Clone, Default)]
150pub struct ClickOptionsBuilder {
151    button: Option<MouseButton>,
152    click_count: Option<u32>,
153    delay: Option<f64>,
154    force: Option<bool>,
155    modifiers: Option<Vec<KeyboardModifier>>,
156    no_wait_after: Option<bool>,
157    position: Option<Position>,
158    timeout: Option<f64>,
159    trial: Option<bool>,
160}
161
162impl ClickOptionsBuilder {
163    /// Set the mouse button to click
164    pub fn button(mut self, button: MouseButton) -> Self {
165        self.button = Some(button);
166        self
167    }
168
169    /// Set the number of clicks
170    pub fn click_count(mut self, click_count: u32) -> Self {
171        self.click_count = Some(click_count);
172        self
173    }
174
175    /// Set delay between mousedown and mouseup in milliseconds
176    pub fn delay(mut self, delay: f64) -> Self {
177        self.delay = Some(delay);
178        self
179    }
180
181    /// Bypass actionability checks
182    pub fn force(mut self, force: bool) -> Self {
183        self.force = Some(force);
184        self
185    }
186
187    /// Set modifier keys to press during click
188    pub fn modifiers(mut self, modifiers: Vec<KeyboardModifier>) -> Self {
189        self.modifiers = Some(modifiers);
190        self
191    }
192
193    /// Don't wait for navigation after click
194    pub fn no_wait_after(mut self, no_wait_after: bool) -> Self {
195        self.no_wait_after = Some(no_wait_after);
196        self
197    }
198
199    /// Set position to click relative to element top-left corner
200    pub fn position(mut self, position: Position) -> Self {
201        self.position = Some(position);
202        self
203    }
204
205    /// Set timeout in milliseconds
206    pub fn timeout(mut self, timeout: f64) -> Self {
207        self.timeout = Some(timeout);
208        self
209    }
210
211    /// Perform actionability checks without clicking
212    pub fn trial(mut self, trial: bool) -> Self {
213        self.trial = Some(trial);
214        self
215    }
216
217    /// Build the ClickOptions
218    pub fn build(self) -> ClickOptions {
219        ClickOptions {
220            button: self.button,
221            click_count: self.click_count,
222            delay: self.delay,
223            force: self.force,
224            modifiers: self.modifiers,
225            no_wait_after: self.no_wait_after,
226            position: self.position,
227            timeout: self.timeout,
228            trial: self.trial,
229        }
230    }
231}
232
233#[cfg(test)]
234mod tests {
235    use super::*;
236
237    #[test]
238    fn test_mouse_button_serialization() {
239        assert_eq!(
240            serde_json::to_string(&MouseButton::Left).unwrap(),
241            "\"left\""
242        );
243        assert_eq!(
244            serde_json::to_string(&MouseButton::Right).unwrap(),
245            "\"right\""
246        );
247        assert_eq!(
248            serde_json::to_string(&MouseButton::Middle).unwrap(),
249            "\"middle\""
250        );
251    }
252
253    #[test]
254    fn test_keyboard_modifier_serialization() {
255        assert_eq!(
256            serde_json::to_string(&KeyboardModifier::Alt).unwrap(),
257            "\"Alt\""
258        );
259        assert_eq!(
260            serde_json::to_string(&KeyboardModifier::Control).unwrap(),
261            "\"Control\""
262        );
263        assert_eq!(
264            serde_json::to_string(&KeyboardModifier::Meta).unwrap(),
265            "\"Meta\""
266        );
267        assert_eq!(
268            serde_json::to_string(&KeyboardModifier::Shift).unwrap(),
269            "\"Shift\""
270        );
271        assert_eq!(
272            serde_json::to_string(&KeyboardModifier::ControlOrMeta).unwrap(),
273            "\"ControlOrMeta\""
274        );
275    }
276
277    #[test]
278    fn test_builder_button() {
279        let options = ClickOptions::builder().button(MouseButton::Right).build();
280
281        let json = options.to_json();
282        assert_eq!(json["button"], "right");
283    }
284
285    #[test]
286    fn test_builder_click_count() {
287        let options = ClickOptions::builder().click_count(2).build();
288
289        let json = options.to_json();
290        assert_eq!(json["clickCount"], 2);
291    }
292
293    #[test]
294    fn test_builder_delay() {
295        let options = ClickOptions::builder().delay(100.0).build();
296
297        let json = options.to_json();
298        assert_eq!(json["delay"], 100.0);
299    }
300
301    #[test]
302    fn test_builder_force() {
303        let options = ClickOptions::builder().force(true).build();
304
305        let json = options.to_json();
306        assert_eq!(json["force"], true);
307    }
308
309    #[test]
310    fn test_builder_modifiers() {
311        let options = ClickOptions::builder()
312            .modifiers(vec![KeyboardModifier::Shift, KeyboardModifier::Control])
313            .build();
314
315        let json = options.to_json();
316        assert_eq!(json["modifiers"], serde_json::json!(["Shift", "Control"]));
317    }
318
319    #[test]
320    fn test_builder_position() {
321        let position = Position { x: 10.0, y: 20.0 };
322        let options = ClickOptions::builder().position(position).build();
323
324        let json = options.to_json();
325        assert_eq!(json["position"]["x"], 10.0);
326        assert_eq!(json["position"]["y"], 20.0);
327    }
328
329    #[test]
330    fn test_builder_timeout() {
331        let options = ClickOptions::builder().timeout(5000.0).build();
332
333        let json = options.to_json();
334        assert_eq!(json["timeout"], 5000.0);
335    }
336
337    #[test]
338    fn test_builder_trial() {
339        let options = ClickOptions::builder().trial(true).build();
340
341        let json = options.to_json();
342        assert_eq!(json["trial"], true);
343    }
344
345    #[test]
346    fn test_builder_multiple_options() {
347        let options = ClickOptions::builder()
348            .button(MouseButton::Right)
349            .modifiers(vec![KeyboardModifier::Shift])
350            .position(Position { x: 5.0, y: 10.0 })
351            .force(true)
352            .timeout(3000.0)
353            .build();
354
355        let json = options.to_json();
356        assert_eq!(json["button"], "right");
357        assert_eq!(json["modifiers"], serde_json::json!(["Shift"]));
358        assert_eq!(json["position"]["x"], 5.0);
359        assert_eq!(json["position"]["y"], 10.0);
360        assert_eq!(json["force"], true);
361        assert_eq!(json["timeout"], 3000.0);
362    }
363}