Skip to main content

adk_browser/tools/
click.rs

1//! Click tool for interacting with page elements.
2
3use crate::session::BrowserSession;
4use adk_core::{Result, Tool, ToolContext};
5use async_trait::async_trait;
6use serde_json::{Value, json};
7use std::sync::Arc;
8
9/// Tool for clicking elements on the page.
10pub struct ClickTool {
11    browser: Arc<BrowserSession>,
12}
13
14impl ClickTool {
15    /// Create a new click tool with a shared browser session.
16    pub fn new(browser: Arc<BrowserSession>) -> Self {
17        Self { browser }
18    }
19}
20
21#[async_trait]
22impl Tool for ClickTool {
23    fn name(&self) -> &str {
24        "browser_click"
25    }
26
27    fn description(&self) -> &str {
28        "Click on an element on the page. Use CSS selectors to identify the element."
29    }
30
31    fn parameters_schema(&self) -> Option<Value> {
32        Some(json!({
33            "type": "object",
34            "properties": {
35                "selector": {
36                    "type": "string",
37                    "description": "CSS selector for the element to click (e.g., '#submit-btn', '.nav-link', 'button[type=submit]')"
38                },
39                "wait_timeout": {
40                    "type": "integer",
41                    "description": "Optional timeout in seconds to wait for element to be clickable (default: 10)"
42                }
43            },
44            "required": ["selector"]
45        }))
46    }
47
48    fn response_schema(&self) -> Option<Value> {
49        Some(json!({
50            "type": "object",
51            "properties": {
52                "success": { "type": "boolean" },
53                "clicked_element": { "type": "string" }
54            }
55        }))
56    }
57
58    async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
59        let selector = args
60            .get("selector")
61            .and_then(|v| v.as_str())
62            .ok_or_else(|| adk_core::AdkError::Tool("Missing 'selector' parameter".to_string()))?;
63
64        let wait_timeout = args.get("wait_timeout").and_then(|v| v.as_u64()).unwrap_or(10);
65
66        // Wait for element to be clickable, then click
67        let element = self.browser.wait_for_clickable(selector, wait_timeout).await?;
68
69        element
70            .click()
71            .await
72            .map_err(|e| adk_core::AdkError::Tool(format!("Click failed: {}", e)))?;
73
74        // Get element info for response
75        let tag_name = element.tag_name().await.unwrap_or_else(|_| "unknown".to_string());
76
77        let text = element.text().await.unwrap_or_default();
78        let element_info = if text.is_empty() {
79            tag_name
80        } else {
81            format!("{}: {}", tag_name, text.chars().take(50).collect::<String>())
82        };
83
84        // Include page context so the agent knows what happened after the click
85        let context = self.browser.page_context().await.unwrap_or_default();
86
87        Ok(json!({
88            "success": true,
89            "clicked_element": element_info,
90            "page": context
91        }))
92    }
93}
94
95/// Tool for double-clicking elements.
96pub struct DoubleClickTool {
97    browser: Arc<BrowserSession>,
98}
99
100impl DoubleClickTool {
101    pub fn new(browser: Arc<BrowserSession>) -> Self {
102        Self { browser }
103    }
104}
105
106#[async_trait]
107impl Tool for DoubleClickTool {
108    fn name(&self) -> &str {
109        "browser_double_click"
110    }
111
112    fn description(&self) -> &str {
113        "Double-click on an element on the page."
114    }
115
116    fn parameters_schema(&self) -> Option<Value> {
117        Some(json!({
118            "type": "object",
119            "properties": {
120                "selector": {
121                    "type": "string",
122                    "description": "CSS selector for the element to double-click"
123                }
124            },
125            "required": ["selector"]
126        }))
127    }
128
129    async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
130        let selector = args
131            .get("selector")
132            .and_then(|v| v.as_str())
133            .ok_or_else(|| adk_core::AdkError::Tool("Missing 'selector' parameter".to_string()))?;
134
135        let escaped = crate::escape::escape_js_string(selector);
136
137        // Execute double-click via JS and get tag name in one call
138        let result = self.browser
139            .execute_script(&format!(
140                "var el = document.querySelector('{escaped}'); if (!el) return null; el.dispatchEvent(new MouseEvent('dblclick', {{'view': window, 'bubbles': true, 'cancelable': true}})); return el.tagName.toLowerCase();"
141            ))
142            .await?;
143
144        let tag_name = result.as_str().unwrap_or("unknown");
145        if tag_name == "unknown" && result.is_null() {
146            return Err(adk_core::AdkError::Tool(format!("Element not found: {selector}")));
147        }
148
149        Ok(json!({
150            "success": true,
151            "double_clicked_element": tag_name
152        }))
153    }
154}