viewpoint_core/page/mouse_drag/
mod.rs1use tracing::{debug, instrument};
4
5use super::Page;
6use crate::error::LocatorError;
7
8#[derive(Debug)]
35pub struct DragAndDropBuilder<'a> {
36 page: &'a Page,
37 source: String,
38 target: String,
39 source_position: Option<(f64, f64)>,
40 target_position: Option<(f64, f64)>,
41 steps: u32,
42}
43
44impl<'a> DragAndDropBuilder<'a> {
45 pub(crate) fn new(page: &'a Page, source: String, target: String) -> Self {
47 Self {
48 page,
49 source,
50 target,
51 source_position: None,
52 target_position: None,
53 steps: 1,
54 }
55 }
56
57 #[must_use]
61 pub fn source_position(mut self, x: f64, y: f64) -> Self {
62 self.source_position = Some((x, y));
63 self
64 }
65
66 #[must_use]
70 pub fn target_position(mut self, x: f64, y: f64) -> Self {
71 self.target_position = Some((x, y));
72 self
73 }
74
75 #[must_use]
77 pub fn steps(mut self, steps: u32) -> Self {
78 self.steps = steps.max(1);
79 self
80 }
81
82 #[instrument(level = "debug", skip(self), fields(source = %self.source, target = %self.target))]
84 pub async fn send(self) -> Result<(), LocatorError> {
85 let source_box = self.get_element_box(&self.source).await?;
87
88 let target_box = self.get_element_box(&self.target).await?;
90
91 let (source_x, source_y) = if let Some((ox, oy)) = self.source_position {
93 (source_box.0 + ox, source_box.1 + oy)
94 } else {
95 (
97 source_box.0 + source_box.2 / 2.0,
98 source_box.1 + source_box.3 / 2.0,
99 )
100 };
101
102 let (target_x, target_y) = if let Some((ox, oy)) = self.target_position {
104 (target_box.0 + ox, target_box.1 + oy)
105 } else {
106 (
108 target_box.0 + target_box.2 / 2.0,
109 target_box.1 + target_box.3 / 2.0,
110 )
111 };
112
113 debug!(
114 "Dragging from ({}, {}) to ({}, {})",
115 source_x, source_y, target_x, target_y
116 );
117
118 self.page.mouse().move_(source_x, source_y).send().await?;
120 self.page.mouse().down().send().await?;
121 self.page
122 .mouse()
123 .move_(target_x, target_y)
124 .steps(self.steps)
125 .send()
126 .await?;
127 self.page.mouse().up().send().await?;
128
129 Ok(())
130 }
131
132 async fn get_element_box(&self, selector: &str) -> Result<(f64, f64, f64, f64), LocatorError> {
134 let js = format!(
135 r"(function() {{
136 const el = document.querySelector({selector});
137 if (!el) return null;
138 const rect = el.getBoundingClientRect();
139 return {{ x: rect.x, y: rect.y, width: rect.width, height: rect.height }};
140 }})()",
141 selector = crate::page::locator::selector::js_string_literal(selector)
142 );
143
144 let result = self.evaluate_js(&js).await?;
145
146 if result.is_null() {
147 return Err(LocatorError::NotFound(selector.to_string()));
148 }
149
150 let x = result
151 .get("x")
152 .and_then(serde_json::Value::as_f64)
153 .unwrap_or(0.0);
154 let y = result
155 .get("y")
156 .and_then(serde_json::Value::as_f64)
157 .unwrap_or(0.0);
158 let width = result
159 .get("width")
160 .and_then(serde_json::Value::as_f64)
161 .unwrap_or(0.0);
162 let height = result
163 .get("height")
164 .and_then(serde_json::Value::as_f64)
165 .unwrap_or(0.0);
166
167 Ok((x, y, width, height))
168 }
169
170 async fn evaluate_js(&self, expression: &str) -> Result<serde_json::Value, LocatorError> {
172 if self.page.is_closed() {
173 return Err(LocatorError::PageClosed);
174 }
175
176 let params = viewpoint_cdp::protocol::runtime::EvaluateParams {
177 expression: expression.to_string(),
178 object_group: None,
179 include_command_line_api: None,
180 silent: Some(true),
181 context_id: None,
182 return_by_value: Some(true),
183 await_promise: Some(false),
184 };
185
186 let result: viewpoint_cdp::protocol::runtime::EvaluateResult = self
187 .page
188 .connection()
189 .send_command(
190 "Runtime.evaluate",
191 Some(params),
192 Some(self.page.session_id()),
193 )
194 .await?;
195
196 if let Some(exception) = result.exception_details {
197 return Err(LocatorError::EvaluationError(exception.text));
198 }
199
200 result
201 .result
202 .value
203 .ok_or_else(|| LocatorError::EvaluationError("No result value".to_string()))
204 }
205}