viewpoint_core/page/locator/builders/
mod.rs1use std::time::Duration;
7
8use tracing::{debug, instrument};
9use viewpoint_cdp::protocol::input::{
10 DispatchKeyEventParams, DispatchMouseEventParams, MouseButton,
11};
12
13use super::Locator;
14use crate::error::LocatorError;
15
16#[derive(Debug)]
43pub struct ClickBuilder<'l, 'a> {
44 locator: &'l Locator<'a>,
45 position: Option<(f64, f64)>,
47 button: MouseButton,
49 modifiers: i32,
51 force: bool,
53 click_count: i32,
55}
56
57impl<'l, 'a> ClickBuilder<'l, 'a> {
58 pub(crate) fn new(locator: &'l Locator<'a>) -> Self {
59 Self {
60 locator,
61 position: None,
62 button: MouseButton::Left,
63 modifiers: 0,
64 force: false,
65 click_count: 1,
66 }
67 }
68
69 #[must_use]
73 pub fn position(mut self, x: f64, y: f64) -> Self {
74 self.position = Some((x, y));
75 self
76 }
77
78 #[must_use]
80 pub fn button(mut self, button: MouseButton) -> Self {
81 self.button = button;
82 self
83 }
84
85 #[must_use]
89 pub fn modifiers(mut self, modifiers: i32) -> Self {
90 self.modifiers = modifiers;
91 self
92 }
93
94 #[must_use]
99 pub fn force(mut self, force: bool) -> Self {
100 self.force = force;
101 self
102 }
103
104 #[must_use]
106 pub(crate) fn click_count(mut self, count: i32) -> Self {
107 self.click_count = count;
108 self
109 }
110
111 #[instrument(level = "debug", skip(self), fields(selector = ?self.locator.selector))]
113 pub async fn send(self) -> Result<(), LocatorError> {
114 let (x, y) = if self.force {
115 let info = self.locator.query_element_info().await?;
116 if !info.found {
117 return Err(LocatorError::NotFound(format!(
118 "{:?}",
119 self.locator.selector
120 )));
121 }
122
123 if let Some((offset_x, offset_y)) = self.position {
124 (
125 info.x.unwrap_or(0.0) + offset_x,
126 info.y.unwrap_or(0.0) + offset_y,
127 )
128 } else {
129 (
130 info.x.unwrap_or(0.0) + info.width.unwrap_or(0.0) / 2.0,
131 info.y.unwrap_or(0.0) + info.height.unwrap_or(0.0) / 2.0,
132 )
133 }
134 } else {
135 let info = self.locator.wait_for_actionable().await?;
136
137 if let Some((offset_x, offset_y)) = self.position {
138 (
139 info.x.expect("visible element has x") + offset_x,
140 info.y.expect("visible element has y") + offset_y,
141 )
142 } else {
143 (
144 info.x.expect("visible element has x")
145 + info.width.expect("visible element has width") / 2.0,
146 info.y.expect("visible element has y")
147 + info.height.expect("visible element has height") / 2.0,
148 )
149 }
150 };
151
152 debug!(x, y, button = ?self.button, modifiers = self.modifiers, click_count = self.click_count, "Clicking element");
153
154 let mut move_event = DispatchMouseEventParams::mouse_move(x, y);
156 if self.modifiers != 0 {
157 move_event.modifiers = Some(self.modifiers);
158 }
159 self.locator.dispatch_mouse_event(move_event).await?;
160
161 let mut down_event = DispatchMouseEventParams::mouse_down(x, y, self.button);
163 if self.modifiers != 0 {
164 down_event.modifiers = Some(self.modifiers);
165 }
166 down_event.click_count = Some(self.click_count);
167 self.locator.dispatch_mouse_event(down_event).await?;
168
169 let mut up_event = DispatchMouseEventParams::mouse_up(x, y, self.button);
171 if self.modifiers != 0 {
172 up_event.modifiers = Some(self.modifiers);
173 }
174 up_event.click_count = Some(self.click_count);
175 self.locator.dispatch_mouse_event(up_event).await?;
176
177 Ok(())
178 }
179}
180
181impl<'l> std::future::IntoFuture for ClickBuilder<'l, '_> {
182 type Output = Result<(), LocatorError>;
183 type IntoFuture =
184 std::pin::Pin<Box<dyn std::future::Future<Output = Self::Output> + Send + 'l>>;
185
186 fn into_future(self) -> Self::IntoFuture {
187 Box::pin(self.send())
188 }
189}
190
191#[derive(Debug)]
199pub struct TypeBuilder<'l, 'a> {
200 locator: &'l Locator<'a>,
201 text: String,
202 delay: Option<Duration>,
203}
204
205impl<'l, 'a> TypeBuilder<'l, 'a> {
206 pub(crate) fn new(locator: &'l Locator<'a>, text: &str) -> Self {
207 Self {
208 locator,
209 text: text.to_string(),
210 delay: None,
211 }
212 }
213
214 #[must_use]
216 pub fn delay(mut self, delay: Duration) -> Self {
217 self.delay = Some(delay);
218 self
219 }
220
221 #[instrument(level = "debug", skip(self), fields(selector = ?self.locator.selector))]
223 pub async fn send(self) -> Result<(), LocatorError> {
224 self.locator.wait_for_actionable().await?;
225
226 debug!(text = %self.text, delay = ?self.delay, "Typing text");
227
228 self.locator.focus_element().await?;
229
230 for ch in self.text.chars() {
231 let char_str = ch.to_string();
232 self.locator
233 .dispatch_key_event(DispatchKeyEventParams::char(&char_str))
234 .await?;
235
236 if let Some(delay) = self.delay {
237 tokio::time::sleep(delay).await;
238 }
239 }
240
241 Ok(())
242 }
243}
244
245impl<'l> std::future::IntoFuture for TypeBuilder<'l, '_> {
246 type Output = Result<(), LocatorError>;
247 type IntoFuture =
248 std::pin::Pin<Box<dyn std::future::Future<Output = Self::Output> + Send + 'l>>;
249
250 fn into_future(self) -> Self::IntoFuture {
251 Box::pin(self.send())
252 }
253}
254
255#[derive(Debug)]
263pub struct HoverBuilder<'l, 'a> {
264 locator: &'l Locator<'a>,
265 position: Option<(f64, f64)>,
266 modifiers: i32,
267 force: bool,
268}
269
270impl<'l, 'a> HoverBuilder<'l, 'a> {
271 pub(crate) fn new(locator: &'l Locator<'a>) -> Self {
272 Self {
273 locator,
274 position: None,
275 modifiers: 0,
276 force: false,
277 }
278 }
279
280 #[must_use]
282 pub fn position(mut self, x: f64, y: f64) -> Self {
283 self.position = Some((x, y));
284 self
285 }
286
287 #[must_use]
289 pub fn modifiers(mut self, modifiers: i32) -> Self {
290 self.modifiers = modifiers;
291 self
292 }
293
294 #[must_use]
296 pub fn force(mut self, force: bool) -> Self {
297 self.force = force;
298 self
299 }
300
301 #[instrument(level = "debug", skip(self), fields(selector = ?self.locator.selector))]
303 pub async fn send(self) -> Result<(), LocatorError> {
304 let (x, y) = if self.force {
305 let info = self.locator.query_element_info().await?;
306 if !info.found {
307 return Err(LocatorError::NotFound(format!(
308 "{:?}",
309 self.locator.selector
310 )));
311 }
312
313 if let Some((offset_x, offset_y)) = self.position {
314 (
315 info.x.unwrap_or(0.0) + offset_x,
316 info.y.unwrap_or(0.0) + offset_y,
317 )
318 } else {
319 (
320 info.x.unwrap_or(0.0) + info.width.unwrap_or(0.0) / 2.0,
321 info.y.unwrap_or(0.0) + info.height.unwrap_or(0.0) / 2.0,
322 )
323 }
324 } else {
325 let info = self.locator.wait_for_actionable().await?;
326
327 if let Some((offset_x, offset_y)) = self.position {
328 (
329 info.x.expect("visible element has x") + offset_x,
330 info.y.expect("visible element has y") + offset_y,
331 )
332 } else {
333 (
334 info.x.expect("visible element has x")
335 + info.width.expect("visible element has width") / 2.0,
336 info.y.expect("visible element has y")
337 + info.height.expect("visible element has height") / 2.0,
338 )
339 }
340 };
341
342 debug!(x, y, modifiers = self.modifiers, "Hovering over element");
343
344 let mut move_event = DispatchMouseEventParams::mouse_move(x, y);
345 if self.modifiers != 0 {
346 move_event.modifiers = Some(self.modifiers);
347 }
348 self.locator.dispatch_mouse_event(move_event).await?;
349
350 Ok(())
351 }
352}
353
354impl<'l> std::future::IntoFuture for HoverBuilder<'l, '_> {
355 type Output = Result<(), LocatorError>;
356 type IntoFuture =
357 std::pin::Pin<Box<dyn std::future::Future<Output = Self::Output> + Send + 'l>>;
358
359 fn into_future(self) -> Self::IntoFuture {
360 Box::pin(self.send())
361 }
362}
363
364#[derive(Debug)]
372pub struct TapBuilder<'l, 'a> {
373 locator: &'l Locator<'a>,
374 position: Option<(f64, f64)>,
375 force: bool,
376 modifiers: i32,
377}
378
379impl<'l, 'a> TapBuilder<'l, 'a> {
380 pub(crate) fn new(locator: &'l Locator<'a>) -> Self {
381 Self {
382 locator,
383 position: None,
384 force: false,
385 modifiers: 0,
386 }
387 }
388
389 #[must_use]
391 pub fn position(mut self, x: f64, y: f64) -> Self {
392 self.position = Some((x, y));
393 self
394 }
395
396 #[must_use]
398 pub fn force(mut self, force: bool) -> Self {
399 self.force = force;
400 self
401 }
402
403 #[must_use]
405 pub fn modifiers(mut self, modifiers: i32) -> Self {
406 self.modifiers = modifiers;
407 self
408 }
409
410 #[instrument(level = "debug", skip(self), fields(selector = ?self.locator.selector))]
412 pub async fn send(self) -> Result<(), LocatorError> {
413 let (x, y) = if self.force {
414 let info = self.locator.query_element_info().await?;
415 if !info.found {
416 return Err(LocatorError::NotFound(format!(
417 "{:?}",
418 self.locator.selector
419 )));
420 }
421
422 if let Some((offset_x, offset_y)) = self.position {
423 (
424 info.x.unwrap_or(0.0) + offset_x,
425 info.y.unwrap_or(0.0) + offset_y,
426 )
427 } else {
428 (
429 info.x.unwrap_or(0.0) + info.width.unwrap_or(0.0) / 2.0,
430 info.y.unwrap_or(0.0) + info.height.unwrap_or(0.0) / 2.0,
431 )
432 }
433 } else {
434 let info = self.locator.wait_for_actionable().await?;
435
436 if let Some((offset_x, offset_y)) = self.position {
437 (
438 info.x.expect("visible element has x") + offset_x,
439 info.y.expect("visible element has y") + offset_y,
440 )
441 } else {
442 (
443 info.x.expect("visible element has x")
444 + info.width.expect("visible element has width") / 2.0,
445 info.y.expect("visible element has y")
446 + info.height.expect("visible element has height") / 2.0,
447 )
448 }
449 };
450
451 debug!(x, y, modifiers = self.modifiers, "Tapping element");
452
453 if self.modifiers != 0 {
454 self.locator
455 .page
456 .touchscreen()
457 .tap_with_modifiers(x, y, self.modifiers)
458 .await
459 } else {
460 self.locator.page.touchscreen().tap(x, y).await
461 }
462 }
463}