1pub fn format_activity_message(tool_name: &str, args: &serde_json::Value) -> String {
7 match tool_name {
8 "file_read" => format_file_read(args),
9 "file_write" => format_file_write(args),
10 "file_edit" => format_file_edit(args),
11 "bash" => format_bash(args),
12 "git_status" => "Checking git status...".to_string(),
13 "git_diff" => "Checking git diff...".to_string(),
14 "git_log" => "Checking git log...".to_string(),
15 "git_add" => "Staging files...".to_string(),
16 "git_commit" => "Creating commit...".to_string(),
17 "git_push" => "Pushing to remote...".to_string(),
18 "git_pull" => "Pulling from remote...".to_string(),
19 "git_clone" => format_git_clone(args),
20 "grep" => format_grep(args),
21 "ast_grep" => format_ast_grep(args),
22 "lsp" => format_lsp(args),
23 "browser" => format_browser(args),
24 _ => format!("Executing {}...", tool_name),
25 }
26}
27
28fn format_file_read(args: &serde_json::Value) -> String {
30 args.get("path")
31 .and_then(|p| p.as_str())
32 .map(|p| format!("Reading {}...", truncate_path(p, 150)))
33 .unwrap_or_else(|| "Reading file...".to_string())
34}
35
36fn format_file_write(args: &serde_json::Value) -> String {
38 args.get("path")
39 .and_then(|p| p.as_str())
40 .map(|p| format!("Writing {}...", truncate_path(p, 150)))
41 .unwrap_or_else(|| "Writing file...".to_string())
42}
43
44fn format_file_edit(args: &serde_json::Value) -> String {
46 args.get("path")
47 .and_then(|p| p.as_str())
48 .map(|p| format!("Editing {}...", truncate_path(p, 150)))
49 .unwrap_or_else(|| "Editing file...".to_string())
50}
51
52fn format_bash(args: &serde_json::Value) -> String {
54 args.get("command")
55 .and_then(|c| c.as_str())
56 .map(|c| format!("Running {}...", truncate_command(c, 150)))
57 .unwrap_or_else(|| "Executing command...".to_string())
58}
59
60fn format_git_clone(args: &serde_json::Value) -> String {
62 args.get("url")
63 .and_then(|u| u.as_str())
64 .map(|u| format!("Cloning {}...", truncate_path(u, 150)))
65 .unwrap_or_else(|| "Cloning repository...".to_string())
66}
67
68fn format_grep(args: &serde_json::Value) -> String {
70 args.get("pattern")
71 .and_then(|p| p.as_str())
72 .map(|p| format!("Searching for '{}'...", truncate_command(p, 150)))
73 .unwrap_or_else(|| "Searching...".to_string())
74}
75
76fn format_ast_grep(args: &serde_json::Value) -> String {
78 args.get("pattern")
79 .and_then(|p| p.as_str())
80 .map(|p| format!("AST searching '{}'...", truncate_command(p, 150)))
81 .unwrap_or_else(|| "AST searching...".to_string())
82}
83
84fn format_lsp(args: &serde_json::Value) -> String {
86 args.get("command")
87 .and_then(|c| c.as_str())
88 .map(|c| format!("Running LSP {}...", c))
89 .unwrap_or_else(|| "Running LSP...".to_string())
90}
91
92fn format_browser(args: &serde_json::Value) -> String {
93 let action = args
94 .get("action")
95 .and_then(|a| a.as_str())
96 .unwrap_or("unknown");
97
98 let detail = match action {
99 "open" => args
100 .get("url")
101 .and_then(|u| u.as_str())
102 .map(|u| format!("opening {}", truncate_path(u, 80)))
103 .unwrap_or_else(|| "opening URL".to_string()),
104 "close" => "closing browser".to_string(),
105 "snapshot" => "taking DOM snapshot".to_string(),
106 "screenshot" => args
107 .get("path")
108 .and_then(|p| p.as_str())
109 .map(|p| format!("taking screenshot to {}", truncate_path(p, 60)))
110 .unwrap_or_else(|| "taking screenshot".to_string()),
111 "back" => "navigating back".to_string(),
112 "forward" => "navigating forward".to_string(),
113 "reload" => "reloading page".to_string(),
114
115 "click" => args
116 .get("selector")
117 .and_then(|s| s.as_str())
118 .map(|s| format!("clicking {}", truncate_command(s, 80)))
119 .unwrap_or_else(|| "clicking element".to_string()),
120 "dblclick" => args
121 .get("selector")
122 .and_then(|s| s.as_str())
123 .map(|s| format!("double-clicking {}", truncate_command(s, 80)))
124 .unwrap_or_else(|| "double-clicking element".to_string()),
125 "hover" => args
126 .get("selector")
127 .and_then(|s| s.as_str())
128 .map(|s| format!("hovering {}", truncate_command(s, 80)))
129 .unwrap_or_else(|| "hovering element".to_string()),
130 "focus" => args
131 .get("selector")
132 .and_then(|s| s.as_str())
133 .map(|s| format!("focusing {}", truncate_command(s, 80)))
134 .unwrap_or_else(|| "focusing element".to_string()),
135 "scrollintoview" => args
136 .get("selector")
137 .and_then(|s| s.as_str())
138 .map(|s| format!("scrolling to {}", truncate_command(s, 80)))
139 .unwrap_or_else(|| "scrolling element into view".to_string()),
140
141 "fill" => {
142 let selector = args.get("selector").and_then(|s| s.as_str());
143 let text = args.get("text").and_then(|t| t.as_str());
144 match (selector, text) {
145 (Some(s), Some(t)) => format!(
146 "filling {} with \"{}\"",
147 truncate_command(s, 40),
148 truncate_command(t, 40)
149 ),
150 (Some(s), None) => format!("filling {}", truncate_command(s, 80)),
151 _ => "filling input".to_string(),
152 }
153 }
154 "type" => args
155 .get("selector")
156 .and_then(|s| s.as_str())
157 .map(|s| format!("typing into {}", truncate_command(s, 80)))
158 .unwrap_or_else(|| "typing".to_string()),
159 "press" => args
160 .get("key")
161 .and_then(|k| k.as_str())
162 .map(|k| format!("pressing {}", k))
163 .unwrap_or_else(|| "pressing key".to_string()),
164 "select" => {
165 let selector = args.get("selector").and_then(|s| s.as_str());
166 let value = args.get("value").and_then(|v| v.as_str());
167 match (selector, value) {
168 (Some(s), Some(v)) => format!(
169 "selecting \"{}\" in {}",
170 truncate_command(v, 40),
171 truncate_command(s, 40)
172 ),
173 (Some(s), None) => format!("selecting option in {}", truncate_command(s, 80)),
174 _ => "selecting option".to_string(),
175 }
176 }
177 "check" => args
178 .get("selector")
179 .and_then(|s| s.as_str())
180 .map(|s| format!("checking {}", truncate_command(s, 80)))
181 .unwrap_or_else(|| "checking checkbox".to_string()),
182 "uncheck" => args
183 .get("selector")
184 .and_then(|s| s.as_str())
185 .map(|s| format!("unchecking {}", truncate_command(s, 80)))
186 .unwrap_or_else(|| "unchecking checkbox".to_string()),
187 "drag" => {
188 let source = args.get("source").and_then(|s| s.as_str());
189 let target = args.get("target").and_then(|t| t.as_str());
190 match (source, target) {
191 (Some(s), Some(t)) => format!(
192 "dragging {} to {}",
193 truncate_command(s, 40),
194 truncate_command(t, 40)
195 ),
196 _ => "dragging element".to_string(),
197 }
198 }
199 "upload" => {
200 let selector = args.get("selector").and_then(|s| s.as_str());
201 let path = args.get("path").and_then(|p| p.as_str());
202 match (selector, path) {
203 (Some(s), Some(p)) => format!(
204 "uploading {} to {}",
205 truncate_path(p, 40),
206 truncate_command(s, 40)
207 ),
208 _ => "uploading file".to_string(),
209 }
210 }
211 "pdf" => args
212 .get("path")
213 .and_then(|p| p.as_str())
214 .map(|p| format!("saving PDF to {}", truncate_path(p, 60)))
215 .unwrap_or_else(|| "saving PDF".to_string()),
216
217 "get" => args
218 .get("selector")
219 .and_then(|s| s.as_str())
220 .map(|s| format!("getting element {}", truncate_command(s, 80)))
221 .unwrap_or_else(|| "getting element".to_string()),
222 "get_attr" => {
223 let selector = args.get("selector").and_then(|s| s.as_str());
224 let attr = args.get("attr").and_then(|a| a.as_str());
225 match (selector, attr) {
226 (Some(s), Some(a)) => {
227 format!("getting {} attribute from {}", a, truncate_command(s, 60))
228 }
229 _ => "getting attribute".to_string(),
230 }
231 }
232 "get_count" => args
233 .get("selector")
234 .and_then(|s| s.as_str())
235 .map(|s| format!("counting {}", truncate_command(s, 80)))
236 .unwrap_or_else(|| "counting elements".to_string()),
237 "get_box" => args
238 .get("selector")
239 .and_then(|s| s.as_str())
240 .map(|s| format!("getting bounding box of {}", truncate_command(s, 60)))
241 .unwrap_or_else(|| "getting bounding box".to_string()),
242 "get_styles" => args
243 .get("selector")
244 .and_then(|s| s.as_str())
245 .map(|s| format!("getting styles of {}", truncate_command(s, 80)))
246 .unwrap_or_else(|| "getting styles".to_string()),
247
248 "wait" => args
249 .get("ms")
250 .and_then(|m| m.as_u64())
251 .map(|m| format!("waiting {}ms", m))
252 .unwrap_or_else(|| "waiting".to_string()),
253 "wait_for_text" => args
254 .get("text")
255 .and_then(|t| t.as_str())
256 .map(|t| format!("waiting for text \"{}\"", truncate_command(t, 60)))
257 .unwrap_or_else(|| "waiting for text".to_string()),
258 "wait_for_url" => args
259 .get("url")
260 .and_then(|u| u.as_str())
261 .map(|u| format!("waiting for URL {}", truncate_path(u, 60)))
262 .unwrap_or_else(|| "waiting for URL".to_string()),
263 "wait_for_load" => "waiting for page load".to_string(),
264 "wait_for_download" => "waiting for download".to_string(),
265 "wait_for_fn" => "waiting for function result".to_string(),
266 "wait_for_state" => args
267 .get("state")
268 .and_then(|s| s.as_str())
269 .map(|s| format!("waiting for {} state", s))
270 .unwrap_or_else(|| "waiting for state".to_string()),
271
272 "find" => args
273 .get("selector")
274 .and_then(|s| s.as_str())
275 .map(|s| format!("finding {}", truncate_command(s, 80)))
276 .unwrap_or_else(|| "finding element".to_string()),
277 "scroll" => {
278 let x = args.get("x").and_then(|v| v.as_i64());
279 let y = args.get("y").and_then(|v| v.as_i64());
280 match (x, y) {
281 (Some(x), Some(y)) => format!("scrolling to ({}, {})", x, y),
282 _ => "scrolling".to_string(),
283 }
284 }
285 "is" => {
286 let selector = args.get("selector").and_then(|s| s.as_str());
287 let state = args.get("state").and_then(|s| s.as_str());
288 match (selector, state) {
289 (Some(s), Some(st)) => {
290 format!("checking if {} is {}", truncate_command(s, 60), st)
291 }
292 _ => "checking element state".to_string(),
293 }
294 }
295 "download" => args
296 .get("url")
297 .and_then(|u| u.as_str())
298 .map(|u| format!("downloading {}", truncate_path(u, 60)))
299 .unwrap_or_else(|| "downloading".to_string()),
300
301 "tab_list" => "listing tabs".to_string(),
302 "tab_new" => args
303 .get("url")
304 .and_then(|u| u.as_str())
305 .map(|u| format!("opening new tab {}", truncate_path(u, 60)))
306 .unwrap_or_else(|| "opening new tab".to_string()),
307 "tab_close" => args
308 .get("index")
309 .and_then(|i| i.as_u64())
310 .map(|i| format!("closing tab {}", i))
311 .unwrap_or_else(|| "closing tab".to_string()),
312 "tab_select" => args
313 .get("index")
314 .and_then(|i| i.as_u64())
315 .map(|i| format!("selecting tab {}", i))
316 .unwrap_or_else(|| "selecting tab".to_string()),
317
318 "dialog_accept" => args
319 .get("text")
320 .and_then(|t| t.as_str())
321 .map(|t| format!("accepting dialog with \"{}\"", truncate_command(t, 40)))
322 .unwrap_or_else(|| "accepting dialog".to_string()),
323 "dialog_dismiss" => "dismissing dialog".to_string(),
324
325 "cookies" => args
326 .get("url")
327 .and_then(|u| u.as_str())
328 .map(|u| format!("getting cookies for {}", truncate_path(u, 60)))
329 .unwrap_or_else(|| "getting cookies".to_string()),
330 "cookies_set" => "setting cookies".to_string(),
331 "storage_get" => args
332 .get("key")
333 .and_then(|k| k.as_str())
334 .map(|k| format!("getting storage \"{}\"", truncate_command(k, 40)))
335 .unwrap_or_else(|| "getting from storage".to_string()),
336 "storage_set" => args
337 .get("key")
338 .and_then(|k| k.as_str())
339 .map(|k| format!("setting storage \"{}\"", truncate_command(k, 40)))
340 .unwrap_or_else(|| "setting storage".to_string()),
341 "network_requests" => "getting network requests".to_string(),
342
343 "set_viewport" => {
344 let width = args.get("width").and_then(|w| w.as_u64());
345 let height = args.get("height").and_then(|h| h.as_u64());
346 match (width, height) {
347 (Some(w), Some(h)) => format!("setting viewport to {}x{}", w, h),
348 _ => "setting viewport".to_string(),
349 }
350 }
351 "set_device" => args
352 .get("device")
353 .and_then(|d| d.as_str())
354 .map(|d| format!("setting device to {}", d))
355 .unwrap_or_else(|| "setting device".to_string()),
356 "set_geo" => {
357 let lat = args.get("latitude").and_then(|l| l.as_f64());
358 let lon = args.get("longitude").and_then(|l| l.as_f64());
359 match (lat, lon) {
360 (Some(lat), Some(lon)) => format!("setting geolocation to ({}, {})", lat, lon),
361 _ => "setting geolocation".to_string(),
362 }
363 }
364
365 "eval" => args
366 .get("script")
367 .and_then(|s| s.as_str())
368 .map(|s| format!("evaluating {}", truncate_command(s, 60)))
369 .unwrap_or_else(|| "evaluating script".to_string()),
370
371 _ => action.to_string(),
372 };
373
374 format!("Executing browser: {}...", detail)
375}
376
377fn truncate_path(s: &str, max_len: usize) -> String {
379 if s.len() <= max_len {
380 s.to_string()
381 } else {
382 format!("...{}", &s[s.len().saturating_sub(max_len - 3)..])
383 }
384}
385
386fn truncate_command(s: &str, max_len: usize) -> String {
388 if s.len() <= max_len {
389 s.to_string()
390 } else {
391 format!("{}...", &s[..max_len.saturating_sub(3)])
392 }
393}
394
395#[cfg(test)]
396mod tests {
397 use super::*;
398 use serde_json::json;
399
400 #[test]
401 fn test_format_file_read() {
402 let args = json!({"path": "/some/long/path/to/file.txt"});
403 let msg = format_activity_message("file_read", &args);
404 assert!(msg.contains("Reading"));
405 }
406
407 #[test]
408 fn test_format_bash() {
409 let args = json!({"command": "echo hello"});
410 let msg = format_activity_message("bash", &args);
411 assert!(msg.contains("Running"));
412 }
413
414 #[test]
415 fn test_format_unknown_tool() {
416 let args = json!({});
417 let msg = format_activity_message("unknown_tool", &args);
418 assert_eq!(msg, "Executing unknown_tool...");
419 }
420
421 #[test]
422 fn test_truncate_path() {
423 assert_eq!(truncate_path("short", 10), "short");
424 assert_eq!(
425 truncate_path("very_long_path_to_file.txt", 14),
426 "...to_file.txt"
427 );
428 }
429
430 #[test]
431 fn test_truncate_command() {
432 assert_eq!(truncate_command("short", 10), "short");
433 assert_eq!(truncate_command("very_long_command_here", 10), "very_lo...");
434 }
435
436 #[test]
437 fn test_git_commands() {
438 assert_eq!(
439 format_activity_message("git_status", &json!({})),
440 "Checking git status..."
441 );
442 assert_eq!(
443 format_activity_message("git_diff", &json!({})),
444 "Checking git diff..."
445 );
446 assert_eq!(
447 format_activity_message("git_add", &json!({})),
448 "Staging files..."
449 );
450 assert_eq!(
451 format_activity_message("git_commit", &json!({})),
452 "Creating commit..."
453 );
454 }
455
456 #[test]
457 fn test_file_operations() {
458 let args = json!({"path": "/test.txt"});
459 assert!(format_activity_message("file_read", &args).contains("Reading"));
460 assert!(format_activity_message("file_write", &args).contains("Writing"));
461 assert!(format_activity_message("file_edit", &args).contains("Editing"));
462 }
463
464 #[test]
465 fn test_browser_navigation() {
466 assert_eq!(
467 format_activity_message(
468 "browser",
469 &json!({"action": "open", "url": "https://example.com"})
470 ),
471 "Executing browser: opening https://example.com..."
472 );
473 assert_eq!(
474 format_activity_message("browser", &json!({"action": "close"})),
475 "Executing browser: closing browser..."
476 );
477 assert_eq!(
478 format_activity_message(
479 "browser",
480 &json!({"action": "screenshot", "path": "/tmp/shot.png"})
481 ),
482 "Executing browser: taking screenshot to /tmp/shot.png..."
483 );
484 assert_eq!(
485 format_activity_message("browser", &json!({"action": "back"})),
486 "Executing browser: navigating back..."
487 );
488 assert_eq!(
489 format_activity_message("browser", &json!({"action": "reload"})),
490 "Executing browser: reloading page..."
491 );
492 }
493
494 #[test]
495 fn test_browser_interaction() {
496 assert_eq!(
497 format_activity_message(
498 "browser",
499 &json!({"action": "click", "selector": "#submit"})
500 ),
501 "Executing browser: clicking #submit..."
502 );
503 assert_eq!(
504 format_activity_message(
505 "browser",
506 &json!({"action": "fill", "selector": "#email", "text": "test@example.com"})
507 ),
508 "Executing browser: filling #email with \"test@example.com\"..."
509 );
510 assert_eq!(
511 format_activity_message("browser", &json!({"action": "press", "key": "Enter"})),
512 "Executing browser: pressing Enter..."
513 );
514 assert_eq!(
515 format_activity_message("browser", &json!({"action": "hover", "selector": ".menu"})),
516 "Executing browser: hovering .menu..."
517 );
518 assert_eq!(
519 format_activity_message("browser", &json!({"action": "check", "selector": "#terms"})),
520 "Executing browser: checking #terms..."
521 );
522 assert_eq!(
523 format_activity_message(
524 "browser",
525 &json!({"action": "uncheck", "selector": "#newsletter"})
526 ),
527 "Executing browser: unchecking #newsletter..."
528 );
529 }
530
531 #[test]
532 fn test_browser_wait() {
533 assert_eq!(
534 format_activity_message("browser", &json!({"action": "wait", "ms": 1000})),
535 "Executing browser: waiting 1000ms..."
536 );
537 assert_eq!(
538 format_activity_message(
539 "browser",
540 &json!({"action": "wait_for_text", "text": "Login"})
541 ),
542 "Executing browser: waiting for text \"Login\"..."
543 );
544 assert_eq!(
545 format_activity_message(
546 "browser",
547 &json!({"action": "wait_for_url", "url": "/dashboard"})
548 ),
549 "Executing browser: waiting for URL /dashboard..."
550 );
551 assert_eq!(
552 format_activity_message("browser", &json!({"action": "wait_for_load"})),
553 "Executing browser: waiting for page load..."
554 );
555 }
556
557 #[test]
558 fn test_browser_tabs() {
559 assert_eq!(
560 format_activity_message("browser", &json!({"action": "tab_list"})),
561 "Executing browser: listing tabs..."
562 );
563 assert_eq!(
564 format_activity_message(
565 "browser",
566 &json!({"action": "tab_new", "url": "https://example.com"})
567 ),
568 "Executing browser: opening new tab https://example.com..."
569 );
570 assert_eq!(
571 format_activity_message("browser", &json!({"action": "tab_select", "index": 2})),
572 "Executing browser: selecting tab 2..."
573 );
574 assert_eq!(
575 format_activity_message("browser", &json!({"action": "tab_close", "index": 1})),
576 "Executing browser: closing tab 1..."
577 );
578 }
579
580 #[test]
581 fn test_browser_query() {
582 assert_eq!(
583 format_activity_message("browser", &json!({"action": "get", "selector": ".item"})),
584 "Executing browser: getting element .item..."
585 );
586 assert_eq!(
587 format_activity_message(
588 "browser",
589 &json!({"action": "get_attr", "selector": "a", "attr": "href"})
590 ),
591 "Executing browser: getting href attribute from a..."
592 );
593 assert_eq!(
594 format_activity_message(
595 "browser",
596 &json!({"action": "get_count", "selector": ".items"})
597 ),
598 "Executing browser: counting .items..."
599 );
600 }
601
602 #[test]
603 fn test_browser_settings() {
604 assert_eq!(
605 format_activity_message(
606 "browser",
607 &json!({"action": "set_viewport", "width": 1920, "height": 1080})
608 ),
609 "Executing browser: setting viewport to 1920x1080..."
610 );
611 assert_eq!(
612 format_activity_message(
613 "browser",
614 &json!({"action": "set_device", "device": "iPhone 12"})
615 ),
616 "Executing browser: setting device to iPhone 12..."
617 );
618 assert_eq!(
619 format_activity_message(
620 "browser",
621 &json!({"action": "set_geo", "latitude": 37.7749, "longitude": -122.4194})
622 ),
623 "Executing browser: setting geolocation to (37.7749, -122.4194)..."
624 );
625 }
626
627 #[test]
628 fn test_browser_dialog() {
629 assert_eq!(
630 format_activity_message("browser", &json!({"action": "dialog_accept", "text": "OK"})),
631 "Executing browser: accepting dialog with \"OK\"..."
632 );
633 assert_eq!(
634 format_activity_message("browser", &json!({"action": "dialog_dismiss"})),
635 "Executing browser: dismissing dialog..."
636 );
637 }
638
639 #[test]
640 fn test_browser_storage() {
641 assert_eq!(
642 format_activity_message(
643 "browser",
644 &json!({"action": "cookies", "url": "https://example.com"})
645 ),
646 "Executing browser: getting cookies for https://example.com..."
647 );
648 assert_eq!(
649 format_activity_message("browser", &json!({"action": "storage_get", "key": "token"})),
650 "Executing browser: getting storage \"token\"..."
651 );
652 assert_eq!(
653 format_activity_message(
654 "browser",
655 &json!({"action": "storage_set", "key": "session"})
656 ),
657 "Executing browser: setting storage \"session\"..."
658 );
659 }
660
661 #[test]
662 fn test_browser_eval() {
663 assert_eq!(
664 format_activity_message(
665 "browser",
666 &json!({"action": "eval", "script": "return 1 + 1"})
667 ),
668 "Executing browser: evaluating return 1 + 1..."
669 );
670 }
671
672 #[test]
673 fn test_browser_unknown_action() {
674 assert_eq!(
675 format_activity_message("browser", &json!({"action": "unknown_action"})),
676 "Executing browser: unknown_action..."
677 );
678 }
679
680 #[test]
681 fn test_browser_missing_args() {
682 assert_eq!(
683 format_activity_message("browser", &json!({"action": "open"})),
684 "Executing browser: opening URL..."
685 );
686 assert_eq!(
687 format_activity_message("browser", &json!({"action": "click"})),
688 "Executing browser: clicking element..."
689 );
690 assert_eq!(
691 format_activity_message("browser", &json!({"action": "wait"})),
692 "Executing browser: waiting..."
693 );
694 }
695
696 #[test]
697 fn test_browser_navigation_extras() {
698 assert_eq!(
699 format_activity_message("browser", &json!({"action": "snapshot"})),
700 "Executing browser: taking DOM snapshot..."
701 );
702 assert_eq!(
703 format_activity_message("browser", &json!({"action": "forward"})),
704 "Executing browser: navigating forward..."
705 );
706 }
707
708 #[test]
709 fn test_browser_interaction_extras() {
710 assert_eq!(
711 format_activity_message(
712 "browser",
713 &json!({"action": "dblclick", "selector": "#btn"})
714 ),
715 "Executing browser: double-clicking #btn..."
716 );
717 assert_eq!(
718 format_activity_message("browser", &json!({"action": "focus", "selector": "#input"})),
719 "Executing browser: focusing #input..."
720 );
721 assert_eq!(
722 format_activity_message(
723 "browser",
724 &json!({"action": "scrollintoview", "selector": "#footer"})
725 ),
726 "Executing browser: scrolling to #footer..."
727 );
728 assert_eq!(
729 format_activity_message("browser", &json!({"action": "type", "selector": "#search"})),
730 "Executing browser: typing into #search..."
731 );
732 assert_eq!(
733 format_activity_message(
734 "browser",
735 &json!({"action": "select", "selector": "#country", "value": "BR"})
736 ),
737 "Executing browser: selecting \"BR\" in #country..."
738 );
739 assert_eq!(
740 format_activity_message(
741 "browser",
742 &json!({"action": "drag", "source": "#item", "target": "#dropzone"})
743 ),
744 "Executing browser: dragging #item to #dropzone..."
745 );
746 assert_eq!(
747 format_activity_message(
748 "browser",
749 &json!({"action": "upload", "selector": "#file", "path": "/tmp/file.txt"})
750 ),
751 "Executing browser: uploading /tmp/file.txt to #file..."
752 );
753 assert_eq!(
754 format_activity_message(
755 "browser",
756 &json!({"action": "pdf", "path": "/tmp/page.pdf"})
757 ),
758 "Executing browser: saving PDF to /tmp/page.pdf..."
759 );
760 }
761
762 #[test]
763 fn test_browser_query_extras() {
764 assert_eq!(
765 format_activity_message(
766 "browser",
767 &json!({"action": "get_box", "selector": "#modal"})
768 ),
769 "Executing browser: getting bounding box of #modal..."
770 );
771 assert_eq!(
772 format_activity_message(
773 "browser",
774 &json!({"action": "get_styles", "selector": ".button"})
775 ),
776 "Executing browser: getting styles of .button..."
777 );
778 }
779
780 #[test]
781 fn test_browser_wait_extras() {
782 assert_eq!(
783 format_activity_message("browser", &json!({"action": "wait_for_download"})),
784 "Executing browser: waiting for download..."
785 );
786 assert_eq!(
787 format_activity_message("browser", &json!({"action": "wait_for_fn"})),
788 "Executing browser: waiting for function result..."
789 );
790 assert_eq!(
791 format_activity_message(
792 "browser",
793 &json!({"action": "wait_for_state", "state": "visible"})
794 ),
795 "Executing browser: waiting for visible state..."
796 );
797 }
798
799 #[test]
800 fn test_browser_state() {
801 assert_eq!(
802 format_activity_message("browser", &json!({"action": "find", "selector": ".item"})),
803 "Executing browser: finding .item..."
804 );
805 assert_eq!(
806 format_activity_message("browser", &json!({"action": "scroll", "x": 0, "y": 500})),
807 "Executing browser: scrolling to (0, 500)..."
808 );
809 assert_eq!(
810 format_activity_message(
811 "browser",
812 &json!({"action": "is", "selector": "#btn", "state": "visible"})
813 ),
814 "Executing browser: checking if #btn is visible..."
815 );
816 assert_eq!(
817 format_activity_message(
818 "browser",
819 &json!({"action": "download", "url": "https://example.com/file.zip"})
820 ),
821 "Executing browser: downloading https://example.com/file.zip..."
822 );
823 }
824
825 #[test]
826 fn test_browser_storage_network() {
827 assert_eq!(
828 format_activity_message("browser", &json!({"action": "cookies_set"})),
829 "Executing browser: setting cookies..."
830 );
831 assert_eq!(
832 format_activity_message("browser", &json!({"action": "network_requests"})),
833 "Executing browser: getting network requests..."
834 );
835 }
836}