feat(screenshot): hide scrollbars in headless screenshots (cherry-pick b4f2f37)

Cherry-picks upstream agent-browser #1396. Adds a configurable
--hide-scrollbars flag (AGENT_BROWSER_HIDE_SCROLLBARS env, hideScrollbars
config key, default true) that appends Chrome's --hide-scrollbars launch arg
for headless (non-extension) launches so native scrollbars aren't painted into
screenshots. Plumbed through flags.rs, connection.rs, main.rs, native/actions.rs
and native/cdp/chrome.rs; help text in output.rs + skill-data.

Fork adaptation:
- the arg lands in the headless && !has_extensions block, separate from the
  stealth base args — no interaction with anti-detection.
- dropped upstream docs/, agent-browser.schema.json and README hunks (removed
  or rewritten in this fork).

Verified: cargo check --tests passes.
This commit is contained in:
leeguooooo
2026-06-01 10:35:20 +09:00
parent 44b6218ef9
commit d027659571
10 changed files with 212 additions and 5 deletions
+2
View File
@@ -2751,6 +2751,7 @@ mod tests {
provider: None, provider: None,
ignore_https_errors: false, ignore_https_errors: false,
allow_file_access: false, allow_file_access: false,
hide_scrollbars: true,
device: None, device: None,
auto_connect: false, auto_connect: false,
force_launch: false, force_launch: false,
@@ -2766,6 +2767,7 @@ mod tests {
cli_proxy: false, cli_proxy: false,
cli_proxy_bypass: false, cli_proxy_bypass: false,
cli_allow_file_access: false, cli_allow_file_access: false,
cli_hide_scrollbars: false,
cli_annotate: false, cli_annotate: false,
cli_download_path: false, cli_download_path: false,
cli_headed: false, cli_headed: false,
+5
View File
@@ -412,6 +412,7 @@ pub struct DaemonOptions<'a> {
pub proxy_password: Option<&'a str>, pub proxy_password: Option<&'a str>,
pub ignore_https_errors: bool, pub ignore_https_errors: bool,
pub allow_file_access: bool, pub allow_file_access: bool,
pub hide_scrollbars: bool,
pub profile: Option<&'a str>, pub profile: Option<&'a str>,
pub state: Option<&'a str>, pub state: Option<&'a str>,
pub provider: Option<&'a str>, pub provider: Option<&'a str>,
@@ -476,6 +477,10 @@ fn apply_daemon_env(cmd: &mut Command, session: &str, opts: &DaemonOptions) {
if opts.allow_file_access { if opts.allow_file_access {
cmd.env("AGENT_BROWSER_ALLOW_FILE_ACCESS", "1"); cmd.env("AGENT_BROWSER_ALLOW_FILE_ACCESS", "1");
} }
cmd.env(
"AGENT_BROWSER_HIDE_SCROLLBARS",
if opts.hide_scrollbars { "1" } else { "0" },
);
if let Some(prof) = opts.profile { if let Some(prof) = opts.profile {
cmd.env("AGENT_BROWSER_PROFILE", prof); cmd.env("AGENT_BROWSER_PROFILE", prof);
} }
+1
View File
@@ -65,6 +65,7 @@ pub(super) fn check(checks: &mut Vec<Check>) {
proxy_password: None, proxy_password: None,
ignore_https_errors: false, ignore_https_errors: false,
allow_file_access: false, allow_file_access: false,
hide_scrollbars: true,
profile: None, profile: None,
state: None, state: None,
provider: None, provider: None,
+56 -1
View File
@@ -70,6 +70,7 @@ pub struct Config {
pub user_agent: Option<String>, pub user_agent: Option<String>,
pub provider: Option<String>, pub provider: Option<String>,
pub device: Option<String>, pub device: Option<String>,
pub hide_scrollbars: Option<bool>,
pub ignore_https_errors: Option<bool>, pub ignore_https_errors: Option<bool>,
pub allow_file_access: Option<bool>, pub allow_file_access: Option<bool>,
pub cdp: Option<String>, pub cdp: Option<String>,
@@ -131,6 +132,7 @@ impl Config {
user_agent: other.user_agent.or(self.user_agent), user_agent: other.user_agent.or(self.user_agent),
provider: other.provider.or(self.provider), provider: other.provider.or(self.provider),
device: other.device.or(self.device), device: other.device.or(self.device),
hide_scrollbars: other.hide_scrollbars.or(self.hide_scrollbars),
ignore_https_errors: other.ignore_https_errors.or(self.ignore_https_errors), ignore_https_errors: other.ignore_https_errors.or(self.ignore_https_errors),
allow_file_access: other.allow_file_access.or(self.allow_file_access), allow_file_access: other.allow_file_access.or(self.allow_file_access),
cdp: other.cdp.or(self.cdp), cdp: other.cdp.or(self.cdp),
@@ -187,6 +189,12 @@ fn env_var_is_truthy(name: &str) -> bool {
} }
} }
fn env_var_bool(name: &str) -> Option<bool> {
env::var(name)
.ok()
.map(|val| !matches!(val.to_lowercase().as_str(), "0" | "false" | "no" | ""))
}
/// Parse an optional boolean value after a flag. Returns (value, consumed_next_arg). /// Parse an optional boolean value after a flag. Returns (value, consumed_next_arg).
/// Recognizes "true" as true, "false" as false. Bare flag defaults to true. /// Recognizes "true" as true, "false" as false. Bare flag defaults to true.
fn parse_bool_arg(args: &[String], i: usize) -> (bool, bool) { fn parse_bool_arg(args: &[String], i: usize) -> (bool, bool) {
@@ -306,6 +314,7 @@ pub struct Flags {
pub provider: Option<String>, pub provider: Option<String>,
pub ignore_https_errors: bool, pub ignore_https_errors: bool,
pub allow_file_access: bool, pub allow_file_access: bool,
pub hide_scrollbars: bool,
pub device: Option<String>, pub device: Option<String>,
pub auto_connect: bool, pub auto_connect: bool,
pub force_launch: bool, pub force_launch: bool,
@@ -343,6 +352,7 @@ pub struct Flags {
pub cli_proxy: bool, pub cli_proxy: bool,
pub cli_proxy_bypass: bool, pub cli_proxy_bypass: bool,
pub cli_allow_file_access: bool, pub cli_allow_file_access: bool,
pub cli_hide_scrollbars: bool,
pub cli_annotate: bool, pub cli_annotate: bool,
pub cli_download_path: bool, pub cli_download_path: bool,
pub cli_headed: bool, pub cli_headed: bool,
@@ -443,6 +453,9 @@ pub fn parse_flags(args: &[String]) -> Flags {
|| config.ignore_https_errors.unwrap_or(false), || config.ignore_https_errors.unwrap_or(false),
allow_file_access: env_var_is_truthy("AGENT_BROWSER_ALLOW_FILE_ACCESS") allow_file_access: env_var_is_truthy("AGENT_BROWSER_ALLOW_FILE_ACCESS")
|| config.allow_file_access.unwrap_or(false), || config.allow_file_access.unwrap_or(false),
hide_scrollbars: env_var_bool("AGENT_BROWSER_HIDE_SCROLLBARS")
.or(config.hide_scrollbars)
.unwrap_or(true),
device: env::var("AGENT_BROWSER_IOS_DEVICE").ok().or(config.device), device: env::var("AGENT_BROWSER_IOS_DEVICE").ok().or(config.device),
auto_connect: !env_var_is_truthy("AGENT_BROWSER_NO_AUTO_CONNECT") auto_connect: !env_var_is_truthy("AGENT_BROWSER_NO_AUTO_CONNECT")
&& (env_var_is_truthy("AGENT_BROWSER_AUTO_CONNECT") && (env_var_is_truthy("AGENT_BROWSER_AUTO_CONNECT")
@@ -518,6 +531,7 @@ pub fn parse_flags(args: &[String]) -> Flags {
cli_proxy: false, cli_proxy: false,
cli_proxy_bypass: false, cli_proxy_bypass: false,
cli_allow_file_access: false, cli_allow_file_access: false,
cli_hide_scrollbars: false,
cli_annotate: false, cli_annotate: false,
cli_download_path: false, cli_download_path: false,
cli_headed: false, cli_headed: false,
@@ -677,6 +691,14 @@ pub fn parse_flags(args: &[String]) -> Flags {
i += 1; i += 1;
} }
} }
"--hide-scrollbars" => {
let (val, consumed) = parse_bool_arg(args, i);
flags.hide_scrollbars = val;
flags.cli_hide_scrollbars = true;
if consumed {
i += 1;
}
}
"--device" => { "--device" => {
if let Some(d) = args.get(i + 1) { if let Some(d) = args.get(i + 1) {
flags.device = Some(d.clone()); flags.device = Some(d.clone());
@@ -852,6 +874,7 @@ pub fn clean_args(args: &[String]) -> Vec<String> {
"--debug", "--debug",
"--ignore-https-errors", "--ignore-https-errors",
"--allow-file-access", "--allow-file-access",
"--hide-scrollbars",
"--auto-connect", "--auto-connect",
"--launch", "--launch",
"--new", "--new",
@@ -933,6 +956,7 @@ pub fn clean_args(args: &[String]) -> Vec<String> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::test_utils::EnvGuard;
fn args(s: &str) -> Vec<String> { fn args(s: &str) -> Vec<String> {
s.split_whitespace().map(String::from).collect() s.split_whitespace().map(String::from).collect()
@@ -1176,6 +1200,7 @@ mod tests {
"userAgent": "test-agent", "userAgent": "test-agent",
"provider": "ios", "provider": "ios",
"device": "iPhone 15", "device": "iPhone 15",
"hideScrollbars": false,
"ignoreHttpsErrors": true, "ignoreHttpsErrors": true,
"allowFileAccess": true, "allowFileAccess": true,
"cdp": "9222", "cdp": "9222",
@@ -1201,6 +1226,7 @@ mod tests {
assert_eq!(config.user_agent.as_deref(), Some("test-agent")); assert_eq!(config.user_agent.as_deref(), Some("test-agent"));
assert_eq!(config.provider.as_deref(), Some("ios")); assert_eq!(config.provider.as_deref(), Some("ios"));
assert_eq!(config.device.as_deref(), Some("iPhone 15")); assert_eq!(config.device.as_deref(), Some("iPhone 15"));
assert_eq!(config.hide_scrollbars, Some(false));
assert_eq!(config.ignore_https_errors, Some(true)); assert_eq!(config.ignore_https_errors, Some(true));
assert_eq!(config.allow_file_access, Some(true)); assert_eq!(config.allow_file_access, Some(true));
assert_eq!(config.cdp.as_deref(), Some("9222")); assert_eq!(config.cdp.as_deref(), Some("9222"));
@@ -1454,6 +1480,33 @@ mod tests {
assert!(flags.cli_allow_file_access); assert!(flags.cli_allow_file_access);
} }
#[test]
fn test_hide_scrollbars_default_true() {
let guard = EnvGuard::new(&["AGENT_BROWSER_HIDE_SCROLLBARS"]);
guard.remove("AGENT_BROWSER_HIDE_SCROLLBARS");
let flags = parse_flags(&args("open example.com"));
assert!(flags.hide_scrollbars);
assert!(!flags.cli_hide_scrollbars);
}
#[test]
fn test_hide_scrollbars_false() {
let guard = EnvGuard::new(&["AGENT_BROWSER_HIDE_SCROLLBARS"]);
guard.remove("AGENT_BROWSER_HIDE_SCROLLBARS");
let flags = parse_flags(&args("--hide-scrollbars false open"));
assert!(!flags.hide_scrollbars);
assert!(flags.cli_hide_scrollbars);
}
#[test]
fn test_hide_scrollbars_bare_defaults_true() {
let guard = EnvGuard::new(&["AGENT_BROWSER_HIDE_SCROLLBARS"]);
guard.remove("AGENT_BROWSER_HIDE_SCROLLBARS");
let flags = parse_flags(&args("--hide-scrollbars open"));
assert!(flags.hide_scrollbars);
assert!(flags.cli_hide_scrollbars);
}
#[test] #[test]
fn test_auto_connect_false() { fn test_auto_connect_false() {
let flags = parse_flags(&args("--auto-connect false open")); let flags = parse_flags(&args("--auto-connect false open"));
@@ -1462,7 +1515,9 @@ mod tests {
#[test] #[test]
fn test_clean_args_removes_bool_flag_with_value() { fn test_clean_args_removes_bool_flag_with_value() {
let cleaned = clean_args(&args("--headed false --debug true open example.com")); let cleaned = clean_args(&args(
"--headed false --debug true --hide-scrollbars false open example.com",
));
assert_eq!(cleaned, vec!["open", "example.com"]); assert_eq!(cleaned, vec!["open", "example.com"]);
} }
+48
View File
@@ -60,6 +60,23 @@ fn print_json_error_with_type(message: impl AsRef<str>, error_type: &str) {
})); }));
} }
fn should_send_hide_scrollbars_launch_option(
cli_hide_scrollbars: bool,
hide_scrollbars: bool,
) -> bool {
cli_hide_scrollbars || !hide_scrollbars
}
fn apply_hide_scrollbars_launch_option(
launch_cmd: &mut serde_json::Value,
cli_hide_scrollbars: bool,
hide_scrollbars: bool,
) {
if should_send_hide_scrollbars_launch_option(cli_hide_scrollbars, hide_scrollbars) {
launch_cmd["hideScrollbars"] = json!(hide_scrollbars);
}
}
struct ParsedProxy { struct ParsedProxy {
server: String, server: String,
username: Option<String>, username: Option<String>,
@@ -741,6 +758,7 @@ fn main() {
proxy_password: proxy_password.as_deref(), proxy_password: proxy_password.as_deref(),
ignore_https_errors: flags.ignore_https_errors, ignore_https_errors: flags.ignore_https_errors,
allow_file_access: flags.allow_file_access, allow_file_access: flags.allow_file_access,
hide_scrollbars: flags.hide_scrollbars,
profile: flags.profile.as_deref(), profile: flags.profile.as_deref(),
state: flags.state.as_deref(), state: flags.state.as_deref(),
provider: flags.provider.as_deref(), provider: flags.provider.as_deref(),
@@ -814,6 +832,7 @@ fn main() {
}, },
flags.ignore_https_errors.then_some("--ignore-https-errors"), flags.ignore_https_errors.then_some("--ignore-https-errors"),
flags.cli_allow_file_access.then_some("--allow-file-access"), flags.cli_allow_file_access.then_some("--allow-file-access"),
flags.cli_hide_scrollbars.then_some("--hide-scrollbars"),
flags.cli_download_path.then_some("--download-path"), flags.cli_download_path.then_some("--download-path"),
flags.cli_headed.then_some("--headed"), flags.cli_headed.then_some("--headed"),
] ]
@@ -1062,6 +1081,10 @@ fn main() {
|| flags.args.is_some() || flags.args.is_some()
|| flags.user_agent.is_some() || flags.user_agent.is_some()
|| flags.allow_file_access || flags.allow_file_access
|| should_send_hide_scrollbars_launch_option(
flags.cli_hide_scrollbars,
flags.hide_scrollbars,
)
|| flags.color_scheme.is_some() || flags.color_scheme.is_some()
|| flags.download_path.is_some() || flags.download_path.is_some()
|| flags.engine.is_some() || flags.engine.is_some()
@@ -1136,6 +1159,12 @@ fn main() {
launch_cmd["allowFileAccess"] = json!(true); launch_cmd["allowFileAccess"] = json!(true);
} }
apply_hide_scrollbars_launch_option(
&mut launch_cmd,
flags.cli_hide_scrollbars,
flags.hide_scrollbars,
);
if let Some(ref cs) = flags.color_scheme { if let Some(ref cs) = flags.color_scheme {
launch_cmd["colorScheme"] = json!(cs); launch_cmd["colorScheme"] = json!(cs);
} }
@@ -1488,4 +1517,23 @@ mod tests {
"Daemon process exited during startup:\nline \"quoted\"\u{001b}[2mansi\u{001b}[22m" "Daemon process exited during startup:\nline \"quoted\"\u{001b}[2mansi\u{001b}[22m"
); );
} }
#[test]
fn test_hide_scrollbars_launch_option_serialization() {
assert!(!should_send_hide_scrollbars_launch_option(false, true));
assert!(should_send_hide_scrollbars_launch_option(false, false));
assert!(should_send_hide_scrollbars_launch_option(true, true));
let mut default_cmd = json!({ "action": "launch" });
apply_hide_scrollbars_launch_option(&mut default_cmd, false, true);
assert!(default_cmd.get("hideScrollbars").is_none());
let mut config_false_cmd = json!({ "action": "launch" });
apply_hide_scrollbars_launch_option(&mut config_false_cmd, false, false);
assert_eq!(config_false_cmd["hideScrollbars"], false);
let mut cli_true_cmd = json!({ "action": "launch" });
apply_hide_scrollbars_launch_option(&mut cli_true_cmd, true, true);
assert_eq!(cli_true_cmd["hideScrollbars"], true);
}
} }
+51 -3
View File
@@ -197,6 +197,7 @@ fn launch_hash(opts: &LaunchOptions) -> u64 {
opts.proxy_password.hash(&mut h); opts.proxy_password.hash(&mut h);
opts.user_agent.hash(&mut h); opts.user_agent.hash(&mut h);
opts.allow_file_access.hash(&mut h); opts.allow_file_access.hash(&mut h);
opts.hide_scrollbars.hash(&mut h);
h.finish() h.finish()
} }
@@ -1876,11 +1877,24 @@ fn launch_options_from_env() -> LaunchOptions {
.unwrap_or(false), .unwrap_or(false),
color_scheme: env::var("AGENT_BROWSER_COLOR_SCHEME").ok(), color_scheme: env::var("AGENT_BROWSER_COLOR_SCHEME").ok(),
download_path: env::var("AGENT_BROWSER_DOWNLOAD_PATH").ok(), download_path: env::var("AGENT_BROWSER_DOWNLOAD_PATH").ok(),
hide_scrollbars: hide_scrollbars_from_env(),
viewport_size: None, viewport_size: None,
use_real_keychain: false, use_real_keychain: false,
} }
} }
fn hide_scrollbars_from_env() -> bool {
env::var("AGENT_BROWSER_HIDE_SCROLLBARS")
.map(|v| !matches!(v.to_ascii_lowercase().as_str(), "0" | "false" | "no" | ""))
.unwrap_or(true)
}
fn hide_scrollbars_from_launch_cmd(cmd: &Value) -> bool {
cmd.get("hideScrollbars")
.and_then(|v| v.as_bool())
.unwrap_or_else(hide_scrollbars_from_env)
}
async fn try_auto_restore_state(state: &mut DaemonState) { async fn try_auto_restore_state(state: &mut DaemonState) {
let session_name = match state.session_name.as_deref() { let session_name = match state.session_name.as_deref() {
Some(n) if !n.is_empty() => n.to_string(), Some(n) if !n.is_empty() => n.to_string(),
@@ -2044,6 +2058,7 @@ async fn handle_launch(cmd: &Value, state: &mut DaemonState) -> Result<Value, St
.get("downloadPath") .get("downloadPath")
.and_then(|v| v.as_str()) .and_then(|v| v.as_str())
.map(String::from), .map(String::from),
hide_scrollbars: hide_scrollbars_from_launch_cmd(cmd),
viewport_size: None, viewport_size: None,
use_real_keychain: false, use_real_keychain: false,
}; };
@@ -8646,17 +8661,21 @@ mod tests {
#[test] #[test]
fn test_launch_options_from_env_defaults() { fn test_launch_options_from_env_defaults() {
let _guard = EnvGuard::new(&["AGENT_BROWSER_HEADED"]); let guard = EnvGuard::new(&["AGENT_BROWSER_HEADED", "AGENT_BROWSER_HIDE_SCROLLBARS"]);
guard.remove("AGENT_BROWSER_HEADED");
guard.remove("AGENT_BROWSER_HIDE_SCROLLBARS");
let opts = launch_options_from_env(); let opts = launch_options_from_env();
assert!(opts.headless); assert!(opts.headless);
assert!(opts.args.is_empty()); assert!(opts.args.is_empty());
assert!(!opts.allow_file_access); assert!(!opts.allow_file_access);
assert!(opts.hide_scrollbars);
} }
#[test] #[test]
fn test_launch_options_from_env_headed_flag() { fn test_launch_options_from_env_headed_flag() {
let _guard = EnvGuard::new(&["AGENT_BROWSER_HEADED"]); let guard = EnvGuard::new(&["AGENT_BROWSER_HEADED", "AGENT_BROWSER_HIDE_SCROLLBARS"]);
_guard.set("AGENT_BROWSER_HEADED", "1"); guard.set("AGENT_BROWSER_HEADED", "1");
guard.remove("AGENT_BROWSER_HIDE_SCROLLBARS");
let opts = launch_options_from_env(); let opts = launch_options_from_env();
assert!( assert!(
!opts.headless, !opts.headless,
@@ -8664,6 +8683,35 @@ mod tests {
); );
} }
#[test]
fn test_launch_options_from_env_hide_scrollbars_false() {
let guard = EnvGuard::new(&["AGENT_BROWSER_HIDE_SCROLLBARS"]);
guard.set("AGENT_BROWSER_HIDE_SCROLLBARS", "false");
let opts = launch_options_from_env();
assert!(!opts.hide_scrollbars);
}
#[test]
fn test_launch_cmd_hide_scrollbars_missing_uses_env_default() {
let guard = EnvGuard::new(&["AGENT_BROWSER_HIDE_SCROLLBARS"]);
guard.set("AGENT_BROWSER_HIDE_SCROLLBARS", "false");
assert!(!hide_scrollbars_from_launch_cmd(&json!({
"action": "launch"
})));
}
#[test]
fn test_launch_cmd_hide_scrollbars_explicit_overrides_env_default() {
let guard = EnvGuard::new(&["AGENT_BROWSER_HIDE_SCROLLBARS"]);
guard.set("AGENT_BROWSER_HIDE_SCROLLBARS", "false");
assert!(hide_scrollbars_from_launch_cmd(&json!({
"action": "launch",
"hideScrollbars": true
})));
}
#[test] #[test]
fn test_har_entry_to_json_enriches_request_and_response() { fn test_har_entry_to_json_enriches_request_and_response() {
// wall_time: 2026-03-15T12:00:00Z = 1_773_576_000 // wall_time: 2026-03-15T12:00:00Z = 1_773_576_000
+34
View File
@@ -103,6 +103,9 @@ pub struct LaunchOptions {
pub ignore_https_errors: bool, pub ignore_https_errors: bool,
pub color_scheme: Option<String>, pub color_scheme: Option<String>,
pub download_path: Option<String>, pub download_path: Option<String>,
/// Hide native scrollbars in headless Chromium screenshots by launching
/// Chrome with `--hide-scrollbars`.
pub hide_scrollbars: bool,
/// Initial viewport dimensions used for `--window-size` so the content /// Initial viewport dimensions used for `--window-size` so the content
/// area matches the desired viewport from the start. /// area matches the desired viewport from the start.
pub viewport_size: Option<(u32, u32)>, pub viewport_size: Option<(u32, u32)>,
@@ -130,6 +133,7 @@ impl Default for LaunchOptions {
ignore_https_errors: false, ignore_https_errors: false,
color_scheme: None, color_scheme: None,
download_path: None, download_path: None,
hide_scrollbars: true,
viewport_size: None, viewport_size: None,
use_real_keychain: false, use_real_keychain: false,
} }
@@ -178,6 +182,13 @@ fn build_chrome_args(options: &LaunchOptions) -> Result<ChromeArgs, String> {
// injected in headless mode). Skip --headless when extensions are loaded. // injected in headless mode). Skip --headless when extensions are loaded.
if options.headless && !has_extensions { if options.headless && !has_extensions {
args.push("--headless=new".to_string()); args.push("--headless=new".to_string());
// Linux paints native scrollbars into viewport screenshots unless
// Chrome is launched with this flag. `--hide-scrollbars` is
// presence-based, so agent-browser exposes --hide-scrollbars false
// as the public opt-out instead of forwarding a fake inverse switch.
if options.hide_scrollbars {
args.push("--hide-scrollbars".to_string());
}
// Enable SwiftShader software rendering in headless mode. This // Enable SwiftShader software rendering in headless mode. This
// prevents silent crashes in environments where GPU drivers are // prevents silent crashes in environments where GPU drivers are
// missing or restricted (VMs, containers, some cloud machines) // missing or restricted (VMs, containers, some cloud machines)
@@ -1360,6 +1371,7 @@ mod tests {
}; };
let result = build_chrome_args(&opts).unwrap(); let result = build_chrome_args(&opts).unwrap();
assert!(result.args.iter().any(|a| a == "--headless=new")); assert!(result.args.iter().any(|a| a == "--headless=new"));
assert!(result.args.iter().any(|a| a == "--hide-scrollbars"));
assert!(result assert!(result
.args .args
.iter() .iter()
@@ -1380,6 +1392,7 @@ mod tests {
}; };
let result = build_chrome_args(&opts).unwrap(); let result = build_chrome_args(&opts).unwrap();
assert!(!result.args.iter().any(|a| a.contains("--headless"))); assert!(!result.args.iter().any(|a| a.contains("--headless")));
assert!(!result.args.iter().any(|a| a == "--hide-scrollbars"));
assert!(!result assert!(!result
.args .args
.iter() .iter()
@@ -1434,6 +1447,23 @@ mod tests {
} }
} }
#[test]
fn test_build_args_hide_scrollbars_false_suppresses_default_hide_scrollbars() {
let opts = LaunchOptions {
headless: true,
hide_scrollbars: false,
..Default::default()
};
let result = build_chrome_args(&opts).unwrap();
assert!(
!result.args.iter().any(|a| a == "--hide-scrollbars"),
"--hide-scrollbars false should suppress agent-browser's default hide switch"
);
if let Some(ref dir) = result.temp_user_data_dir {
let _ = std::fs::remove_dir_all(dir);
}
}
#[test] #[test]
fn test_build_args_start_maximized_suppresses_default_window_size() { fn test_build_args_start_maximized_suppresses_default_window_size() {
let opts = LaunchOptions { let opts = LaunchOptions {
@@ -1474,6 +1504,10 @@ mod tests {
!result.args.iter().any(|a| a.contains("--headless")), !result.args.iter().any(|a| a.contains("--headless")),
"headless flag should be omitted when extensions are present" "headless flag should be omitted when extensions are present"
); );
assert!(
!result.args.iter().any(|a| a == "--hide-scrollbars"),
"scrollbars should remain visible when extensions force headed mode"
);
assert!( assert!(
!result.args.iter().any(|a| a.contains("--window-size")), !result.args.iter().any(|a| a.contains("--window-size")),
"window-size should be omitted when extensions force headed mode" "window-size should be omitted when extensions force headed mode"
+7 -1
View File
@@ -1583,6 +1583,8 @@ Usage: agent-browser screenshot [selector] [path]
Captures a screenshot of the current page. If no path is provided, Captures a screenshot of the current page. If no path is provided,
saves to a temporary directory with a generated filename. saves to a temporary directory with a generated filename.
Headless Chromium screenshots hide native scrollbars for consistent image output.
Pass --hide-scrollbars false when launching to keep native scrollbars visible.
Options: Options:
--full, -f Capture full page (not just viewport) --full, -f Capture full page (not just viewport)
@@ -3098,6 +3100,8 @@ Options:
e.g., --proxy-bypass "localhost,*.internal.com" e.g., --proxy-bypass "localhost,*.internal.com"
--ignore-https-errors Ignore HTTPS certificate errors --ignore-https-errors Ignore HTTPS certificate errors
--allow-file-access Allow file:// URLs to access local files (Chromium only) --allow-file-access Allow file:// URLs to access local files (Chromium only)
--hide-scrollbars <bool> Hide native scrollbars in headless Chromium screenshots (default: true)
Use --hide-scrollbars false to keep scrollbars visible
-p, --provider <name> Browser provider: ios, browserbase, kernel, browseruse, browserless, agentcore -p, --provider <name> Browser provider: ios, browserbase, kernel, browseruse, browserless, agentcore
--device <name> iOS device name (e.g., "iPhone 15 Pro") --device <name> iOS device name (e.g., "iPhone 15 Pro")
--json JSON output --json JSON output
@@ -3137,11 +3141,12 @@ Configuration:
Boolean flags accept an optional true/false value to override config: Boolean flags accept an optional true/false value to override config:
--headed (same as --headed true) --headed (same as --headed true)
--headed false (disables "headed": true from config) --headed false (disables "headed": true from config)
--hide-scrollbars false (keeps native scrollbars visible in headless Chromium screenshots)
Extensions from user and project configs are merged (not replaced). Extensions from user and project configs are merged (not replaced).
Example agent-browser.json: Example agent-browser.json:
{{"headed": true, "proxy": "http://localhost:8080", "profile": "./browser-data"}} {{"headed": true, "hideScrollbars": false, "proxy": "http://localhost:8080"}}
Environment: Environment:
AGENT_BROWSER_CONFIG Path to config file (or use --config) AGENT_BROWSER_CONFIG Path to config file (or use --config)
@@ -3161,6 +3166,7 @@ Environment:
AGENT_BROWSER_PROVIDER Browser provider (ios, browserbase, kernel, browseruse, browserless, agentcore) AGENT_BROWSER_PROVIDER Browser provider (ios, browserbase, kernel, browseruse, browserless, agentcore)
AGENT_BROWSER_AUTO_CONNECT Auto-discover and connect to running Chrome AGENT_BROWSER_AUTO_CONNECT Auto-discover and connect to running Chrome
AGENT_BROWSER_ALLOW_FILE_ACCESS Allow file:// URLs to access local files AGENT_BROWSER_ALLOW_FILE_ACCESS Allow file:// URLs to access local files
AGENT_BROWSER_HIDE_SCROLLBARS Hide scrollbars in headless Chromium screenshots (default: true)
AGENT_BROWSER_COLOR_SCHEME Color scheme preference (dark, light, no-preference) AGENT_BROWSER_COLOR_SCHEME Color scheme preference (dark, light, no-preference)
AGENT_BROWSER_DOWNLOAD_PATH Default download directory for browser downloads AGENT_BROWSER_DOWNLOAD_PATH Default download directory for browser downloads
AGENT_BROWSER_DEFAULT_TIMEOUT Default action timeout in ms (default: 25000) AGENT_BROWSER_DEFAULT_TIMEOUT Default action timeout in ms (default: 25000)
+3
View File
@@ -243,6 +243,9 @@ agent-browser screenshot --full full.png # full scroll height
agent-browser screenshot --annotate map.png # numbered labels + legend keyed to snapshot refs agent-browser screenshot --annotate map.png # numbered labels + legend keyed to snapshot refs
``` ```
Headless Chromium screenshots hide native scrollbars for consistent image output.
Pass `--hide-scrollbars false` when launching to keep native scrollbars visible.
`--annotate` is designed for multimodal models: each label `[N]` maps to ref `@eN`. `--annotate` is designed for multimodal models: each label `[N]` maps to ref `@eN`.
### Handle multiple pages via tabs ### Handle multiple pages via tabs
+5
View File
@@ -103,6 +103,9 @@ agent-browser screenshot --full # Full page
agent-browser pdf output.pdf # Save as PDF agent-browser pdf output.pdf # Save as PDF
``` ```
Headless Chromium screenshots hide native scrollbars for consistent image output.
Pass `--hide-scrollbars false` when launching to keep native scrollbars visible.
## Video Recording ## Video Recording
```bash ```bash
@@ -309,6 +312,7 @@ agent-browser --headers <json> ... # HTTP headers scoped to URL's origin
agent-browser --executable-path <p> # Custom browser executable agent-browser --executable-path <p> # Custom browser executable
agent-browser --extension <path> ... # Load browser extension (repeatable) agent-browser --extension <path> ... # Load browser extension (repeatable)
agent-browser --ignore-https-errors # Ignore SSL certificate errors agent-browser --ignore-https-errors # Ignore SSL certificate errors
agent-browser --hide-scrollbars false # Keep native scrollbars visible in headless Chromium screenshots
agent-browser --help # Show help (-h) agent-browser --help # Show help (-h)
agent-browser --version # Show version (-V) agent-browser --version # Show version (-V)
agent-browser <command> --help # Show detailed help for a command agent-browser <command> --help # Show detailed help for a command
@@ -383,6 +387,7 @@ AGENT_BROWSER_EXECUTABLE_PATH="/path/chrome" # Custom browser path
AGENT_BROWSER_EXTENSIONS="/ext1,/ext2" # Comma-separated extension paths AGENT_BROWSER_EXTENSIONS="/ext1,/ext2" # Comma-separated extension paths
AGENT_BROWSER_INIT_SCRIPTS="/a.js,/b.js" # Comma-separated init script paths AGENT_BROWSER_INIT_SCRIPTS="/a.js,/b.js" # Comma-separated init script paths
AGENT_BROWSER_ENABLE="react-devtools" # Comma-separated built-in init script features AGENT_BROWSER_ENABLE="react-devtools" # Comma-separated built-in init script features
AGENT_BROWSER_HIDE_SCROLLBARS="false" # Keep native scrollbars visible in headless Chromium screenshots
AGENT_BROWSER_PROVIDER="browserbase" # Cloud browser provider AGENT_BROWSER_PROVIDER="browserbase" # Cloud browser provider
AGENT_BROWSER_STREAM_PORT="9223" # Override WebSocket streaming port (default: OS-assigned) AGENT_BROWSER_STREAM_PORT="9223" # Override WebSocket streaming port (default: OS-assigned)
AGENT_BROWSER_HOME="/path/to/agent-browser" # Custom install location AGENT_BROWSER_HOME="/path/to/agent-browser" # Custom install location