browser_use/tools/
click.rs

1use crate::error::{BrowserError, Result};
2use crate::tools::{Tool, ToolContext, ToolResult};
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5
6/// Parameters for the click tool
7#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
8pub struct ClickParams {
9    /// CSS selector (use either this or index, not both)
10    #[serde(skip_serializing_if = "Option::is_none")]
11    pub selector: Option<String>,
12
13    /// Element index from DOM tree (use either this or selector, not both)
14    #[serde(skip_serializing_if = "Option::is_none")]
15    pub index: Option<usize>,
16}
17
18/// Tool for clicking elements
19#[derive(Default)]
20pub struct ClickTool;
21
22impl Tool for ClickTool {
23    type Params = ClickParams;
24
25    fn name(&self) -> &str {
26        "click"
27    }
28
29    fn execute_typed(&self, params: ClickParams, context: &mut ToolContext) -> Result<ToolResult> {
30        // Validate that exactly one selector method is provided
31        match (&params.selector, &params.index) {
32            (Some(_), Some(_)) => {
33                return Err(BrowserError::ToolExecutionFailed {
34                    tool: "click".to_string(),
35                    reason: "Cannot specify both 'selector' and 'index'. Use one or the other."
36                        .to_string(),
37                });
38            }
39            (None, None) => {
40                return Err(BrowserError::ToolExecutionFailed {
41                    tool: "click".to_string(),
42                    reason: "Must specify either 'selector' or 'index'.".to_string(),
43                });
44            }
45            _ => {}
46        }
47
48        if let Some(selector) = params.selector {
49            // CSS selector path
50            let tab = context.session.tab()?;
51            let element = context.session.find_element(&tab, &selector)?;
52            element
53                .click()
54                .map_err(|e| BrowserError::ToolExecutionFailed {
55                    tool: "click".to_string(),
56                    reason: e.to_string(),
57                })?;
58
59            Ok(ToolResult::success_with(serde_json::json!({
60                "selector": selector,
61                "method": "css"
62            })))
63        } else if let Some(index) = params.index {
64            // Index path - convert index to CSS selector
65            let css_selector = {
66                let dom = context.get_dom()?;
67                let selector = dom.get_selector(index).ok_or_else(|| {
68                    BrowserError::ElementNotFound(format!("No element with index {}", index))
69                })?;
70                selector.clone()
71            };
72
73            let tab = context.session.tab()?;
74            let element = context.session.find_element(&tab, &css_selector)?;
75            element
76                .click()
77                .map_err(|e| BrowserError::ToolExecutionFailed {
78                    tool: "click".to_string(),
79                    reason: e.to_string(),
80                })?;
81
82            Ok(ToolResult::success_with(serde_json::json!({
83                "index": index,
84                "selector": css_selector,
85                "method": "index"
86            })))
87        } else {
88            unreachable!("Validation above ensures one field is Some")
89        }
90    }
91}