browser_use/tools/
hover.rs

1use crate::error::{BrowserError, Result};
2use crate::tools::{Tool, ToolContext, ToolResult};
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5
6/// Parameters for the hover tool
7#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
8pub struct HoverParams {
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 hovering over elements
19#[derive(Default)]
20pub struct HoverTool;
21
22const HOVER_JS: &str = include_str!("hover.js");
23
24impl Tool for HoverTool {
25    type Params = HoverParams;
26
27    fn name(&self) -> &str {
28        "hover"
29    }
30
31    fn execute_typed(&self, params: HoverParams, context: &mut ToolContext) -> Result<ToolResult> {
32        // Validate that exactly one selector method is provided
33        match (&params.selector, &params.index) {
34            (Some(_), Some(_)) => {
35                return Err(BrowserError::ToolExecutionFailed {
36                    tool: "hover".to_string(),
37                    reason: "Cannot specify both 'selector' and 'index'. Use one or the other."
38                        .to_string(),
39                });
40            }
41            (None, None) => {
42                return Err(BrowserError::ToolExecutionFailed {
43                    tool: "hover".to_string(),
44                    reason: "Must specify either 'selector' or 'index'.".to_string(),
45                });
46            }
47            _ => {}
48        }
49
50        let css_selector = if let Some(selector) = params.selector {
51            selector
52        } else if let Some(index) = params.index {
53            let dom = context.get_dom()?;
54            let selector = dom.get_selector(index).ok_or_else(|| {
55                BrowserError::ElementNotFound(format!("No element with index {}", index))
56            })?;
57            selector.clone()
58        } else {
59            unreachable!("Validation above ensures one field is Some")
60        };
61
62        // Find the element (to verify it exists)
63
64        // Scroll into view if needed, then hover
65        let selector_json =
66            serde_json::to_string(&css_selector).expect("serializing CSS selector never fails");
67        let hover_js = HOVER_JS.replace("__SELECTOR__", &selector_json);
68
69        let result = context
70            .session
71            .tab()?
72            .evaluate(&hover_js, false)
73            .map_err(|e| BrowserError::ToolExecutionFailed {
74                tool: "hover".to_string(),
75                reason: e.to_string(),
76            })?;
77
78        // Parse the JSON string returned by JavaScript
79        let result_json: serde_json::Value = if let Some(serde_json::Value::String(json_str)) =
80            result.value
81        {
82            serde_json::from_str(&json_str)
83                .unwrap_or(serde_json::json!({"success": false, "error": "Failed to parse result"}))
84        } else {
85            result
86                .value
87                .unwrap_or(serde_json::json!({"success": false, "error": "No result returned"}))
88        };
89
90        if result_json["success"].as_bool() == Some(true) {
91            Ok(ToolResult::success_with(serde_json::json!({
92                "selector": css_selector,
93                "element": {
94                    "tagName": result_json["tagName"],
95                    "id": result_json["id"],
96                    "className": result_json["className"]
97                }
98            })))
99        } else {
100            Err(BrowserError::ToolExecutionFailed {
101                tool: "hover".to_string(),
102                reason: result_json["error"]
103                    .as_str()
104                    .unwrap_or("Unknown error")
105                    .to_string(),
106            })
107        }
108    }
109}