fix: inherit viewport dimensions in recording context (#1208)
* fix: inherit viewport dimensions in recording context When `record start` creates a new browser context, it now re-applies the current viewport settings (from `set viewport` or `set device`) so the recording resolution matches what the user configured instead of falling back to the default 1280×720. Closes #1207 * style: apply cargo fmt to e2e test * chore: remove obvious comments * chore: remove obvious comments from e2e test --------- Co-authored-by: hyunjinee <leehj0110@kakao.com>
This commit is contained in:
@@ -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::<u64>().ok())
|
||||
.unwrap_or(30_000),
|
||||
viewport: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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();
|
||||
@@ -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<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")
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user