viewpoint_core/page/mouse_drag/
mod.rs1use tracing::{debug, instrument};
4use viewpoint_js::js;
5
6use super::Page;
7use crate::error::LocatorError;
8
9#[derive(Debug)]
36pub struct DragAndDropBuilder<'a> {
37 page: &'a Page,
38 source: String,
39 target: String,
40 source_position: Option<(f64, f64)>,
41 target_position: Option<(f64, f64)>,
42 steps: u32,
43}
44
45impl<'a> DragAndDropBuilder<'a> {
46 pub(crate) fn new(page: &'a Page, source: String, target: String) -> Self {
48 Self {
49 page,
50 source,
51 target,
52 source_position: None,
53 target_position: None,
54 steps: 1,
55 }
56 }
57
58 #[must_use]
62 pub fn source_position(mut self, x: f64, y: f64) -> Self {
63 self.source_position = Some((x, y));
64 self
65 }
66
67 #[must_use]
71 pub fn target_position(mut self, x: f64, y: f64) -> Self {
72 self.target_position = Some((x, y));
73 self
74 }
75
76 #[must_use]
78 pub fn steps(mut self, steps: u32) -> Self {
79 self.steps = steps.max(1);
80 self
81 }
82
83 #[instrument(level = "debug", skip(self), fields(source = %self.source, target = %self.target))]
85 pub async fn send(self) -> Result<(), LocatorError> {
86 let source_box = self.get_element_box(&self.source).await?;
88
89 let target_box = self.get_element_box(&self.target).await?;
91
92 let (source_x, source_y) = if let Some((ox, oy)) = self.source_position {
94 (source_box.0 + ox, source_box.1 + oy)
95 } else {
96 (
98 source_box.0 + source_box.2 / 2.0,
99 source_box.1 + source_box.3 / 2.0,
100 )
101 };
102
103 let (target_x, target_y) = if let Some((ox, oy)) = self.target_position {
105 (target_box.0 + ox, target_box.1 + oy)
106 } else {
107 (
109 target_box.0 + target_box.2 / 2.0,
110 target_box.1 + target_box.3 / 2.0,
111 )
112 };
113
114 debug!(
115 "Dragging from ({}, {}) to ({}, {})",
116 source_x, source_y, target_x, target_y
117 );
118
119 self.page.mouse().move_(source_x, source_y).send().await?;
121 self.page.mouse().down().send().await?;
122 self.page
123 .mouse()
124 .move_(target_x, target_y)
125 .steps(self.steps)
126 .send()
127 .await?;
128 self.page.mouse().up().send().await?;
129
130 Ok(())
131 }
132
133 async fn get_element_box(&self, selector: &str) -> Result<(f64, f64, f64, f64), LocatorError> {
135 let js_code = js! {
136 (function() {
137 const el = document.querySelector(#{selector});
138 if (!el) return null;
139 const rect = el.getBoundingClientRect();
140 return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
141 })()
142 };
143
144 let result = self.evaluate_js(&js_code).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> {
174 if self.page.is_closed() {
175 return Err(LocatorError::PageClosed);
176 }
177
178 self.page
179 .evaluate_js_raw(expression)
180 .await
181 .map_err(|e| LocatorError::EvaluationError(e.to_string()))
182 }
183}