viewpoint_core/page/locator/builders/
click.rs1use tracing::{debug, instrument, trace};
4use viewpoint_cdp::protocol::input::{DispatchMouseEventParams, MouseButton};
5
6use super::super::Locator;
7use crate::error::LocatorError;
8use crate::wait::NavigationWaiter;
9
10#[derive(Debug)]
36pub struct ClickBuilder<'l, 'a> {
37 locator: &'l Locator<'a>,
38 position: Option<(f64, f64)>,
40 button: MouseButton,
42 modifiers: i32,
44 force: bool,
46 click_count: i32,
48 no_wait_after: bool,
50}
51
52impl<'l, 'a> ClickBuilder<'l, 'a> {
53 pub(crate) fn new(locator: &'l Locator<'a>) -> Self {
54 Self {
55 locator,
56 position: None,
57 button: MouseButton::Left,
58 modifiers: 0,
59 force: false,
60 click_count: 1,
61 no_wait_after: false,
62 }
63 }
64
65 #[must_use]
69 pub fn position(mut self, x: f64, y: f64) -> Self {
70 self.position = Some((x, y));
71 self
72 }
73
74 #[must_use]
76 pub fn button(mut self, button: MouseButton) -> Self {
77 self.button = button;
78 self
79 }
80
81 #[must_use]
85 pub fn modifiers(mut self, modifiers: i32) -> Self {
86 self.modifiers = modifiers;
87 self
88 }
89
90 #[must_use]
95 pub fn force(mut self, force: bool) -> Self {
96 self.force = force;
97 self
98 }
99
100 #[must_use]
102 pub(crate) fn click_count(mut self, count: i32) -> Self {
103 self.click_count = count;
104 self
105 }
106
107 #[must_use]
124 pub fn no_wait_after(mut self, no_wait_after: bool) -> Self {
125 self.no_wait_after = no_wait_after;
126 self
127 }
128
129 #[instrument(level = "debug", skip(self), fields(selector = ?self.locator.selector))]
131 pub async fn send(self) -> Result<(), LocatorError> {
132 let navigation_waiter = if self.no_wait_after {
134 None
135 } else {
136 Some(NavigationWaiter::new(
137 self.locator.page.connection().subscribe_events(),
138 self.locator.page.session_id().to_string(),
139 self.locator.page.frame_id().to_string(),
140 ))
141 };
142
143 self.perform_click().await?;
145
146 if let Some(waiter) = navigation_waiter {
148 match waiter.wait_for_navigation_if_triggered().await {
149 Ok(navigated) => {
150 if navigated {
151 trace!("Navigation completed after click");
152 }
153 }
154 Err(e) => {
155 debug!(error = ?e, "Navigation wait failed after click");
156 return Err(LocatorError::WaitError(e));
157 }
158 }
159 }
160
161 Ok(())
162 }
163
164 async fn perform_click(&self) -> Result<(), LocatorError> {
166 let (x, y) = if self.force {
167 let info = self.locator.query_element_info().await?;
168 if !info.found {
169 return Err(LocatorError::NotFound(format!(
170 "{:?}",
171 self.locator.selector
172 )));
173 }
174
175 if let Some((offset_x, offset_y)) = self.position {
176 (
177 info.x.unwrap_or(0.0) + offset_x,
178 info.y.unwrap_or(0.0) + offset_y,
179 )
180 } else {
181 (
182 info.x.unwrap_or(0.0) + info.width.unwrap_or(0.0) / 2.0,
183 info.y.unwrap_or(0.0) + info.height.unwrap_or(0.0) / 2.0,
184 )
185 }
186 } else {
187 let info = self.locator.wait_for_actionable().await?;
188
189 if let Some((offset_x, offset_y)) = self.position {
190 (
191 info.x.expect("visible element has x") + offset_x,
192 info.y.expect("visible element has y") + offset_y,
193 )
194 } else {
195 (
196 info.x.expect("visible element has x")
197 + info.width.expect("visible element has width") / 2.0,
198 info.y.expect("visible element has y")
199 + info.height.expect("visible element has height") / 2.0,
200 )
201 }
202 };
203
204 debug!(x, y, button = ?self.button, modifiers = self.modifiers, click_count = self.click_count, "Clicking element");
205
206 let mut move_event = DispatchMouseEventParams::mouse_move(x, y);
208 if self.modifiers != 0 {
209 move_event.modifiers = Some(self.modifiers);
210 }
211 self.locator.dispatch_mouse_event(move_event).await?;
212
213 let mut down_event = DispatchMouseEventParams::mouse_down(x, y, self.button);
215 if self.modifiers != 0 {
216 down_event.modifiers = Some(self.modifiers);
217 }
218 down_event.click_count = Some(self.click_count);
219 self.locator.dispatch_mouse_event(down_event).await?;
220
221 let mut up_event = DispatchMouseEventParams::mouse_up(x, y, self.button);
223 if self.modifiers != 0 {
224 up_event.modifiers = Some(self.modifiers);
225 }
226 up_event.click_count = Some(self.click_count);
227 self.locator.dispatch_mouse_event(up_event).await?;
228
229 Ok(())
230 }
231}
232
233impl<'l> std::future::IntoFuture for ClickBuilder<'l, '_> {
234 type Output = Result<(), LocatorError>;
235 type IntoFuture =
236 std::pin::Pin<Box<dyn std::future::Future<Output = Self::Output> + Send + 'l>>;
237
238 fn into_future(self) -> Self::IntoFuture {
239 Box::pin(self.send())
240 }
241}