limit_cli/tools/browser/client_ext/
interaction.rs1use crate::tools::browser::executor::BrowserError;
4
5pub trait InteractionExt {
7 fn click(
9 &self,
10 selector: &str,
11 ) -> impl std::future::Future<Output = Result<(), BrowserError>> + Send;
12
13 fn dblclick(
15 &self,
16 selector: &str,
17 ) -> impl std::future::Future<Output = Result<(), BrowserError>> + Send;
18
19 fn fill(
21 &self,
22 selector: &str,
23 text: &str,
24 ) -> impl std::future::Future<Output = Result<(), BrowserError>> + Send;
25
26 fn type_text(
28 &self,
29 selector: &str,
30 text: &str,
31 ) -> impl std::future::Future<Output = Result<(), BrowserError>> + Send;
32
33 fn press(
35 &self,
36 key: &str,
37 ) -> impl std::future::Future<Output = Result<(), BrowserError>> + Send;
38
39 fn hover(
41 &self,
42 selector: &str,
43 ) -> impl std::future::Future<Output = Result<(), BrowserError>> + Send;
44
45 fn select_option(
47 &self,
48 selector: &str,
49 value: &str,
50 ) -> impl std::future::Future<Output = Result<(), BrowserError>> + Send;
51
52 fn focus(
54 &self,
55 selector: &str,
56 ) -> impl std::future::Future<Output = Result<(), BrowserError>> + Send;
57
58 fn check(
60 &self,
61 selector: &str,
62 ) -> impl std::future::Future<Output = Result<(), BrowserError>> + Send;
63
64 fn uncheck(
66 &self,
67 selector: &str,
68 ) -> impl std::future::Future<Output = Result<(), BrowserError>> + Send;
69
70 fn scrollintoview(
72 &self,
73 selector: &str,
74 ) -> impl std::future::Future<Output = Result<(), BrowserError>> + Send;
75
76 fn drag(
78 &self,
79 source: &str,
80 target: &str,
81 ) -> impl std::future::Future<Output = Result<(), BrowserError>> + Send;
82
83 fn upload(
85 &self,
86 selector: &str,
87 files: &[&str],
88 ) -> impl std::future::Future<Output = Result<(), BrowserError>> + Send;
89
90 fn scroll(
92 &self,
93 direction: &str,
94 pixels: Option<u32>,
95 ) -> impl std::future::Future<Output = Result<(), BrowserError>> + Send;
96}
97
98impl InteractionExt for super::super::BrowserClient {
99 async fn click(&self, selector: &str) -> Result<(), BrowserError> {
100 if selector.is_empty() {
101 return Err(BrowserError::InvalidArguments(
102 "Selector cannot be empty".to_string(),
103 ));
104 }
105
106 let output = self.executor().execute(&["click", selector]).await?;
107
108 if output.success {
109 Ok(())
110 } else {
111 Err(BrowserError::Other(format!(
112 "Failed to click element: {}",
113 output.stderr
114 )))
115 }
116 }
117
118 async fn dblclick(&self, selector: &str) -> Result<(), BrowserError> {
119 if selector.is_empty() {
120 return Err(BrowserError::InvalidArguments(
121 "Selector cannot be empty".to_string(),
122 ));
123 }
124
125 let output = self.executor().execute(&["dblclick", selector]).await?;
126
127 if output.success {
128 Ok(())
129 } else {
130 Err(BrowserError::Other(format!(
131 "Failed to double-click element: {}",
132 output.stderr
133 )))
134 }
135 }
136
137 async fn fill(&self, selector: &str, text: &str) -> Result<(), BrowserError> {
138 if selector.is_empty() {
139 return Err(BrowserError::InvalidArguments(
140 "Selector cannot be empty".to_string(),
141 ));
142 }
143
144 let output = self.executor().execute(&["fill", selector, text]).await?;
145
146 if output.success {
147 Ok(())
148 } else {
149 Err(BrowserError::Other(format!(
150 "Failed to fill element: {}",
151 output.stderr
152 )))
153 }
154 }
155
156 async fn type_text(&self, selector: &str, text: &str) -> Result<(), BrowserError> {
157 if selector.is_empty() {
158 return Err(BrowserError::InvalidArguments(
159 "Selector cannot be empty".to_string(),
160 ));
161 }
162
163 let output = self.executor().execute(&["type", selector, text]).await?;
164
165 if output.success {
166 Ok(())
167 } else {
168 Err(BrowserError::Other(format!(
169 "Failed to type text: {}",
170 output.stderr
171 )))
172 }
173 }
174
175 async fn press(&self, key: &str) -> Result<(), BrowserError> {
176 if key.is_empty() {
177 return Err(BrowserError::InvalidArguments(
178 "Key cannot be empty".to_string(),
179 ));
180 }
181
182 let output = self.executor().execute(&["press", key]).await?;
183
184 if output.success {
185 Ok(())
186 } else {
187 Err(BrowserError::Other(format!(
188 "Failed to press key: {}",
189 output.stderr
190 )))
191 }
192 }
193
194 async fn hover(&self, selector: &str) -> Result<(), BrowserError> {
195 if selector.is_empty() {
196 return Err(BrowserError::InvalidArguments(
197 "Selector cannot be empty".to_string(),
198 ));
199 }
200
201 let output = self.executor().execute(&["hover", selector]).await?;
202
203 if output.success {
204 Ok(())
205 } else {
206 Err(BrowserError::Other(format!(
207 "Failed to hover: {}",
208 output.stderr
209 )))
210 }
211 }
212
213 async fn select_option(&self, selector: &str, value: &str) -> Result<(), BrowserError> {
214 if selector.is_empty() {
215 return Err(BrowserError::InvalidArguments(
216 "Selector cannot be empty".to_string(),
217 ));
218 }
219
220 let output = self
221 .executor()
222 .execute(&["select", selector, value])
223 .await?;
224
225 if output.success {
226 Ok(())
227 } else {
228 Err(BrowserError::Other(format!(
229 "Failed to select option: {}",
230 output.stderr
231 )))
232 }
233 }
234
235 async fn focus(&self, selector: &str) -> Result<(), BrowserError> {
236 if selector.is_empty() {
237 return Err(BrowserError::InvalidArguments(
238 "Selector cannot be empty".to_string(),
239 ));
240 }
241
242 let output = self.executor().execute(&["focus", selector]).await?;
243
244 if output.success {
245 Ok(())
246 } else {
247 Err(BrowserError::Other(format!(
248 "Failed to focus element: {}",
249 output.stderr
250 )))
251 }
252 }
253
254 async fn check(&self, selector: &str) -> Result<(), BrowserError> {
255 if selector.is_empty() {
256 return Err(BrowserError::InvalidArguments(
257 "Selector cannot be empty".to_string(),
258 ));
259 }
260
261 let output = self.executor().execute(&["check", selector]).await?;
262
263 if output.success {
264 Ok(())
265 } else {
266 Err(BrowserError::Other(format!(
267 "Failed to check element: {}",
268 output.stderr
269 )))
270 }
271 }
272
273 async fn uncheck(&self, selector: &str) -> Result<(), BrowserError> {
274 if selector.is_empty() {
275 return Err(BrowserError::InvalidArguments(
276 "Selector cannot be empty".to_string(),
277 ));
278 }
279
280 let output = self.executor().execute(&["uncheck", selector]).await?;
281
282 if output.success {
283 Ok(())
284 } else {
285 Err(BrowserError::Other(format!(
286 "Failed to uncheck element: {}",
287 output.stderr
288 )))
289 }
290 }
291
292 async fn scrollintoview(&self, selector: &str) -> Result<(), BrowserError> {
293 if selector.is_empty() {
294 return Err(BrowserError::InvalidArguments(
295 "Selector cannot be empty".to_string(),
296 ));
297 }
298
299 let output = self
300 .executor()
301 .execute(&["scrollintoview", selector])
302 .await?;
303
304 if output.success {
305 Ok(())
306 } else {
307 Err(BrowserError::Other(format!(
308 "Failed to scroll element into view: {}",
309 output.stderr
310 )))
311 }
312 }
313
314 async fn drag(&self, source: &str, target: &str) -> Result<(), BrowserError> {
315 if source.is_empty() || target.is_empty() {
316 return Err(BrowserError::InvalidArguments(
317 "Source and target selectors cannot be empty".to_string(),
318 ));
319 }
320
321 let output = self.executor().execute(&["drag", source, target]).await?;
322
323 if output.success {
324 Ok(())
325 } else {
326 Err(BrowserError::Other(format!(
327 "Failed to drag and drop: {}",
328 output.stderr
329 )))
330 }
331 }
332
333 async fn upload(&self, selector: &str, files: &[&str]) -> Result<(), BrowserError> {
334 if selector.is_empty() {
335 return Err(BrowserError::InvalidArguments(
336 "Selector cannot be empty".to_string(),
337 ));
338 }
339
340 if files.is_empty() {
341 return Err(BrowserError::InvalidArguments(
342 "At least one file must be specified".to_string(),
343 ));
344 }
345
346 let mut args = vec!["upload", selector];
347 let files_owned: Vec<String> = files.iter().map(|f| f.to_string()).collect();
348 let file_refs: Vec<&str> = files_owned.iter().map(|f| f.as_str()).collect();
349 args.extend(file_refs);
350
351 let output = self.executor().execute(&args).await?;
352
353 if output.success {
354 Ok(())
355 } else {
356 Err(BrowserError::Other(format!(
357 "Failed to upload files: {}",
358 output.stderr
359 )))
360 }
361 }
362
363 async fn scroll(&self, direction: &str, pixels: Option<u32>) -> Result<(), BrowserError> {
364 let valid_directions = ["up", "down", "left", "right"];
365 if !valid_directions.contains(&direction) {
366 return Err(BrowserError::InvalidArguments(format!(
367 "Invalid scroll direction '{}'. Valid directions: {}",
368 direction,
369 valid_directions.join(", ")
370 )));
371 }
372
373 let output = match pixels {
374 Some(px) => {
375 let px_str = px.to_string();
376 self.executor()
377 .execute(&["scroll", direction, &px_str])
378 .await?
379 }
380 None => self.executor().execute(&["scroll", direction]).await?,
381 };
382
383 if output.success {
384 Ok(())
385 } else {
386 Err(BrowserError::Other(format!(
387 "Failed to scroll: {}",
388 output.stderr
389 )))
390 }
391 }
392}