Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions cli/src/native/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -301,6 +304,7 @@ impl DaemonState {
.ok()
.and_then(|s| s.parse::<u64>().ok())
.unwrap_or(30_000),
viewport: None,
}
}

Expand Down Expand Up @@ -3605,7 +3609,7 @@ async fn handle_tab_close(cmd: &Value, state: &mut DaemonState) -> Result<Value,
mgr.tab_close(index).await
}

async fn handle_viewport(cmd: &Value, state: &DaemonState) -> Result<Value, String> {
async fn handle_viewport(cmd: &Value, state: &mut DaemonState) -> Result<Value, String> {
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;
Expand All @@ -3617,6 +3621,8 @@ async fn handle_viewport(cmd: &Value, state: &DaemonState) -> Result<Value, Stri

mgr.set_viewport(width, height, scale, mobile).await?;

state.viewport = Some((width, height, scale, mobile));

// Update stream server viewport so status messages and screencast use the new dimensions
if let Some(ref server) = state.stream_server {
server.set_viewport(width as u32, height as u32).await;
Expand Down Expand Up @@ -3872,6 +3878,8 @@ async fn handle_recording_start(cmd: &Value, state: &mut DaemonState) -> 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();
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -4661,7 +4673,7 @@ async fn handle_wheel(cmd: &Value, state: &DaemonState) -> Result<Value, String>
Ok(json!({ "scrolled": true, "deltaX": delta_x, "deltaY": delta_y }))
}

async fn handle_device(cmd: &Value, state: &DaemonState) -> Result<Value, String> {
async fn handle_device(cmd: &Value, state: &mut DaemonState) -> Result<Value, String> {
let mgr = state.browser.as_ref().ok_or("Browser not launched")?;
let name = cmd
.get("name")
Expand Down Expand Up @@ -4690,6 +4702,8 @@ async fn handle_device(cmd: &Value, state: &DaemonState) -> Result<Value, String
mgr.set_viewport(width, height, scale, mobile).await?;
mgr.set_user_agent(ua).await?;

state.viewport = Some((width, height, scale, mobile));

// Update stream server viewport so status messages and screencast use the new dimensions
if let Some(ref server) = state.stream_server {
server.set_viewport(width as u32, height as u32).await;
Expand Down
81 changes: 81 additions & 0 deletions cli/src/native/e2e_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4127,3 +4127,84 @@ async fn e2e_upload_with_css_selector() {
let resp = execute_command(&json!({ "id": "99", "action": "close" }), &mut state).await;
assert_success(&resp);
}

// ---------------------------------------------------------------------------
// Recording: viewport inheritance
// ---------------------------------------------------------------------------

/// Verify that `recording_start` inherits the current viewport dimensions
/// into the newly created recording context. Without this, the recording
/// context falls back to the default 1280×720 regardless of what the user set.
#[tokio::test]
#[ignore]
async fn e2e_recording_inherits_viewport() {
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": "data:text/html,<h1>Viewport</h1>" }),
&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);
}
Loading