browser_use/tools/
hover.rs1use crate::error::{BrowserError, Result};
2use crate::tools::{Tool, ToolContext, ToolResult};
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
8pub struct HoverParams {
9 #[serde(skip_serializing_if = "Option::is_none")]
11 pub selector: Option<String>,
12
13 #[serde(skip_serializing_if = "Option::is_none")]
15 pub index: Option<usize>,
16}
17
18#[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 match (¶ms.selector, ¶ms.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 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 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}