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    /// Element selector (CSS selector or index)
10    #[serde(flatten)]
11    pub selector: ElementSelector,
12}
13
14#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
15#[serde(untagged)]
16pub enum ElementSelector {
17    /// Select by CSS selector
18    Css {
19        /// CSS selector
20        selector: String,
21    },
22    /// Select by index from DOM tree
23    Index {
24        /// Element index
25        index: usize,
26    },
27}
28
29/// Tool for hovering over elements
30#[derive(Default)]
31pub struct HoverTool;
32
33const HOVER_JS: &str = include_str!("hover.js");
34
35impl Tool for HoverTool {
36    type Params = HoverParams;
37
38    fn name(&self) -> &str {
39        "hover"
40    }
41
42    fn execute_typed(&self, params: HoverParams, context: &mut ToolContext) -> Result<ToolResult> {
43        let css_selector = match params.selector {
44            ElementSelector::Css { selector } => selector,
45            ElementSelector::Index { index } => {
46                let dom = context.get_dom()?;
47                let selector_info = dom.get_selector(index).ok_or_else(|| {
48                    BrowserError::ElementNotFound(format!("No element with index {}", index))
49                })?;
50                selector_info.css_selector.clone()
51            }
52        };
53
54        // Find the element (to verify it exists)
55
56        // Scroll into view if needed, then hover
57        let selector_json =
58            serde_json::to_string(&css_selector).expect("serializing CSS selector never fails");
59        let hover_js = HOVER_JS.replace("__SELECTOR__", &selector_json);
60
61        let result = context
62            .session
63            .tab()
64            .evaluate(&hover_js, false)
65            .map_err(|e| BrowserError::ToolExecutionFailed {
66                tool: "hover".to_string(),
67                reason: e.to_string(),
68            })?;
69
70        // Parse the JSON string returned by JavaScript
71        let result_json: serde_json::Value = if let Some(serde_json::Value::String(json_str)) =
72            result.value
73        {
74            serde_json::from_str(&json_str)
75                .unwrap_or(serde_json::json!({"success": false, "error": "Failed to parse result"}))
76        } else {
77            result
78                .value
79                .unwrap_or(serde_json::json!({"success": false, "error": "No result returned"}))
80        };
81
82        if result_json["success"].as_bool() == Some(true) {
83            Ok(ToolResult::success_with(serde_json::json!({
84                "selector": css_selector,
85                "element": {
86                    "tagName": result_json["tagName"],
87                    "id": result_json["id"],
88                    "className": result_json["className"]
89                }
90            })))
91        } else {
92            Err(BrowserError::ToolExecutionFailed {
93                tool: "hover".to_string(),
94                reason: result_json["error"]
95                    .as_str()
96                    .unwrap_or("Unknown error")
97                    .to_string(),
98            })
99        }
100    }
101}