browser_use/tools/
scroll.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 ScrollParams {
9 #[serde(skip_serializing_if = "Option::is_none")]
12 pub amount: Option<i32>,
13}
14
15#[derive(Default)]
17pub struct ScrollTool;
18
19const SCROLL_JS: &str = include_str!("scroll.js");
20
21impl Tool for ScrollTool {
22 type Params = ScrollParams;
23
24 fn name(&self) -> &str {
25 "scroll"
26 }
27
28 fn execute_typed(&self, params: ScrollParams, context: &mut ToolContext) -> Result<ToolResult> {
29 let config = serde_json::json!({
30 "amount": params.amount
31 });
32 let scroll_js = SCROLL_JS.replace("__SCROLL_CONFIG__", &config.to_string());
33
34 let result = context
35 .session
36 .tab()?
37 .evaluate(&scroll_js, true)
38 .map_err(|e| BrowserError::ToolExecutionFailed {
39 tool: "scroll".to_string(),
40 reason: e.to_string(),
41 })?;
42
43 let result_json: serde_json::Value =
45 if let Some(serde_json::Value::String(json_str)) = result.value {
46 serde_json::from_str(&json_str)
47 .unwrap_or(serde_json::json!({"actualScroll": 0, "isAtBottom": false}))
48 } else {
49 result
50 .value
51 .unwrap_or(serde_json::json!({"actualScroll": 0, "isAtBottom": false}))
52 };
53
54 let actual_scroll = result_json["actualScroll"].as_i64().unwrap_or(0);
55 let is_at_bottom = result_json["isAtBottom"].as_bool().unwrap_or(false);
56
57 let message = if is_at_bottom {
58 format!(
59 "Scrolled {} pixels. Reached the bottom of the page.",
60 actual_scroll
61 )
62 } else {
63 format!(
64 "Scrolled {} pixels. Did not reach the bottom of the page.",
65 actual_scroll
66 )
67 };
68
69 Ok(ToolResult::success_with(serde_json::json!({
70 "scrolled": actual_scroll,
71 "isAtBottom": is_at_bottom,
72 "message": message
73 })))
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80
81 #[test]
82 fn test_scroll_params_with_amount() {
83 let json = serde_json::json!({
84 "amount": 500
85 });
86
87 let params: ScrollParams = serde_json::from_value(json).unwrap();
88 assert_eq!(params.amount, Some(500));
89 }
90
91 #[test]
92 fn test_scroll_params_negative_amount() {
93 let json = serde_json::json!({
94 "amount": -300
95 });
96
97 let params: ScrollParams = serde_json::from_value(json).unwrap();
98 assert_eq!(params.amount, Some(-300));
99 }
100
101 #[test]
102 fn test_scroll_params_no_amount() {
103 let json = serde_json::json!({});
104
105 let params: ScrollParams = serde_json::from_value(json).unwrap();
106 assert_eq!(params.amount, None);
107 }
108}