Skip to main content

playwright_rs/protocol/
tap.rs

1// Tap options and related types
2//
3// Provides configuration for tap actions, matching Playwright's API.
4// Tap is very similar to click but sends touch events instead of mouse events.
5
6use crate::protocol::action_options::Scroll;
7use crate::protocol::click::{KeyboardModifier, Position};
8
9/// Tap options
10///
11/// Configuration options for tap actions (touch-screen taps).
12///
13/// Use the builder pattern to construct options:
14///
15/// # Example
16///
17/// ```no_run
18/// use playwright_rs::TapOptions;
19///
20/// // Tap with force (bypass actionability checks)
21/// let options = TapOptions::builder()
22///     .force(true)
23///     .build();
24///
25/// // Trial run (actionability checks only, don't actually tap)
26/// let options = TapOptions::builder()
27///     .trial(true)
28///     .build();
29/// ```
30///
31/// See: <https://playwright.dev/docs/api/class-locator#locator-tap>
32#[derive(Debug, Clone, Default, serde::Serialize)]
33#[serde(rename_all = "camelCase")]
34#[non_exhaustive]
35pub struct TapOptions {
36    /// Whether to bypass actionability checks
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub force: Option<bool>,
39    /// Modifier keys to press during tap
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub modifiers: Option<Vec<KeyboardModifier>>,
42    /// Position to tap relative to element top-left corner
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub position: Option<Position>,
45    /// Maximum time in milliseconds
46    #[serde(serialize_with = "crate::protocol::serialize_timeout_or_default")]
47    pub timeout: Option<f64>,
48    /// Perform actionability checks without tapping
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub trial: Option<bool>,
51    /// Whether the action may scroll the element into view first
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub scroll: Option<Scroll>,
54}
55
56impl TapOptions {
57    /// Create a new builder for TapOptions
58    pub fn builder() -> TapOptionsBuilder {
59        TapOptionsBuilder::default()
60    }
61
62    /// Convert options to JSON value for protocol
63    pub(crate) fn to_json(&self) -> serde_json::Value {
64        serde_json::to_value(self).expect("TapOptions serialization cannot fail")
65    }
66}
67
68/// Builder for TapOptions
69///
70/// Provides a fluent API for constructing tap options.
71#[derive(Debug, Clone, Default)]
72pub struct TapOptionsBuilder {
73    force: Option<bool>,
74    modifiers: Option<Vec<KeyboardModifier>>,
75    position: Option<Position>,
76    timeout: Option<f64>,
77    trial: Option<bool>,
78    scroll: Option<Scroll>,
79}
80
81impl TapOptionsBuilder {
82    /// Bypass actionability checks
83    pub fn force(mut self, force: bool) -> Self {
84        self.force = Some(force);
85        self
86    }
87
88    /// Set modifier keys to press during tap
89    pub fn modifiers(mut self, modifiers: Vec<KeyboardModifier>) -> Self {
90        self.modifiers = Some(modifiers);
91        self
92    }
93
94    /// Set position to tap relative to element top-left corner
95    pub fn position(mut self, position: Position) -> Self {
96        self.position = Some(position);
97        self
98    }
99
100    /// Set timeout in milliseconds
101    pub fn timeout(mut self, timeout: f64) -> Self {
102        self.timeout = Some(timeout);
103        self
104    }
105
106    /// Perform actionability checks without tapping
107    pub fn trial(mut self, trial: bool) -> Self {
108        self.trial = Some(trial);
109        self
110    }
111
112    /// Opt out of scrolling the element into view (`Scroll::None`), or keep
113    /// Playwright's default (`Scroll::Auto`)
114    pub fn scroll(mut self, scroll: Scroll) -> Self {
115        self.scroll = Some(scroll);
116        self
117    }
118
119    /// Build the TapOptions
120    pub fn build(self) -> TapOptions {
121        TapOptions {
122            force: self.force,
123            modifiers: self.modifiers,
124            position: self.position,
125            timeout: self.timeout,
126            trial: self.trial,
127            scroll: self.scroll,
128        }
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135
136    #[test]
137    fn test_tap_options_default() {
138        let options = TapOptions::builder().build();
139        let json = options.to_json();
140        // timeout has a default value
141        assert!(json["timeout"].is_number());
142        // other fields are absent
143        assert!(json.get("force").is_none());
144        assert!(json.get("trial").is_none());
145    }
146
147    #[test]
148    fn test_tap_options_force() {
149        let options = TapOptions::builder().force(true).build();
150        let json = options.to_json();
151        assert_eq!(json["force"], true);
152    }
153
154    #[test]
155    fn test_tap_options_timeout() {
156        let options = TapOptions::builder().timeout(5000.0).build();
157        let json = options.to_json();
158        assert_eq!(json["timeout"], 5000.0);
159    }
160
161    #[test]
162    fn test_tap_options_trial() {
163        let options = TapOptions::builder().trial(true).build();
164        let json = options.to_json();
165        assert_eq!(json["trial"], true);
166    }
167
168    #[test]
169    fn test_tap_options_position() {
170        let options = TapOptions::builder()
171            .position(Position { x: 10.0, y: 20.0 })
172            .build();
173        let json = options.to_json();
174        assert_eq!(json["position"]["x"], 10.0);
175        assert_eq!(json["position"]["y"], 20.0);
176    }
177}