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