#![allow(
clippy::too_many_lines,
clippy::doc_markdown,
clippy::uninlined_format_args,
clippy::unwrap_used,
clippy::expect_used,
clippy::needless_pass_by_value
)]
use serde_json::json;
use super::client::McpClient;
pub fn test_video_null_without_recording(c: &mut McpClient) {
let v = c.script_value(
r"
const video = page.video();
return { isNull: video === null };
",
);
assert_eq!(
v["isNull"].as_bool(),
Some(true),
"page.video() should be null without recordVideo: {v}"
);
}
pub fn test_video_recording_lifecycle(c: &mut McpClient) {
let record_dir = tempfile::tempdir().expect("allocate tempdir for recording");
let record_dir_path = record_dir.path().to_path_buf();
let record_dir_str = record_dir_path.to_string_lossy().into_owned();
let script = r"
const [recordDir] = args;
// 1280x720 covers Firefox's BiDi polyfill output without
// triggering ffmpeg's `Padded dimensions cannot be smaller than
// input` error (the polyfill captures at Firefox's rendered
// viewport size; the default 800x450 is smaller, which the
// `pad` filter refuses to shrink). CDP backends honor the size
// natively.
await context.setRecordVideo({ dir: recordDir, size: { width: 1280, height: 720 } });
// Open a FRESH page under the recordVideo-enabled context. The
// ambient `page` global stays untouched so sibling tests aren't
// disrupted.
const recPage = await context.newPage();
// Two navigations give the screencast encoder a visible state
// transition to capture; the explicit setTimeout pad (real timer
// via rquickjs-extra-timers) lets the CDP screencast pump flush a
// trailing frame deterministically rather than racing goto timing.
await recPage.goto('data:text/html,<h1>rec-1</h1>');
await recPage.goto('data:text/html,<h1>rec-2</h1>');
await new Promise((r) => setTimeout(r, 250));
const video = recPage.video();
const hasVideo = video !== null;
if (!hasVideo) {
return { hasVideo: false };
}
await recPage.close();
try {
const filePath = await video.path();
return { hasVideo: true, kind: 'ok', filePath };
} catch (e) {
return {
hasVideo: true,
kind: 'rejected',
reason: String(e && e.message ? e.message : e),
};
}
";
let v = c.script_value_with_args(script, json!([record_dir_str.clone()]));
match c.backend.as_str() {
"cdp-pipe" | "cdp-raw" => {
assert_eq!(
v["hasVideo"].as_bool(),
Some(true),
"CDP should expose page.video() when recordVideo is set: {v}"
);
assert_eq!(v["kind"].as_str(), Some("ok"), "CDP video.path() should resolve: {v}");
let file_path = v["filePath"].as_str().expect("filePath is a string");
let p = std::path::Path::new(file_path);
assert!(p.exists(), "CDP recorded file must exist: {file_path}");
let size = std::fs::metadata(p).map_or(0, |m| m.len());
assert!(
size > 0,
"CDP recorded file must be non-empty: size={size} path={file_path}"
);
assert!(
file_path.contains(&*record_dir_str),
"returned path must live inside recordDir {record_dir_str}: {file_path}"
);
},
"bidi" => {
assert_eq!(
v["hasVideo"].as_bool(),
Some(true),
"BiDi should expose page.video() when recordVideo is set: {v}"
);
assert_eq!(
v["kind"].as_str(),
Some("ok"),
"BiDi video.path() should resolve via the polyfill: {v}"
);
let file_path = v["filePath"].as_str().expect("filePath is a string");
assert!(
std::path::Path::new(file_path).exists(),
"BiDi recorded file must exist: {file_path}"
);
},
"webkit" => {
assert_eq!(
v["hasVideo"].as_bool(),
Some(true),
"webkit page.video() should expose a handle when recordVideo is set: {v}"
);
assert_eq!(
v["kind"].as_str(),
Some("ok"),
"webkit video.path() should resolve: {v}"
);
let file_path = v["filePath"].as_str().expect("filePath is a string");
assert!(
std::path::Path::new(file_path).exists(),
"webkit recorded file must exist: {file_path}"
);
assert!(
file_path.contains(&*record_dir_str),
"returned path must live inside recordDir {record_dir_str}: {file_path}"
);
},
other => panic!("unknown backend in test: {other}"),
}
drop(record_dir);
}