Add runtime stream enable/disable/status commands (#951)

* Add runtime stream management commands

* Run rustfmt and satisfy clippy

* Fix stream disable cleanup semantics

* Format stream disable regression tests
This commit is contained in:
Thomas Kosiewski
2026-03-25 11:36:16 -07:00
committed by GitHub
parent eb64ca497a
commit 67b5ee1600
13 changed files with 850 additions and 38 deletions
+124
View File
@@ -8,9 +8,12 @@
//! cargo test e2e -- --ignored --test-threads=1
use base64::{engine::general_purpose::STANDARD, Engine};
use futures_util::StreamExt;
use serde_json::{json, Value};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use crate::test_utils::EnvGuard;
use super::actions::{execute_command, DaemonState};
fn assert_success(resp: &Value) {
@@ -190,6 +193,127 @@ async fn e2e_lightpanda_auto_launch_can_open_page() {
assert_eq!(get_data(&resp)["closed"], true);
}
// ---------------------------------------------------------------------------
// Runtime stream lifecycle
// ---------------------------------------------------------------------------
#[tokio::test]
#[ignore]
async fn e2e_runtime_stream_enable_before_launch_attaches_and_disables() {
let guard = EnvGuard::new(&["AGENT_BROWSER_SOCKET_DIR", "AGENT_BROWSER_SESSION"]);
let socket_dir = std::env::temp_dir().join(format!(
"agent-browser-e2e-stream-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("system clock should be after unix epoch")
.as_nanos()
));
std::fs::create_dir_all(&socket_dir).expect("socket dir should be created");
guard.set(
"AGENT_BROWSER_SOCKET_DIR",
socket_dir.to_str().expect("socket dir should be utf-8"),
);
guard.set("AGENT_BROWSER_SESSION", "e2e-runtime-stream");
let mut state = DaemonState::new();
let resp = execute_command(&json!({ "id": "1", "action": "stream_status" }), &mut state).await;
assert_success(&resp);
assert_eq!(get_data(&resp)["enabled"], false);
let resp = execute_command(
&json!({ "id": "2", "action": "stream_enable", "port": 0 }),
&mut state,
)
.await;
assert_success(&resp);
let port = get_data(&resp)["port"]
.as_u64()
.expect("stream enable should report the bound port");
assert_eq!(get_data(&resp)["connected"], false);
let stream_path = socket_dir.join("e2e-runtime-stream.stream");
assert!(
stream_path.exists(),
"runtime enable should create .stream metadata"
);
let (mut ws, _) = tokio_tungstenite::connect_async(format!("ws://127.0.0.1:{port}"))
.await
.expect("websocket client should connect to runtime stream");
let initial = tokio::time::timeout(tokio::time::Duration::from_secs(5), ws.next())
.await
.expect("websocket should emit initial status")
.expect("websocket should stay open")
.expect("websocket message should be valid");
let initial_text = initial.into_text().expect("initial message should be text");
let initial_status: Value =
serde_json::from_str(&initial_text).expect("status JSON should parse");
assert_eq!(initial_status["type"], "status");
assert_eq!(initial_status["connected"], false);
let resp = execute_command(
&json!({ "id": "3", "action": "navigate", "url": "data:text/html,<h1>Runtime Stream</h1>" }),
&mut state,
)
.await;
assert_success(&resp);
let mut observed_connected = false;
let deadline = tokio::time::Instant::now() + tokio::time::Duration::from_secs(10);
while tokio::time::Instant::now() < deadline {
let Some(message) = tokio::time::timeout(tokio::time::Duration::from_secs(2), ws.next())
.await
.expect("websocket should emit status after browser launch")
else {
continue;
};
let message = message.expect("websocket message should be valid");
if !message.is_text() {
continue;
}
let parsed: Value =
serde_json::from_str(message.to_text().expect("text message should be readable"))
.expect("runtime stream payload should be valid JSON");
if parsed.get("type") == Some(&json!("status"))
&& parsed.get("connected") == Some(&json!(true))
{
observed_connected = true;
break;
}
}
assert!(
observed_connected,
"runtime stream should report connected=true after browser launch"
);
let resp = execute_command(
&json!({ "id": "4", "action": "stream_disable" }),
&mut state,
)
.await;
assert_success(&resp);
assert_eq!(get_data(&resp)["disabled"], true);
assert!(
!stream_path.exists(),
"stream disable should remove .stream metadata"
);
let close_message = tokio::time::timeout(tokio::time::Duration::from_secs(5), ws.next())
.await
.expect("websocket should close after disable");
assert!(
close_message.is_none() || close_message.expect("ws result should exist").is_ok(),
"websocket should shut down cleanly when the runtime stream is disabled"
);
let resp = execute_command(&json!({ "id": "99", "action": "close" }), &mut state).await;
assert_success(&resp);
let _ = std::fs::remove_dir_all(&socket_dir);
}
// ---------------------------------------------------------------------------
// Snapshot with refs and ref-based click
// ---------------------------------------------------------------------------