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