Skip to main content

playwright_rs/protocol/
drag_to.rs

1// DragToOptions and related types
2//
3// Provides configuration for drag_to actions, matching Playwright's API.
4
5use crate::protocol::click::Position;
6
7/// Options for [`Locator::drag_to()`](crate::protocol::Locator::drag_to).
8///
9/// Configuration for dragging a source element onto a target element.
10///
11/// Use the builder pattern to construct options:
12///
13/// # Example
14///
15/// ```no_run
16/// use playwright_rs::{DragToOptions, Position};
17///
18/// // Drag with custom source and target positions
19/// let options = DragToOptions::builder()
20///     .source_position(Position { x: 10.0, y: 10.0 })
21///     .target_position(Position { x: 60.0, y: 60.0 })
22///     .build();
23///
24/// // Force drag (bypass actionability checks)
25/// let options = DragToOptions::builder()
26///     .force(true)
27///     .build();
28///
29/// // Trial run (actionability checks only, don't actually drag)
30/// let options = DragToOptions::builder()
31///     .trial(true)
32///     .build();
33/// ```
34///
35/// See: <https://playwright.dev/docs/api/class-locator#locator-drag-to>
36#[derive(Debug, Clone, Default, serde::Serialize)]
37#[serde(rename_all = "camelCase")]
38#[non_exhaustive]
39pub struct DragToOptions {
40    /// Whether to bypass actionability checks
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub force: Option<bool>,
43    /// Don't wait for navigation after the action
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub no_wait_after: Option<bool>,
46    /// Maximum time in milliseconds
47    #[serde(serialize_with = "crate::protocol::serialize_timeout_or_default")]
48    pub timeout: Option<f64>,
49    /// Perform actionability checks without dragging
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub trial: Option<bool>,
52    /// Where to click on the source element (relative to top-left corner)
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub source_position: Option<Position>,
55    /// Where to drop on the target element (relative to top-left corner)
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub target_position: Option<Position>,
58}
59
60impl DragToOptions {
61    /// Create a new builder for DragToOptions
62    pub fn builder() -> DragToOptionsBuilder {
63        DragToOptionsBuilder::default()
64    }
65
66    /// Convert options to JSON value for protocol
67    pub(crate) fn to_json(&self) -> serde_json::Value {
68        serde_json::to_value(self).expect("DragToOptions serialization cannot fail")
69    }
70}
71
72/// Builder for DragToOptions
73///
74/// Provides a fluent API for constructing drag_to options.
75#[derive(Debug, Clone, Default)]
76pub struct DragToOptionsBuilder {
77    force: Option<bool>,
78    no_wait_after: Option<bool>,
79    timeout: Option<f64>,
80    trial: Option<bool>,
81    source_position: Option<Position>,
82    target_position: Option<Position>,
83}
84
85impl DragToOptionsBuilder {
86    /// Bypass actionability checks
87    pub fn force(mut self, force: bool) -> Self {
88        self.force = Some(force);
89        self
90    }
91
92    /// Don't wait for navigation after the action
93    pub fn no_wait_after(mut self, no_wait_after: bool) -> Self {
94        self.no_wait_after = Some(no_wait_after);
95        self
96    }
97
98    /// Set timeout in milliseconds
99    pub fn timeout(mut self, timeout: f64) -> Self {
100        self.timeout = Some(timeout);
101        self
102    }
103
104    /// Perform actionability checks without dragging
105    pub fn trial(mut self, trial: bool) -> Self {
106        self.trial = Some(trial);
107        self
108    }
109
110    /// Set where to click on the source element (relative to top-left corner)
111    pub fn source_position(mut self, source_position: Position) -> Self {
112        self.source_position = Some(source_position);
113        self
114    }
115
116    /// Set where to drop on the target element (relative to top-left corner)
117    pub fn target_position(mut self, target_position: Position) -> Self {
118        self.target_position = Some(target_position);
119        self
120    }
121
122    /// Build the DragToOptions
123    pub fn build(self) -> DragToOptions {
124        DragToOptions {
125            force: self.force,
126            no_wait_after: self.no_wait_after,
127            timeout: self.timeout,
128            trial: self.trial,
129            source_position: self.source_position,
130            target_position: self.target_position,
131        }
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    #[test]
140    fn test_drag_to_options_default() {
141        let options = DragToOptions::builder().build();
142        let json = options.to_json();
143        // timeout has a default value
144        assert!(json["timeout"].is_number());
145        // other optional fields are absent
146        assert!(json.get("force").is_none());
147        assert!(json.get("trial").is_none());
148        assert!(json.get("sourcePosition").is_none());
149        assert!(json.get("targetPosition").is_none());
150    }
151
152    #[test]
153    fn test_drag_to_options_force() {
154        let options = DragToOptions::builder().force(true).build();
155        let json = options.to_json();
156        assert_eq!(json["force"], true);
157    }
158
159    #[test]
160    fn test_drag_to_options_timeout() {
161        let options = DragToOptions::builder().timeout(5000.0).build();
162        let json = options.to_json();
163        assert_eq!(json["timeout"], 5000.0);
164    }
165
166    #[test]
167    fn test_drag_to_options_trial() {
168        let options = DragToOptions::builder().trial(true).build();
169        let json = options.to_json();
170        assert_eq!(json["trial"], true);
171    }
172
173    #[test]
174    fn test_drag_to_options_positions() {
175        let options = DragToOptions::builder()
176            .source_position(Position { x: 5.0, y: 10.0 })
177            .target_position(Position { x: 50.0, y: 60.0 })
178            .build();
179        let json = options.to_json();
180        assert_eq!(json["sourcePosition"]["x"], 5.0);
181        assert_eq!(json["sourcePosition"]["y"], 10.0);
182        assert_eq!(json["targetPosition"]["x"], 50.0);
183        assert_eq!(json["targetPosition"]["y"], 60.0);
184    }
185
186    #[test]
187    fn test_drag_to_options_no_wait_after() {
188        let options = DragToOptions::builder().no_wait_after(true).build();
189        let json = options.to_json();
190        assert_eq!(json["noWaitAfter"], true);
191    }
192}