diff --git a/cli/src/native/actions.rs b/cli/src/native/actions.rs index d62341a..2858bf0 100644 --- a/cli/src/native/actions.rs +++ b/cli/src/native/actions.rs @@ -248,6 +248,9 @@ pub struct DaemonState { pub engine: String, /// Default timeout for wait operations, from AGENT_BROWSER_DEFAULT_TIMEOUT env var. pub default_timeout_ms: u64, + /// Last viewport settings (width, height, deviceScaleFactor, mobile), + /// re-applied to new contexts (e.g., recording). + pub viewport: Option<(i32, i32, f64, bool)>, } impl DaemonState { @@ -301,6 +304,7 @@ impl DaemonState { .ok() .and_then(|s| s.parse::().ok()) .unwrap_or(30_000), + viewport: None, } } @@ -3605,7 +3609,7 @@ async fn handle_tab_close(cmd: &Value, state: &mut DaemonState) -> Result Result { +async fn handle_viewport(cmd: &Value, state: &mut DaemonState) -> Result { let mgr = state.browser.as_ref().ok_or("Browser not launched")?; let width = cmd.get("width").and_then(|v| v.as_i64()).unwrap_or(1280) as i32; let height = cmd.get("height").and_then(|v| v.as_i64()).unwrap_or(720) as i32; @@ -3617,6 +3621,8 @@ async fn handle_viewport(cmd: &Value, state: &DaemonState) -> Result Result< .and_then(|v| v.as_str()) .filter(|s| !s.is_empty()); + let viewport = state.viewport; + let (client, new_session_id) = { let mgr = state.browser.as_mut().ok_or("Browser not launched")?; let old_session_id = mgr.active_session_id()?.to_string(); @@ -3985,6 +3993,10 @@ async fn handle_recording_start(cmd: &Value, state: &mut DaemonState) -> Result< target_type: "page".to_string(), }); + if let Some((w, h, scale, mobile)) = viewport { + let _ = mgr.set_viewport(w, h, scale, mobile).await; + } + // Navigate to URL if nav_url != "about:blank" { let _ = mgr @@ -4661,7 +4673,7 @@ async fn handle_wheel(cmd: &Value, state: &DaemonState) -> Result Ok(json!({ "scrolled": true, "deltaX": delta_x, "deltaY": delta_y })) } -async fn handle_device(cmd: &Value, state: &DaemonState) -> Result { +async fn handle_device(cmd: &Value, state: &mut DaemonState) -> Result { let mgr = state.browser.as_ref().ok_or("Browser not launched")?; let name = cmd .get("name") @@ -4690,6 +4702,8 @@ async fn handle_device(cmd: &Value, state: &DaemonState) -> ResultViewport" }), + &mut state, + ) + .await; + assert_success(&resp); + + let resp = execute_command( + &json!({ "id": "3", "action": "viewport", "width": 800, "height": 600 }), + &mut state, + ) + .await; + assert_success(&resp); + + let tmp_dir = std::env::temp_dir(); + let rec_path = tmp_dir.join(format!("ab-e2e-rec-viewport-{}.webm", std::process::id())); + let resp = execute_command( + &json!({ "id": "4", "action": "recording_start", "path": rec_path.to_string_lossy() }), + &mut state, + ) + .await; + assert_success(&resp); + + tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; + + let resp = execute_command( + &json!({ "id": "5", "action": "evaluate", "script": "window.innerWidth" }), + &mut state, + ) + .await; + assert_success(&resp); + let rec_width = get_data(&resp)["result"].as_i64().unwrap(); + + let resp = execute_command( + &json!({ "id": "6", "action": "evaluate", "script": "window.innerHeight" }), + &mut state, + ) + .await; + assert_success(&resp); + let rec_height = get_data(&resp)["result"].as_i64().unwrap(); + + assert_eq!( + rec_width, 800, + "Recording context width should be 800 (inherited from viewport), got {rec_width}" + ); + assert_eq!( + rec_height, 600, + "Recording context height should be 600 (inherited from viewport), got {rec_height}" + ); + + let resp = execute_command( + &json!({ "id": "7", "action": "recording_stop" }), + &mut state, + ) + .await; + assert_success(&resp); + + let _ = std::fs::remove_file(&rec_path); + let resp = execute_command(&json!({ "id": "99", "action": "close" }), &mut state).await; + assert_success(&resp); +}