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