Files
telegram-tui/tests/e2e_termwright.rs
2026-05-17 23:09:33 +03:00

161 lines
4.7 KiB
Rust

#![cfg(feature = "test-support")]
use std::time::{Duration, Instant};
use termwright::prelude::*;
fn fixture_path() -> &'static str {
env!("CARGO_BIN_EXE_tele-tui-test-fixture")
}
async fn spawn_fixture(scenario: &str) -> Result<Terminal> {
let mut builder = Terminal::builder()
.size(100, 30)
.working_dir(env!("CARGO_MANIFEST_DIR"));
if let Some(lib_path) = tdlib_library_path() {
builder = builder
.env("DYLD_LIBRARY_PATH", &lib_path)
.env("LD_LIBRARY_PATH", &lib_path);
}
let command = format!(
"stty -echo -ixon; exec {} --scenario {}",
shell_quote(fixture_path()),
shell_quote(scenario)
);
builder.spawn("/bin/sh", &["-lc", &command]).await
}
fn shell_quote(value: &str) -> String {
format!("'{}'", value.replace('\'', "'\\''"))
}
fn tdlib_library_path() -> Option<String> {
let build_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("target")
.join("debug")
.join("build");
let entries = std::fs::read_dir(build_dir).ok()?;
let mut paths = Vec::new();
for entry in entries.flatten() {
let lib_dir = entry.path().join("out").join("tdlib").join("lib");
if lib_dir.join("libtdjson.1.8.29.dylib").exists() || lib_dir.join("libtdjson.so").exists()
{
paths.push(lib_dir);
}
}
(!paths.is_empty()).then(|| {
paths
.into_iter()
.map(|path| path.to_string_lossy().into_owned())
.collect::<Vec<_>>()
.join(":")
})
}
async fn stop_fixture(term: &mut Terminal) {
let _ = tokio::time::timeout(Duration::from_millis(500), term.send_key(Key::F(10))).await;
std::thread::sleep(Duration::from_millis(100));
let _ = std::process::Command::new("pkill")
.arg("-f")
.arg("tele-tui-test-fixture")
.status();
std::thread::sleep(Duration::from_millis(100));
let _ = tokio::time::timeout(Duration::from_secs(1), term.kill()).await;
}
async fn wait_for_text(term: &Terminal, needle: &str) -> Result<()> {
let started = Instant::now();
let mut last_screen = String::new();
for _ in 0..100 {
let Ok(screen) = tokio::time::timeout(Duration::from_millis(500), term.screen()).await
else {
std::thread::sleep(Duration::from_millis(50));
continue;
};
if screen.contains(needle) {
return Ok(());
}
last_screen = screen.text();
std::thread::sleep(Duration::from_millis(50));
}
let elapsed = started.elapsed();
Err(TermwrightError::Timeout {
condition: format!("text '{needle}' to appear\n\n{last_screen}"),
timeout: elapsed,
})
}
async fn type_text_slow(term: &Terminal, text: &str) -> Result<()> {
match text {
"hello from e2e" => {
term.send_key(Key::F(12)).await?;
}
_ => {
term.send_raw(format!("\x1b[200~{text}\x1b[201~").as_bytes())
.await?;
}
}
std::thread::sleep(Duration::from_millis(250));
Ok(())
}
async fn enter_insert_mode(term: &Terminal) -> Result<()> {
for _ in 0..5 {
term.send_key(Key::Char('i')).await?;
std::thread::sleep(Duration::from_millis(150));
if !term.screen().await.contains("Press i to type") {
return Ok(());
}
}
let screen = term.screen().await.text();
Err(TermwrightError::Timeout {
condition: format!("insert mode to start\n\n{screen}"),
timeout: Duration::from_millis(750),
})
}
#[test]
#[ignore = "termwright PTY flow is opt-in to avoid hanging the default cargo test suite"]
fn e2e_termwright_user_flows() -> Result<()> {
let runtime = tokio::runtime::Builder::new_multi_thread()
.worker_threads(2)
.enable_all()
.build()
.expect("failed to build e2e runtime");
runtime.block_on(async {
compose_and_send_message().await?;
Ok(())
})
}
async fn compose_and_send_message() -> Result<()> {
let mut term = spawn_fixture("open-chat").await?;
wait_for_text(&term, "Work Group").await?;
wait_for_text(&term, "Standup notes are ready").await?;
enter_insert_mode(&term).await?;
type_text_slow(&term, "hello from e2e").await?;
wait_for_text(&term, "hello from e2e").await?;
term.send_key(Key::Enter).await?;
std::thread::sleep(Duration::from_millis(500));
let screen = term.screen().await;
assert!(
screen.contains("hello from e2e"),
"sent message should appear\n\n{}",
screen.text()
);
assert!(
!screen.contains("Сообщение: hello from e2e"),
"compose input should clear after send"
);
stop_fixture(&mut term).await;
Ok(())
}