browser_use/tools/
select.rs

1use crate::error::{BrowserError, Result};
2use crate::tools::{Tool, ToolContext, ToolResult};
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5
6/// Parameters for the select tool
7#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
8pub struct SelectParams {
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    /// Value to select in the dropdown
18    pub value: String,
19}
20
21/// Tool for selecting dropdown options
22#[derive(Default)]
23pub struct SelectTool;
24
25const SELECT_JS: &str = include_str!("select.js");
26
27impl Tool for SelectTool {
28    type Params = SelectParams;
29
30    fn name(&self) -> &str {
31        "select"
32    }
33
34    fn execute_typed(&self, params: SelectParams, context: &mut ToolContext) -> Result<ToolResult> {
35        // Validate that exactly one selector method is provided
36        match (&params.selector, &params.index) {
37            (Some(_), Some(_)) => {
38                return Err(BrowserError::ToolExecutionFailed {
39                    tool: "select".to_string(),
40                    reason: "Cannot specify both 'selector' and 'index'. Use one or the other."
41                        .to_string(),
42                });
43            }
44            (None, None) => {
45                return Err(BrowserError::ToolExecutionFailed {
46                    tool: "select".to_string(),
47                    reason: "Must specify either 'selector' or 'index'.".to_string(),
48                });
49            }
50            _ => {}
51        }
52
53        let css_selector = if let Some(selector) = params.selector {
54            selector
55        } else if let Some(index) = params.index {
56            let dom = context.get_dom()?;
57            let selector = dom.get_selector(index).ok_or_else(|| {
58                BrowserError::ElementNotFound(format!("No element with index {}", index))
59            })?;
60            selector.clone()
61        } else {
62            unreachable!("Validation above ensures one field is Some")
63        };
64        let value = params.value;
65
66        let select_config = serde_json::json!({
67            "selector": css_selector,
68            "value": value,
69        });
70        let select_js = SELECT_JS.replace("__SELECT_CONFIG__", &select_config.to_string());
71
72        let result = context
73            .session
74            .tab()?
75            .evaluate(&select_js, false)
76            .map_err(|e| BrowserError::ToolExecutionFailed {
77                tool: "select".to_string(),
78                reason: e.to_string(),
79            })?;
80
81        // Parse the JSON string returned by JavaScript
82        let result_json: serde_json::Value = if let Some(serde_json::Value::String(json_str)) =
83            result.value
84        {
85            serde_json::from_str(&json_str)
86                .unwrap_or(serde_json::json!({"success": false, "error": "Failed to parse result"}))
87        } else {
88            result
89                .value
90                .unwrap_or(serde_json::json!({"success": false, "error": "No result returned"}))
91        };
92
93        if result_json["success"].as_bool() == Some(true) {
94            Ok(ToolResult::success_with(serde_json::json!({
95                "selector": css_selector,
96                "value": value,
97                "selectedText": result_json["selectedText"]
98            })))
99        } else {
100            Err(BrowserError::ToolExecutionFailed {
101                tool: "select".to_string(),
102                reason: result_json["error"]
103                    .as_str()
104                    .unwrap_or("Unknown error")
105                    .to_string(),
106            })
107        }
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    #[test]
116    fn test_select_params_css() {
117        let json = serde_json::json!({
118            "selector": "#country-select",
119            "value": "us"
120        });
121
122        let params: SelectParams = serde_json::from_value(json).unwrap();
123        assert_eq!(params.selector, Some("#country-select".to_string()));
124        assert_eq!(params.index, None);
125        assert_eq!(params.value, "us");
126    }
127
128    #[test]
129    fn test_select_params_index() {
130        let json = serde_json::json!({
131            "index": 5,
132            "value": "option2"
133        });
134
135        let params: SelectParams = serde_json::from_value(json).unwrap();
136        assert_eq!(params.selector, None);
137        assert_eq!(params.index, Some(5));
138        assert_eq!(params.value, "option2");
139    }
140}