add scale factor to set viewport for retina screenshots (#691)

* device scale

* fix node.js daemon

* fix cargo fmt formatting for scale factor code

* fixes
This commit is contained in:
Chris Tate
2026-03-09 11:10:22 -05:00
committed by GitHub
parent a0bd0c2f0f
commit 644a4f5b63
9 changed files with 157 additions and 11 deletions
+54 -5
View File
@@ -1871,25 +1871,35 @@ fn parse_set(rest: &[&str], id: &str) -> Result<Value, ParseError> {
Some("viewport") => {
let w_str = rest.get(1).ok_or_else(|| ParseError::MissingArguments {
context: "set viewport".to_string(),
usage: "set viewport <width> <height>",
usage: "set viewport <width> <height> [scale]",
})?;
let h_str = rest.get(2).ok_or_else(|| ParseError::MissingArguments {
context: "set viewport".to_string(),
usage: "set viewport <width> <height>",
usage: "set viewport <width> <height> [scale]",
})?;
let w = w_str
.parse::<i32>()
.map_err(|_| ParseError::MissingArguments {
context: "set viewport".to_string(),
usage: "set viewport <width> <height>",
usage: "set viewport <width> <height> [scale]",
})?;
let h = h_str
.parse::<i32>()
.map_err(|_| ParseError::MissingArguments {
context: "set viewport".to_string(),
usage: "set viewport <width> <height>",
usage: "set viewport <width> <height> [scale]",
})?;
Ok(json!({ "id": id, "action": "viewport", "width": w, "height": h }))
let mut cmd = json!({ "id": id, "action": "viewport", "width": w, "height": h });
if let Some(scale_str) = rest.get(3) {
let scale = scale_str
.parse::<f64>()
.map_err(|_| ParseError::MissingArguments {
context: "set viewport".to_string(),
usage: "set viewport <width> <height> [scale]",
})?;
cmd["deviceScaleFactor"] = json!(scale);
}
Ok(cmd)
}
Some("device") => {
let dev = rest.get(1).ok_or_else(|| ParseError::MissingArguments {
@@ -3045,6 +3055,45 @@ mod tests {
assert_eq!(cmd["reducedMotion"], "reduce");
}
#[test]
fn test_set_viewport() {
let cmd = parse_command(&args("set viewport 1920 1080"), &default_flags()).unwrap();
assert_eq!(cmd["action"], "viewport");
assert_eq!(cmd["width"], 1920);
assert_eq!(cmd["height"], 1080);
assert!(cmd.get("deviceScaleFactor").is_none());
}
#[test]
fn test_set_viewport_with_scale() {
let cmd = parse_command(&args("set viewport 1920 1080 2"), &default_flags()).unwrap();
assert_eq!(cmd["action"], "viewport");
assert_eq!(cmd["width"], 1920);
assert_eq!(cmd["height"], 1080);
assert_eq!(cmd["deviceScaleFactor"], 2.0);
}
#[test]
fn test_set_viewport_with_fractional_scale() {
let cmd = parse_command(&args("set viewport 375 812 3"), &default_flags()).unwrap();
assert_eq!(cmd["action"], "viewport");
assert_eq!(cmd["width"], 375);
assert_eq!(cmd["height"], 812);
assert_eq!(cmd["deviceScaleFactor"], 3.0);
}
#[test]
fn test_set_viewport_missing_height() {
let result = parse_command(&args("set viewport 1920"), &default_flags());
assert!(result.is_err());
}
#[test]
fn test_set_viewport_invalid_scale() {
let result = parse_command(&args("set viewport 1920 1080 abc"), &default_flags());
assert!(result.is_err());
}
#[test]
fn test_find_first_no_value() {
let cmd = parse_command(&args("find first a click"), &default_flags()).unwrap();
+71
View File
@@ -777,6 +777,77 @@ async fn e2e_wait() {
assert_success(&resp);
}
// ---------------------------------------------------------------------------
// Viewport with deviceScaleFactor (retina)
// ---------------------------------------------------------------------------
#[tokio::test]
#[ignore]
async fn e2e_viewport_scale_factor() {
let mut state = DaemonState::new();
let resp = execute_command(
&json!({ "id": "1", "action": "launch", "headless": true }),
&mut state,
)
.await;
assert_success(&resp);
let resp = execute_command(
&json!({ "id": "2", "action": "navigate", "url": "about:blank" }),
&mut state,
)
.await;
assert_success(&resp);
// Default devicePixelRatio should be 1
let resp = execute_command(
&json!({ "id": "3", "action": "evaluate", "script": "window.devicePixelRatio" }),
&mut state,
)
.await;
assert_success(&resp);
let default_dpr = get_data(&resp)["result"].as_f64().unwrap();
assert_eq!(default_dpr, 1.0, "Default devicePixelRatio should be 1");
// Set viewport with 2x scale factor
let resp = execute_command(
&json!({ "id": "4", "action": "viewport", "width": 1920, "height": 1080, "deviceScaleFactor": 2.0 }),
&mut state,
)
.await;
assert_success(&resp);
assert_eq!(get_data(&resp)["width"], 1920);
assert_eq!(get_data(&resp)["height"], 1080);
assert_eq!(get_data(&resp)["deviceScaleFactor"], 2.0);
// devicePixelRatio should now be 2
let resp = execute_command(
&json!({ "id": "5", "action": "evaluate", "script": "window.devicePixelRatio" }),
&mut state,
)
.await;
assert_success(&resp);
let new_dpr = get_data(&resp)["result"].as_f64().unwrap();
assert_eq!(
new_dpr, 2.0,
"devicePixelRatio should be 2 after setting scale factor"
);
// CSS viewport width should still be 1920 (not 3840)
let resp = execute_command(
&json!({ "id": "6", "action": "evaluate", "script": "window.innerWidth" }),
&mut state,
)
.await;
assert_success(&resp);
let css_width = get_data(&resp)["result"].as_i64().unwrap();
assert_eq!(css_width, 1920, "CSS width should remain 1920 at 2x scale");
let resp = execute_command(&json!({ "id": "99", "action": "close" }), &mut state).await;
assert_success(&resp);
}
// ---------------------------------------------------------------------------
// Viewport and emulation
// ---------------------------------------------------------------------------
+2 -1
View File
@@ -1590,7 +1590,7 @@ Usage: agent-browser set <setting> [args]
Configures various browser settings and emulation options.
Settings:
viewport <w> <h> Set viewport size
viewport <w> <h> [scale] Set viewport size (scale = deviceScaleFactor, e.g. 2 for retina)
device <name> Emulate device (e.g., "iPhone 12")
geo <lat> <lng> Set geolocation
offline [on|off] Toggle offline mode
@@ -1605,6 +1605,7 @@ Global Options:
Examples:
agent-browser set viewport 1920 1080
agent-browser set viewport 1920 1080 2 # 2x retina
agent-browser set device "iPhone 12"
agent-browser set geo 37.7749 -122.4194
agent-browser set offline on