adk_browser/tools/
wait.rs

1//! Wait tools for synchronization.
2
3use crate::session::BrowserSession;
4use adk_core::{Result, Tool, ToolContext};
5use async_trait::async_trait;
6use serde_json::{json, Value};
7use std::sync::Arc;
8use std::time::Duration;
9
10/// Tool for waiting for an element to appear.
11pub struct WaitForElementTool {
12    browser: Arc<BrowserSession>,
13}
14
15impl WaitForElementTool {
16    /// Create a new wait tool with a shared browser session.
17    pub fn new(browser: Arc<BrowserSession>) -> Self {
18        Self { browser }
19    }
20}
21
22#[async_trait]
23impl Tool for WaitForElementTool {
24    fn name(&self) -> &str {
25        "browser_wait_for_element"
26    }
27
28    fn description(&self) -> &str {
29        "Wait for an element to appear on the page. Useful after navigation or dynamic content loading."
30    }
31
32    fn parameters_schema(&self) -> Option<Value> {
33        Some(json!({
34            "type": "object",
35            "properties": {
36                "selector": {
37                    "type": "string",
38                    "description": "CSS selector for the element to wait for"
39                },
40                "timeout": {
41                    "type": "integer",
42                    "description": "Maximum wait time in seconds (default: 30)"
43                },
44                "visible": {
45                    "type": "boolean",
46                    "description": "Wait for element to be visible, not just present (default: false)"
47                }
48            },
49            "required": ["selector"]
50        }))
51    }
52
53    fn response_schema(&self) -> Option<Value> {
54        Some(json!({
55            "type": "object",
56            "properties": {
57                "success": { "type": "boolean" },
58                "found": { "type": "boolean" },
59                "element_text": { "type": "string" }
60            }
61        }))
62    }
63
64    async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
65        let selector = args
66            .get("selector")
67            .and_then(|v| v.as_str())
68            .ok_or_else(|| adk_core::AdkError::Tool("Missing 'selector' parameter".to_string()))?;
69
70        let timeout = args.get("timeout").and_then(|v| v.as_u64()).unwrap_or(30);
71
72        let visible = args.get("visible").and_then(|v| v.as_bool()).unwrap_or(false);
73
74        let element = if visible {
75            self.browser.wait_for_clickable(selector, timeout).await?
76        } else {
77            self.browser.wait_for_element(selector, timeout).await?
78        };
79
80        let text = element.text().await.unwrap_or_default();
81        let text_preview = text.chars().take(100).collect::<String>();
82
83        Ok(json!({
84            "success": true,
85            "found": true,
86            "element_text": text_preview
87        }))
88    }
89}
90
91/// Tool for waiting a fixed duration.
92pub struct WaitTool;
93
94impl WaitTool {
95    pub fn new() -> Self {
96        Self
97    }
98}
99
100impl Default for WaitTool {
101    fn default() -> Self {
102        Self::new()
103    }
104}
105
106#[async_trait]
107impl Tool for WaitTool {
108    fn name(&self) -> &str {
109        "browser_wait"
110    }
111
112    fn description(&self) -> &str {
113        "Wait for a specified duration. Use sparingly - prefer waiting for specific elements."
114    }
115
116    fn parameters_schema(&self) -> Option<Value> {
117        Some(json!({
118            "type": "object",
119            "properties": {
120                "seconds": {
121                    "type": "number",
122                    "description": "Number of seconds to wait (max: 30)"
123                }
124            },
125            "required": ["seconds"]
126        }))
127    }
128
129    async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
130        let seconds = args
131            .get("seconds")
132            .and_then(|v| v.as_f64())
133            .ok_or_else(|| adk_core::AdkError::Tool("Missing 'seconds' parameter".to_string()))?;
134
135        // Cap at 30 seconds
136        let seconds = seconds.min(30.0);
137
138        tokio::time::sleep(Duration::from_secs_f64(seconds)).await;
139
140        Ok(json!({
141            "success": true,
142            "waited_seconds": seconds
143        }))
144    }
145}
146
147/// Tool for waiting for page to load.
148pub struct WaitForPageLoadTool {
149    browser: Arc<BrowserSession>,
150}
151
152impl WaitForPageLoadTool {
153    pub fn new(browser: Arc<BrowserSession>) -> Self {
154        Self { browser }
155    }
156}
157
158#[async_trait]
159impl Tool for WaitForPageLoadTool {
160    fn name(&self) -> &str {
161        "browser_wait_for_page_load"
162    }
163
164    fn description(&self) -> &str {
165        "Wait for the page to finish loading (document.readyState === 'complete')."
166    }
167
168    fn parameters_schema(&self) -> Option<Value> {
169        Some(json!({
170            "type": "object",
171            "properties": {
172                "timeout": {
173                    "type": "integer",
174                    "description": "Maximum wait time in seconds (default: 30)"
175                }
176            }
177        }))
178    }
179
180    async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
181        let timeout = args.get("timeout").and_then(|v| v.as_u64()).unwrap_or(30);
182
183        let script = "return document.readyState";
184        let start = std::time::Instant::now();
185
186        loop {
187            let result = self.browser.execute_script(script).await?;
188            if result.as_str() == Some("complete") {
189                break;
190            }
191
192            if start.elapsed().as_secs() > timeout {
193                return Err(adk_core::AdkError::Tool(format!(
194                    "Page load timeout after {}s",
195                    timeout
196                )));
197            }
198
199            tokio::time::sleep(Duration::from_millis(100)).await;
200        }
201
202        let url = self.browser.current_url().await?;
203        let title = self.browser.title().await?;
204
205        Ok(json!({
206            "success": true,
207            "url": url,
208            "title": title,
209            "ready_state": "complete"
210        }))
211    }
212}
213
214/// Tool for waiting for text to appear.
215pub struct WaitForTextTool {
216    browser: Arc<BrowserSession>,
217}
218
219impl WaitForTextTool {
220    pub fn new(browser: Arc<BrowserSession>) -> Self {
221        Self { browser }
222    }
223}
224
225#[async_trait]
226impl Tool for WaitForTextTool {
227    fn name(&self) -> &str {
228        "browser_wait_for_text"
229    }
230
231    fn description(&self) -> &str {
232        "Wait for specific text to appear anywhere on the page."
233    }
234
235    fn parameters_schema(&self) -> Option<Value> {
236        Some(json!({
237            "type": "object",
238            "properties": {
239                "text": {
240                    "type": "string",
241                    "description": "The text to wait for"
242                },
243                "timeout": {
244                    "type": "integer",
245                    "description": "Maximum wait time in seconds (default: 30)"
246                }
247            },
248            "required": ["text"]
249        }))
250    }
251
252    async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
253        let text = args
254            .get("text")
255            .and_then(|v| v.as_str())
256            .ok_or_else(|| adk_core::AdkError::Tool("Missing 'text' parameter".to_string()))?;
257
258        let timeout = args.get("timeout").and_then(|v| v.as_u64()).unwrap_or(30);
259
260        let script =
261            format!("return document.body.innerText.includes('{}')", text.replace('\'', "\\'"));
262
263        let start = std::time::Instant::now();
264
265        loop {
266            let result = self.browser.execute_script(&script).await?;
267            if result.as_bool() == Some(true) {
268                return Ok(json!({
269                    "success": true,
270                    "found": true,
271                    "text": text
272                }));
273            }
274
275            if start.elapsed().as_secs() > timeout {
276                return Err(adk_core::AdkError::Tool(format!(
277                    "Text '{}' not found after {}s",
278                    text, timeout
279                )));
280            }
281
282            tokio::time::sleep(Duration::from_millis(100)).await;
283        }
284    }
285}