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:
@@ -2751,6 +2751,7 @@ mod tests {
|
||||
provider: None,
|
||||
ignore_https_errors: false,
|
||||
allow_file_access: false,
|
||||
hide_scrollbars: true,
|
||||
device: None,
|
||||
auto_connect: false,
|
||||
force_launch: false,
|
||||
@@ -2766,6 +2767,7 @@ mod tests {
|
||||
cli_proxy: false,
|
||||
cli_proxy_bypass: false,
|
||||
cli_allow_file_access: false,
|
||||
cli_hide_scrollbars: false,
|
||||
cli_annotate: false,
|
||||
cli_download_path: false,
|
||||
cli_headed: false,
|
||||
|
||||
@@ -412,6 +412,7 @@ pub struct DaemonOptions<'a> {
|
||||
pub proxy_password: Option<&'a str>,
|
||||
pub ignore_https_errors: bool,
|
||||
pub allow_file_access: bool,
|
||||
pub hide_scrollbars: bool,
|
||||
pub profile: Option<&'a str>,
|
||||
pub state: 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 {
|
||||
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 {
|
||||
cmd.env("AGENT_BROWSER_PROFILE", prof);
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@ pub(super) fn check(checks: &mut Vec<Check>) {
|
||||
proxy_password: None,
|
||||
ignore_https_errors: false,
|
||||
allow_file_access: false,
|
||||
hide_scrollbars: true,
|
||||
profile: None,
|
||||
state: None,
|
||||
provider: None,
|
||||
|
||||
+56
-1
@@ -70,6 +70,7 @@ pub struct Config {
|
||||
pub user_agent: Option<String>,
|
||||
pub provider: Option<String>,
|
||||
pub device: Option<String>,
|
||||
pub hide_scrollbars: Option<bool>,
|
||||
pub ignore_https_errors: Option<bool>,
|
||||
pub allow_file_access: Option<bool>,
|
||||
pub cdp: Option<String>,
|
||||
@@ -131,6 +132,7 @@ impl Config {
|
||||
user_agent: other.user_agent.or(self.user_agent),
|
||||
provider: other.provider.or(self.provider),
|
||||
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),
|
||||
allow_file_access: other.allow_file_access.or(self.allow_file_access),
|
||||
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).
|
||||
/// Recognizes "true" as true, "false" as false. Bare flag defaults to true.
|
||||
fn parse_bool_arg(args: &[String], i: usize) -> (bool, bool) {
|
||||
@@ -306,6 +314,7 @@ pub struct Flags {
|
||||
pub provider: Option<String>,
|
||||
pub ignore_https_errors: bool,
|
||||
pub allow_file_access: bool,
|
||||
pub hide_scrollbars: bool,
|
||||
pub device: Option<String>,
|
||||
pub auto_connect: bool,
|
||||
pub force_launch: bool,
|
||||
@@ -343,6 +352,7 @@ pub struct Flags {
|
||||
pub cli_proxy: bool,
|
||||
pub cli_proxy_bypass: bool,
|
||||
pub cli_allow_file_access: bool,
|
||||
pub cli_hide_scrollbars: bool,
|
||||
pub cli_annotate: bool,
|
||||
pub cli_download_path: bool,
|
||||
pub cli_headed: bool,
|
||||
@@ -443,6 +453,9 @@ pub fn parse_flags(args: &[String]) -> Flags {
|
||||
|| config.ignore_https_errors.unwrap_or(false),
|
||||
allow_file_access: env_var_is_truthy("AGENT_BROWSER_ALLOW_FILE_ACCESS")
|
||||
|| 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),
|
||||
auto_connect: !env_var_is_truthy("AGENT_BROWSER_NO_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_bypass: false,
|
||||
cli_allow_file_access: false,
|
||||
cli_hide_scrollbars: false,
|
||||
cli_annotate: false,
|
||||
cli_download_path: false,
|
||||
cli_headed: false,
|
||||
@@ -677,6 +691,14 @@ pub fn parse_flags(args: &[String]) -> Flags {
|
||||
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" => {
|
||||
if let Some(d) = args.get(i + 1) {
|
||||
flags.device = Some(d.clone());
|
||||
@@ -852,6 +874,7 @@ pub fn clean_args(args: &[String]) -> Vec<String> {
|
||||
"--debug",
|
||||
"--ignore-https-errors",
|
||||
"--allow-file-access",
|
||||
"--hide-scrollbars",
|
||||
"--auto-connect",
|
||||
"--launch",
|
||||
"--new",
|
||||
@@ -933,6 +956,7 @@ pub fn clean_args(args: &[String]) -> Vec<String> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::test_utils::EnvGuard;
|
||||
|
||||
fn args(s: &str) -> Vec<String> {
|
||||
s.split_whitespace().map(String::from).collect()
|
||||
@@ -1176,6 +1200,7 @@ mod tests {
|
||||
"userAgent": "test-agent",
|
||||
"provider": "ios",
|
||||
"device": "iPhone 15",
|
||||
"hideScrollbars": false,
|
||||
"ignoreHttpsErrors": true,
|
||||
"allowFileAccess": true,
|
||||
"cdp": "9222",
|
||||
@@ -1201,6 +1226,7 @@ mod tests {
|
||||
assert_eq!(config.user_agent.as_deref(), Some("test-agent"));
|
||||
assert_eq!(config.provider.as_deref(), Some("ios"));
|
||||
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.allow_file_access, Some(true));
|
||||
assert_eq!(config.cdp.as_deref(), Some("9222"));
|
||||
@@ -1454,6 +1480,33 @@ mod tests {
|
||||
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]
|
||||
fn test_auto_connect_false() {
|
||||
let flags = parse_flags(&args("--auto-connect false open"));
|
||||
@@ -1462,7 +1515,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
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"]);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
server: String,
|
||||
username: Option<String>,
|
||||
@@ -741,6 +758,7 @@ fn main() {
|
||||
proxy_password: proxy_password.as_deref(),
|
||||
ignore_https_errors: flags.ignore_https_errors,
|
||||
allow_file_access: flags.allow_file_access,
|
||||
hide_scrollbars: flags.hide_scrollbars,
|
||||
profile: flags.profile.as_deref(),
|
||||
state: flags.state.as_deref(),
|
||||
provider: flags.provider.as_deref(),
|
||||
@@ -814,6 +832,7 @@ fn main() {
|
||||
},
|
||||
flags.ignore_https_errors.then_some("--ignore-https-errors"),
|
||||
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_headed.then_some("--headed"),
|
||||
]
|
||||
@@ -1062,6 +1081,10 @@ fn main() {
|
||||
|| flags.args.is_some()
|
||||
|| flags.user_agent.is_some()
|
||||
|| flags.allow_file_access
|
||||
|| should_send_hide_scrollbars_launch_option(
|
||||
flags.cli_hide_scrollbars,
|
||||
flags.hide_scrollbars,
|
||||
)
|
||||
|| flags.color_scheme.is_some()
|
||||
|| flags.download_path.is_some()
|
||||
|| flags.engine.is_some()
|
||||
@@ -1136,6 +1159,12 @@ fn main() {
|
||||
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 {
|
||||
launch_cmd["colorScheme"] = json!(cs);
|
||||
}
|
||||
@@ -1488,4 +1517,23 @@ mod tests {
|
||||
"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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,6 +197,7 @@ fn launch_hash(opts: &LaunchOptions) -> u64 {
|
||||
opts.proxy_password.hash(&mut h);
|
||||
opts.user_agent.hash(&mut h);
|
||||
opts.allow_file_access.hash(&mut h);
|
||||
opts.hide_scrollbars.hash(&mut h);
|
||||
h.finish()
|
||||
}
|
||||
|
||||
@@ -1876,11 +1877,24 @@ fn launch_options_from_env() -> LaunchOptions {
|
||||
.unwrap_or(false),
|
||||
color_scheme: env::var("AGENT_BROWSER_COLOR_SCHEME").ok(),
|
||||
download_path: env::var("AGENT_BROWSER_DOWNLOAD_PATH").ok(),
|
||||
hide_scrollbars: hide_scrollbars_from_env(),
|
||||
viewport_size: None,
|
||||
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) {
|
||||
let session_name = match state.session_name.as_deref() {
|
||||
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")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(String::from),
|
||||
hide_scrollbars: hide_scrollbars_from_launch_cmd(cmd),
|
||||
viewport_size: None,
|
||||
use_real_keychain: false,
|
||||
};
|
||||
@@ -8646,17 +8661,21 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
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();
|
||||
assert!(opts.headless);
|
||||
assert!(opts.args.is_empty());
|
||||
assert!(!opts.allow_file_access);
|
||||
assert!(opts.hide_scrollbars);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_launch_options_from_env_headed_flag() {
|
||||
let _guard = EnvGuard::new(&["AGENT_BROWSER_HEADED"]);
|
||||
_guard.set("AGENT_BROWSER_HEADED", "1");
|
||||
let guard = EnvGuard::new(&["AGENT_BROWSER_HEADED", "AGENT_BROWSER_HIDE_SCROLLBARS"]);
|
||||
guard.set("AGENT_BROWSER_HEADED", "1");
|
||||
guard.remove("AGENT_BROWSER_HIDE_SCROLLBARS");
|
||||
let opts = launch_options_from_env();
|
||||
assert!(
|
||||
!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]
|
||||
fn test_har_entry_to_json_enriches_request_and_response() {
|
||||
// wall_time: 2026-03-15T12:00:00Z = 1_773_576_000
|
||||
|
||||
@@ -103,6 +103,9 @@ pub struct LaunchOptions {
|
||||
pub ignore_https_errors: bool,
|
||||
pub color_scheme: 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
|
||||
/// area matches the desired viewport from the start.
|
||||
pub viewport_size: Option<(u32, u32)>,
|
||||
@@ -130,6 +133,7 @@ impl Default for LaunchOptions {
|
||||
ignore_https_errors: false,
|
||||
color_scheme: None,
|
||||
download_path: None,
|
||||
hide_scrollbars: true,
|
||||
viewport_size: None,
|
||||
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.
|
||||
if options.headless && !has_extensions {
|
||||
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
|
||||
// prevents silent crashes in environments where GPU drivers are
|
||||
// missing or restricted (VMs, containers, some cloud machines)
|
||||
@@ -1360,6 +1371,7 @@ mod tests {
|
||||
};
|
||||
let result = build_chrome_args(&opts).unwrap();
|
||||
assert!(result.args.iter().any(|a| a == "--headless=new"));
|
||||
assert!(result.args.iter().any(|a| a == "--hide-scrollbars"));
|
||||
assert!(result
|
||||
.args
|
||||
.iter()
|
||||
@@ -1380,6 +1392,7 @@ mod tests {
|
||||
};
|
||||
let result = build_chrome_args(&opts).unwrap();
|
||||
assert!(!result.args.iter().any(|a| a.contains("--headless")));
|
||||
assert!(!result.args.iter().any(|a| a == "--hide-scrollbars"));
|
||||
assert!(!result
|
||||
.args
|
||||
.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]
|
||||
fn test_build_args_start_maximized_suppresses_default_window_size() {
|
||||
let opts = LaunchOptions {
|
||||
@@ -1474,6 +1504,10 @@ mod tests {
|
||||
!result.args.iter().any(|a| a.contains("--headless")),
|
||||
"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!(
|
||||
!result.args.iter().any(|a| a.contains("--window-size")),
|
||||
"window-size should be omitted when extensions force headed mode"
|
||||
|
||||
+7
-1
@@ -1583,6 +1583,8 @@ Usage: agent-browser screenshot [selector] [path]
|
||||
|
||||
Captures a screenshot of the current page. If no path is provided,
|
||||
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:
|
||||
--full, -f Capture full page (not just viewport)
|
||||
@@ -3098,6 +3100,8 @@ Options:
|
||||
e.g., --proxy-bypass "localhost,*.internal.com"
|
||||
--ignore-https-errors Ignore HTTPS certificate errors
|
||||
--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
|
||||
--device <name> iOS device name (e.g., "iPhone 15 Pro")
|
||||
--json JSON output
|
||||
@@ -3137,11 +3141,12 @@ Configuration:
|
||||
Boolean flags accept an optional true/false value to override config:
|
||||
--headed (same as --headed true)
|
||||
--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).
|
||||
|
||||
Example agent-browser.json:
|
||||
{{"headed": true, "proxy": "http://localhost:8080", "profile": "./browser-data"}}
|
||||
{{"headed": true, "hideScrollbars": false, "proxy": "http://localhost:8080"}}
|
||||
|
||||
Environment:
|
||||
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_AUTO_CONNECT Auto-discover and connect to running Chrome
|
||||
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_DOWNLOAD_PATH Default download directory for browser downloads
|
||||
AGENT_BROWSER_DEFAULT_TIMEOUT Default action timeout in ms (default: 25000)
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
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`.
|
||||
|
||||
### Handle multiple pages via tabs
|
||||
|
||||
@@ -103,6 +103,9 @@ agent-browser screenshot --full # Full page
|
||||
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
|
||||
|
||||
```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 --extension <path> ... # Load browser extension (repeatable)
|
||||
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 --version # Show version (-V)
|
||||
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_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_HIDE_SCROLLBARS="false" # Keep native scrollbars visible in headless Chromium screenshots
|
||||
AGENT_BROWSER_PROVIDER="browserbase" # Cloud browser provider
|
||||
AGENT_BROWSER_STREAM_PORT="9223" # Override WebSocket streaming port (default: OS-assigned)
|
||||
AGENT_BROWSER_HOME="/path/to/agent-browser" # Custom install location
|
||||
|
||||
Reference in New Issue
Block a user