Skip to main content

playwright_rs/protocol/
drop_options.rs

1// DropOptions and related types
2//
3// Provides configuration for Locator::drop, matching Playwright's API.
4
5use crate::protocol::FilePayload;
6use crate::protocol::click::Position;
7
8/// Options for [`Locator::drop()`](crate::protocol::Locator::drop).
9///
10/// Simulates an external drag-and-drop of files and/or data onto an element
11/// (e.g. an upload drop zone), dispatching `dragenter`/`dragover`/`drop` with a
12/// synthetic `DataTransfer`. This is distinct from
13/// [`Locator::drag_to()`](crate::protocol::Locator::drag_to), which drags one
14/// element onto another within the page.
15///
16/// Set `files` and/or `data` (the driver requires at least one).
17///
18/// # Example
19///
20/// ```no_run
21/// use playwright_rs::{DropOptions, FilePayload, Position};
22///
23/// // Drop an in-memory file onto a drop zone
24/// let file = FilePayload::new("note.txt", "text/plain", b"hello".to_vec());
25/// let options = DropOptions::builder().file(file).build();
26///
27/// // Drop MIME-typed data (e.g. a dragged URL) at a specific point
28/// let options = DropOptions::builder()
29///     .data("text/uri-list", "https://example.com")
30///     .position(Position { x: 10.0, y: 10.0 })
31///     .build();
32/// ```
33///
34/// See: <https://playwright.dev/docs/api/class-locator#locator-drop>
35#[derive(Debug, Clone, Default)]
36#[non_exhaustive]
37pub struct DropOptions {
38    /// In-memory files to drop (serialized to the protocol's `payloads`).
39    pub files: Vec<FilePayload>,
40    /// MIME-typed data entries to drop, as `(mime_type, value)` pairs.
41    pub data: Vec<(String, String)>,
42    /// Point within the element to drop at (relative to its top-left corner).
43    pub position: Option<Position>,
44    /// Maximum time in milliseconds.
45    pub timeout: Option<f64>,
46}
47
48impl DropOptions {
49    /// Create a new builder for DropOptions
50    pub fn builder() -> DropOptionsBuilder {
51        DropOptionsBuilder::default()
52    }
53
54    /// Convert options to JSON value for protocol
55    pub(crate) fn to_json(&self) -> serde_json::Value {
56        use base64::{Engine as _, engine::general_purpose};
57
58        let mut json = serde_json::json!({});
59
60        if !self.files.is_empty() {
61            let payloads: Vec<serde_json::Value> = self
62                .files
63                .iter()
64                .map(|f| {
65                    serde_json::json!({
66                        "name": f.name,
67                        "mimeType": f.mime_type,
68                        "buffer": general_purpose::STANDARD.encode(&f.buffer),
69                    })
70                })
71                .collect();
72            json["payloads"] = serde_json::Value::Array(payloads);
73        }
74
75        if !self.data.is_empty() {
76            let data: Vec<serde_json::Value> = self
77                .data
78                .iter()
79                .map(|(mime, value)| serde_json::json!({ "mimeType": mime, "value": value }))
80                .collect();
81            json["data"] = serde_json::Value::Array(data);
82        }
83
84        if let Some(position) = &self.position {
85            json["position"] =
86                serde_json::to_value(position).expect("serialization of position cannot fail");
87        }
88
89        // Timeout is required in Playwright 1.56.1+
90        if let Some(timeout) = self.timeout {
91            json["timeout"] = serde_json::json!(timeout);
92        } else {
93            json["timeout"] = serde_json::json!(crate::DEFAULT_TIMEOUT_MS);
94        }
95
96        json
97    }
98}
99
100/// Builder for DropOptions
101///
102/// Provides a fluent API for constructing drop options.
103#[derive(Debug, Clone, Default)]
104pub struct DropOptionsBuilder {
105    files: Vec<FilePayload>,
106    data: Vec<(String, String)>,
107    position: Option<Position>,
108    timeout: Option<f64>,
109}
110
111impl DropOptionsBuilder {
112    /// Add one in-memory file to drop.
113    pub fn file(mut self, file: FilePayload) -> Self {
114        self.files.push(file);
115        self
116    }
117
118    /// Set the in-memory files to drop (replaces any already added).
119    pub fn files(mut self, files: Vec<FilePayload>) -> Self {
120        self.files = files;
121        self
122    }
123
124    /// Add a MIME-typed data entry to drop (e.g. `"text/plain"` or `"text/uri-list"`).
125    pub fn data(mut self, mime_type: impl Into<String>, value: impl Into<String>) -> Self {
126        self.data.push((mime_type.into(), value.into()));
127        self
128    }
129
130    /// Set the point within the element to drop at (relative to top-left corner).
131    pub fn position(mut self, position: Position) -> Self {
132        self.position = Some(position);
133        self
134    }
135
136    /// Set timeout in milliseconds.
137    pub fn timeout(mut self, timeout: f64) -> Self {
138        self.timeout = Some(timeout);
139        self
140    }
141
142    /// Build the DropOptions
143    pub fn build(self) -> DropOptions {
144        DropOptions {
145            files: self.files,
146            data: self.data,
147            position: self.position,
148            timeout: self.timeout,
149        }
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    #[test]
158    fn test_drop_options_default() {
159        let json = DropOptions::builder().build().to_json();
160        assert!(json["timeout"].is_number());
161        assert!(json.get("payloads").is_none());
162        assert!(json.get("data").is_none());
163        assert!(json.get("position").is_none());
164    }
165
166    #[test]
167    fn test_drop_options_file_payload() {
168        let file = FilePayload::new("note.txt", "text/plain", b"hi".to_vec());
169        let json = DropOptions::builder().file(file).build().to_json();
170        assert_eq!(json["payloads"][0]["name"], "note.txt");
171        assert_eq!(json["payloads"][0]["mimeType"], "text/plain");
172        assert_eq!(json["payloads"][0]["buffer"], "aGk="); // base64("hi")
173    }
174
175    #[test]
176    fn test_drop_options_data() {
177        let json = DropOptions::builder()
178            .data("text/uri-list", "https://example.com")
179            .build()
180            .to_json();
181        assert_eq!(json["data"][0]["mimeType"], "text/uri-list");
182        assert_eq!(json["data"][0]["value"], "https://example.com");
183    }
184
185    #[test]
186    fn test_drop_options_position_and_timeout() {
187        let json = DropOptions::builder()
188            .position(Position { x: 10.0, y: 20.0 })
189            .timeout(5000.0)
190            .build()
191            .to_json();
192        assert_eq!(json["position"]["x"], 10.0);
193        assert_eq!(json["position"]["y"], 20.0);
194        assert_eq!(json["timeout"], 5000.0);
195    }
196}