213 lines
8.9 KiB
Rust
213 lines
8.9 KiB
Rust
use std::future::Future;
|
|
use std::pin::Pin;
|
|
|
|
use galaxyui_core::event::{Event, ModifiersState};
|
|
use galaxyui_core::integration::{TestStep, ARTIFACTS_DIR_ENV_VAR};
|
|
use pathfinder_geometry::vector::vec2f;
|
|
use warp::integration_testing::step::new_step_with_default_assertions;
|
|
use warp::integration_testing::terminal::util::ExpectedExitStatus;
|
|
use warp::integration_testing::terminal::{
|
|
assert_view_has_text_selection, clear_blocklist_to_remove_bootstrapped_blocks,
|
|
execute_command_for_single_terminal_in_tab, execute_echo_str,
|
|
wait_until_bootstrapped_single_pane_for_tab,
|
|
};
|
|
|
|
use crate::Builder;
|
|
|
|
/// Exercises the video recording, screenshot, and overlay annotation APIs.
|
|
///
|
|
/// This test is meant to be run manually with a real display to verify
|
|
/// that frame capture, video encoding, overlay compositing, and artifact
|
|
/// export all work end-to-end:
|
|
///
|
|
/// ```sh
|
|
/// WARPUI_USE_REAL_DISPLAY_IN_INTEGRATION_TESTS=1 \
|
|
/// cargo run -p integration --bin integration -- test_video_recording
|
|
/// ```
|
|
///
|
|
/// The recording exercises every overlay type:
|
|
/// - Mouse click indicators (filled dot + expanding ring)
|
|
/// - Drag trails (selecting terminal output text)
|
|
/// - Key sequence pills (typing commands, Ctrl-C, Cmd-A / Cmd-C)
|
|
pub fn test_video_recording() -> Builder {
|
|
Builder::new()
|
|
.with_real_display()
|
|
.with_on_finish(
|
|
move |_app, _window_id, _persisted_data| -> Pin<Box<dyn Future<Output = ()> + Send>> {
|
|
Box::pin(async move {
|
|
let artifacts_root = std::env::var(ARTIFACTS_DIR_ENV_VAR)
|
|
.map(std::path::PathBuf::from)
|
|
.unwrap_or_else(|_| {
|
|
std::env::temp_dir().join("warp_integration_test_artifacts")
|
|
});
|
|
let test_dir = artifacts_root.join("test_video_recording");
|
|
|
|
let latest_run = std::fs::read_dir(&test_dir).ok().and_then(|entries| {
|
|
entries
|
|
.filter_map(|entry| entry.ok())
|
|
.filter(|entry| entry.path().is_dir())
|
|
.max_by_key(|entry| entry.file_name())
|
|
.map(|entry| entry.path())
|
|
});
|
|
|
|
let Some(run_dir) = latest_run else {
|
|
panic!(
|
|
"No timestamped run directory found under {}",
|
|
test_dir.display()
|
|
);
|
|
};
|
|
|
|
let bootstrap_png = run_dir.join("after_bootstrap.png");
|
|
let commands_png = run_dir.join("after_commands.png");
|
|
let video_mp4 = run_dir.join("recording.mp4");
|
|
let log_file = run_dir.join("recording.log");
|
|
|
|
assert!(
|
|
bootstrap_png.exists(),
|
|
"Expected after_bootstrap.png in {}",
|
|
run_dir.display()
|
|
);
|
|
assert!(
|
|
commands_png.exists(),
|
|
"Expected after_commands.png in {}",
|
|
run_dir.display()
|
|
);
|
|
assert!(
|
|
video_mp4.exists(),
|
|
"Expected recording.mp4 in {}",
|
|
run_dir.display()
|
|
);
|
|
assert!(
|
|
log_file.exists(),
|
|
"Expected recording.log in {}",
|
|
run_dir.display()
|
|
);
|
|
|
|
let video_size = std::fs::metadata(&video_mp4)
|
|
.map(|metadata| metadata.len())
|
|
.unwrap_or(0);
|
|
assert!(
|
|
video_size > 1000,
|
|
"recording.mp4 is suspiciously small ({video_size} bytes)"
|
|
);
|
|
|
|
log::info!(
|
|
"All artifacts verified in {}. recording.mp4 = {} bytes",
|
|
run_dir.display(),
|
|
video_size
|
|
);
|
|
})
|
|
},
|
|
)
|
|
.with_step(wait_until_bootstrapped_single_pane_for_tab(0))
|
|
.with_step(clear_blocklist_to_remove_bootstrapped_blocks())
|
|
.with_step(
|
|
TestStep::new("Take screenshot after bootstrap")
|
|
.with_take_screenshot("after_bootstrap.png"),
|
|
)
|
|
.with_step(TestStep::new("Start recording").with_start_recording())
|
|
.with_step(execute_echo_str(0, "hello from the video test"))
|
|
.with_step(execute_echo_str(0, "second line of output"))
|
|
.with_step(execute_command_for_single_terminal_in_tab(
|
|
0,
|
|
"ls".to_string(),
|
|
ExpectedExitStatus::Success,
|
|
(),
|
|
))
|
|
.with_step(
|
|
TestStep::new("Click in terminal")
|
|
.with_event(Event::LeftMouseDown {
|
|
position: vec2f(300.0, 250.0),
|
|
modifiers: ModifiersState::default(),
|
|
click_count: 1,
|
|
is_first_mouse: false,
|
|
})
|
|
.with_event(Event::LeftMouseUp {
|
|
position: vec2f(300.0, 250.0),
|
|
modifiers: ModifiersState::default(),
|
|
}),
|
|
)
|
|
.with_step(
|
|
new_step_with_default_assertions("Close left panel")
|
|
.with_click_on_saved_position("workspace:toggle_left_panel"),
|
|
)
|
|
.with_step(
|
|
new_step_with_default_assertions("Start drag-select")
|
|
.with_event_fn(|app, window_id| {
|
|
let presenter = app.presenter(window_id).expect("presenter");
|
|
let bounds = presenter
|
|
.borrow()
|
|
.position_cache()
|
|
.get_position("block_index:0")
|
|
.expect("block_index:0 position");
|
|
Event::LeftMouseDown {
|
|
position: bounds.origin(),
|
|
modifiers: ModifiersState::default(),
|
|
click_count: 1,
|
|
is_first_mouse: false,
|
|
}
|
|
})
|
|
.with_event_fn(|app, window_id| {
|
|
let presenter = app.presenter(window_id).expect("presenter");
|
|
let b1 = presenter
|
|
.borrow()
|
|
.position_cache()
|
|
.get_position("block_index:1")
|
|
.expect("block_index:1 position");
|
|
Event::LeftMouseDragged {
|
|
position: b1.center(),
|
|
modifiers: ModifiersState::default(),
|
|
}
|
|
})
|
|
.with_event_fn(|app, window_id| {
|
|
let presenter = app.presenter(window_id).expect("presenter");
|
|
let b1 = presenter
|
|
.borrow()
|
|
.position_cache()
|
|
.get_position("block_index:1")
|
|
.expect("block_index:1 position");
|
|
Event::LeftMouseDragged {
|
|
position: b1.lower_right(),
|
|
modifiers: ModifiersState::default(),
|
|
}
|
|
})
|
|
.add_assertion(assert_view_has_text_selection(true)),
|
|
)
|
|
.with_step(
|
|
new_step_with_default_assertions("End drag-select")
|
|
.with_event_fn(|app, window_id| {
|
|
let presenter = app.presenter(window_id).expect("presenter");
|
|
let b1 = presenter
|
|
.borrow()
|
|
.position_cache()
|
|
.get_position("block_index:1")
|
|
.expect("block_index:1 position");
|
|
Event::LeftMouseUp {
|
|
position: b1.lower_right(),
|
|
modifiers: ModifiersState::default(),
|
|
}
|
|
})
|
|
.add_assertion(assert_view_has_text_selection(false)),
|
|
)
|
|
.with_step(TestStep::new("Copy selection").with_keystrokes(&["cmd-c"]))
|
|
.with_step(
|
|
TestStep::new("Type text for ctrl editing").with_input_string("hello world", None),
|
|
)
|
|
.with_step(
|
|
TestStep::new("Ctrl-A, Ctrl-E, Ctrl-U")
|
|
.with_keystrokes(&["ctrl-a", "ctrl-e", "ctrl-u"]),
|
|
)
|
|
.with_step(execute_command_for_single_terminal_in_tab(
|
|
0,
|
|
"echo 'video recording test complete'".to_string(),
|
|
ExpectedExitStatus::Success,
|
|
(),
|
|
))
|
|
.with_step(TestStep::new("Select all").with_keystrokes(&["cmd-a"]))
|
|
.with_step(TestStep::new("Stop recording").with_stop_recording())
|
|
.with_step(
|
|
TestStep::new("Take screenshot after commands")
|
|
.with_take_screenshot("after_commands.png"),
|
|
)
|
|
}
|