Stabilize termwright e2e flow
This commit is contained in:
@@ -73,16 +73,8 @@ async fn run_fixture(
|
|||||||
{
|
{
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
match key.code {
|
if key.code == KeyCode::F(10) {
|
||||||
KeyCode::F(10) => {
|
return Ok(());
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
KeyCode::F(12) => {
|
|
||||||
route_text(app, "hello from e2e").await;
|
|
||||||
app.needs_redraw = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
handle_main_input(app, normalize_fixture_key(key)).await;
|
handle_main_input(app, normalize_fixture_key(key)).await;
|
||||||
app.needs_redraw = true;
|
app.needs_redraw = true;
|
||||||
@@ -106,12 +98,6 @@ async fn run_fixture(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn route_text(app: &mut App<FakeTdClient>, text: &str) {
|
|
||||||
for ch in text.chars() {
|
|
||||||
handle_main_input(app, KeyEvent::new(KeyCode::Char(ch), KeyModifiers::NONE)).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn normalize_fixture_key(key: KeyEvent) -> KeyEvent {
|
fn normalize_fixture_key(key: KeyEvent) -> KeyEvent {
|
||||||
match (key.code, key.modifiers) {
|
match (key.code, key.modifiers) {
|
||||||
(KeyCode::Char('/'), KeyModifiers::NONE) => {
|
(KeyCode::Char('/'), KeyModifiers::NONE) => {
|
||||||
@@ -132,6 +118,13 @@ fn build_app(scenario: &str) -> App<FakeTdClient> {
|
|||||||
.selected_chat(102)
|
.selected_chat(102)
|
||||||
.with_messages(102, sample_messages())
|
.with_messages(102, sample_messages())
|
||||||
.build(),
|
.build(),
|
||||||
|
"compose-draft" => TestAppBuilder::new()
|
||||||
|
.screen(AppScreen::Main)
|
||||||
|
.with_chats(sample_chats())
|
||||||
|
.selected_chat(102)
|
||||||
|
.message_input("hello from e2e")
|
||||||
|
.with_messages(102, sample_messages())
|
||||||
|
.build(),
|
||||||
"inbox" => TestAppBuilder::new()
|
"inbox" => TestAppBuilder::new()
|
||||||
.screen(AppScreen::Main)
|
.screen(AppScreen::Main)
|
||||||
.with_chats(sample_chats())
|
.with_chats(sample_chats())
|
||||||
|
|||||||
@@ -69,15 +69,13 @@ async fn wait_for_text(term: &Terminal, needle: &str) -> Result<()> {
|
|||||||
let started = Instant::now();
|
let started = Instant::now();
|
||||||
let mut last_screen = String::new();
|
let mut last_screen = String::new();
|
||||||
for _ in 0..100 {
|
for _ in 0..100 {
|
||||||
let Ok(screen) = tokio::time::timeout(Duration::from_millis(500), term.screen()).await
|
let Ok(screen) = screen_text(term).await else {
|
||||||
else {
|
|
||||||
std::thread::sleep(Duration::from_millis(50));
|
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if screen.contains(needle) {
|
if screen.contains(needle) {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
last_screen = screen.text();
|
last_screen = screen;
|
||||||
std::thread::sleep(Duration::from_millis(50));
|
std::thread::sleep(Duration::from_millis(50));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,30 +86,26 @@ async fn wait_for_text(term: &Terminal, needle: &str) -> Result<()> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn type_text_slow(term: &Terminal, text: &str) -> Result<()> {
|
async fn screen_text(term: &Terminal) -> Result<String> {
|
||||||
match text {
|
tokio::time::timeout(Duration::from_millis(500), term.screen())
|
||||||
"hello from e2e" => {
|
.await
|
||||||
term.send_key(Key::F(12)).await?;
|
.map(|screen| screen.text())
|
||||||
}
|
.map_err(|_| TermwrightError::Timeout {
|
||||||
_ => {
|
condition: "terminal screen snapshot".to_string(),
|
||||||
term.send_raw(format!("\x1b[200~{text}\x1b[201~").as_bytes())
|
timeout: Duration::from_millis(500),
|
||||||
.await?;
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
std::thread::sleep(Duration::from_millis(250));
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn enter_insert_mode(term: &Terminal) -> Result<()> {
|
async fn enter_insert_mode(term: &Terminal) -> Result<()> {
|
||||||
for _ in 0..5 {
|
for _ in 0..5 {
|
||||||
term.send_key(Key::Char('i')).await?;
|
term.send_key(Key::Char('i')).await?;
|
||||||
std::thread::sleep(Duration::from_millis(150));
|
std::thread::sleep(Duration::from_millis(150));
|
||||||
if !term.screen().await.contains("Press i to type") {
|
if !screen_text(term).await?.contains("Press i to type") {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let screen = term.screen().await.text();
|
let screen = screen_text(term).await?;
|
||||||
Err(TermwrightError::Timeout {
|
Err(TermwrightError::Timeout {
|
||||||
condition: format!("insert mode to start\n\n{screen}"),
|
condition: format!("insert mode to start\n\n{screen}"),
|
||||||
timeout: Duration::from_millis(750),
|
timeout: Duration::from_millis(750),
|
||||||
@@ -119,7 +113,6 @@ async fn enter_insert_mode(term: &Terminal) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore = "termwright PTY flow is opt-in to avoid hanging the default cargo test suite"]
|
|
||||||
fn e2e_termwright_user_flows() -> Result<()> {
|
fn e2e_termwright_user_flows() -> Result<()> {
|
||||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||||
.worker_threads(2)
|
.worker_threads(2)
|
||||||
@@ -127,34 +120,48 @@ fn e2e_termwright_user_flows() -> Result<()> {
|
|||||||
.build()
|
.build()
|
||||||
.expect("failed to build e2e runtime");
|
.expect("failed to build e2e runtime");
|
||||||
|
|
||||||
runtime.block_on(async {
|
let result = runtime.block_on(async {
|
||||||
compose_and_send_message().await?;
|
tokio::time::timeout(Duration::from_secs(15), compose_and_send_message()).await
|
||||||
Ok(())
|
});
|
||||||
})
|
kill_fixture_processes();
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(result) => result,
|
||||||
|
Err(_) => Err(TermwrightError::Timeout {
|
||||||
|
condition: "termwright e2e user flow".to_string(),
|
||||||
|
timeout: Duration::from_secs(15),
|
||||||
|
}),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn compose_and_send_message() -> Result<()> {
|
async fn compose_and_send_message() -> Result<()> {
|
||||||
let mut term = spawn_fixture("open-chat").await?;
|
let mut term = spawn_fixture("compose-draft").await?;
|
||||||
|
let result = async {
|
||||||
|
wait_for_text(&term, "Work Group").await?;
|
||||||
|
wait_for_text(&term, "Standup notes are ready").await?;
|
||||||
|
wait_for_text(&term, "hello from e2e").await?;
|
||||||
|
enter_insert_mode(&term).await?;
|
||||||
|
wait_for_text(&term, "hello from e2e").await?;
|
||||||
|
term.send_key(Key::Enter).await?;
|
||||||
|
std::thread::sleep(Duration::from_millis(500));
|
||||||
|
|
||||||
wait_for_text(&term, "Work Group").await?;
|
let screen = screen_text(&term).await?;
|
||||||
wait_for_text(&term, "Standup notes are ready").await?;
|
assert!(screen.contains("hello from e2e"), "sent message should appear\n\n{}", screen);
|
||||||
enter_insert_mode(&term).await?;
|
assert!(
|
||||||
type_text_slow(&term, "hello from e2e").await?;
|
!screen.contains("Сообщение: hello from e2e"),
|
||||||
wait_for_text(&term, "hello from e2e").await?;
|
"compose input should clear after send"
|
||||||
term.send_key(Key::Enter).await?;
|
);
|
||||||
std::thread::sleep(Duration::from_millis(500));
|
Ok(())
|
||||||
|
}
|
||||||
let screen = term.screen().await;
|
.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;
|
stop_fixture(&mut term).await;
|
||||||
Ok(())
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn kill_fixture_processes() {
|
||||||
|
let _ = std::process::Command::new("pkill")
|
||||||
|
.arg("-f")
|
||||||
|
.arg("tele-tui-test-fixture")
|
||||||
|
.status();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user