add --selector flag to scroll command (#537)

* add --selector flag to scroll command

The `scroll` command uses `window.scrollBy()`, which has no effect on apps
that use custom scrollable containers (e.g. a nested div with overflow-y: auto).

The backend `handleScroll` already supports a `selector` parameter, but the CLI
never exposed it. This adds `-s` / `--selector` to the `scroll` command so users
can target a specific scrollable element:

    agent-browser scroll down 500 --selector "div.scroll-container"

Also fixes the backend to apply `direction`/`amount` when a selector is present
(previously those fields were only used in the no-selector branch).

Closes #501

* fixes
This commit is contained in:
Chris Tate
2026-02-24 07:40:46 -06:00
committed by GitHub
parent 77f2caa1bc
commit f319195974
6 changed files with 157 additions and 35 deletions
+123 -6
View File
@@ -298,12 +298,48 @@ pub fn parse_command(args: &[String], flags: &Flags) -> Result<Value, ParseError
// === Scroll ===
"scroll" => {
let dir = rest.first().unwrap_or(&"down");
let amount = rest
.get(1)
.and_then(|s| s.parse::<i32>().ok())
.unwrap_or(300);
Ok(json!({ "id": id, "action": "scroll", "direction": dir, "amount": amount }))
let mut cmd = json!({ "id": id, "action": "scroll" });
let obj = cmd.as_object_mut().unwrap();
let mut positional_index = 0;
let mut i = 0;
while i < rest.len() {
match rest[i] {
"-s" | "--selector" => {
if let Some(s) = rest.get(i + 1) {
obj.insert("selector".to_string(), json!(s));
i += 1;
} else {
return Err(ParseError::MissingArguments {
context: "scroll --selector".to_string(),
usage: "scroll [direction] [amount] [--selector <sel>]",
});
}
}
arg if arg.starts_with('-') => {}
_ => {
match positional_index {
0 => {
obj.insert("direction".to_string(), json!(rest[i]));
}
1 => {
if let Ok(n) = rest[i].parse::<i32>() {
obj.insert("amount".to_string(), json!(n));
}
}
_ => {}
}
positional_index += 1;
}
}
i += 1;
}
if !obj.contains_key("direction") {
obj.insert("direction".to_string(), json!("down"));
}
if !obj.contains_key("amount") {
obj.insert("amount".to_string(), json!(300));
}
Ok(cmd)
}
"scrollintoview" | "scrollinto" => {
let sel = rest.first().ok_or_else(|| ParseError::MissingArguments {
@@ -3436,4 +3472,85 @@ mod tests {
ParseError::MissingArguments { .. }
));
}
// === Scroll Tests ===
#[test]
fn test_scroll_defaults() {
let cmd = parse_command(&args("scroll"), &default_flags()).unwrap();
assert_eq!(cmd["action"], "scroll");
assert_eq!(cmd["direction"], "down");
assert_eq!(cmd["amount"], 300);
assert!(cmd.get("selector").is_none());
}
#[test]
fn test_scroll_direction_and_amount() {
let cmd = parse_command(&args("scroll up 200"), &default_flags()).unwrap();
assert_eq!(cmd["action"], "scroll");
assert_eq!(cmd["direction"], "up");
assert_eq!(cmd["amount"], 200);
}
#[test]
fn test_scroll_with_selector() {
let cmd = parse_command(
&args("scroll down 500 --selector div.scroll-container"),
&default_flags(),
)
.unwrap();
assert_eq!(cmd["action"], "scroll");
assert_eq!(cmd["direction"], "down");
assert_eq!(cmd["amount"], 500);
assert_eq!(cmd["selector"], "div.scroll-container");
}
#[test]
fn test_scroll_with_selector_short_flag() {
let cmd = parse_command(
&args("scroll left 100 -s .sidebar"),
&default_flags(),
)
.unwrap();
assert_eq!(cmd["action"], "scroll");
assert_eq!(cmd["direction"], "left");
assert_eq!(cmd["amount"], 100);
assert_eq!(cmd["selector"], ".sidebar");
}
#[test]
fn test_scroll_selector_before_positional() {
let cmd = parse_command(
&args("scroll --selector .panel down 400"),
&default_flags(),
)
.unwrap();
assert_eq!(cmd["action"], "scroll");
assert_eq!(cmd["direction"], "down");
assert_eq!(cmd["amount"], 400);
assert_eq!(cmd["selector"], ".panel");
}
#[test]
fn test_scroll_selector_only() {
let cmd = parse_command(
&args("scroll --selector .content"),
&default_flags(),
)
.unwrap();
assert_eq!(cmd["action"], "scroll");
assert_eq!(cmd["direction"], "down");
assert_eq!(cmd["amount"], 300);
assert_eq!(cmd["selector"], ".content");
}
#[test]
fn test_scroll_selector_missing_value() {
let result = parse_command(&args("scroll down 500 --selector"), &default_flags());
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
ParseError::MissingArguments { .. }
));
}
}
+6 -2
View File
@@ -973,14 +973,17 @@ Use Cases:
r##"
agent-browser scroll - Scroll the page
Usage: agent-browser scroll [direction] [amount]
Usage: agent-browser scroll [direction] [amount] [options]
Scrolls the page in the specified direction.
Scrolls the page or a specific element in the specified direction.
Arguments:
direction up, down, left, right (default: down)
amount Pixels to scroll (default: 300)
Options:
-s, --selector <sel> CSS selector for a scrollable container
Global Options:
--json Output as JSON
--session <name> Use specific session
@@ -990,6 +993,7 @@ Examples:
agent-browser scroll down 500
agent-browser scroll up 200
agent-browser scroll left 100
agent-browser scroll down 500 --selector "div.scroll-container"
"##
}
"scrollintoview" | "scrollinto" => {