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