test: record panel tui e2e evidence
This commit is contained in:
parent
5e81bc38e6
commit
1f07e57a2c
27
.yoi/tickets/00001KV3BQ7Q3/artifacts/e2e-evidence.md
Normal file
27
.yoi/tickets/00001KV3BQ7Q3/artifacts/e2e-evidence.md
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
# E2E evidence for Ticket 00001KV3BQ7Q3
|
||||||
|
|
||||||
|
Validation date: 2026-06-14
|
||||||
|
Worktree: `/home/hare/Projects/yoi/.worktree/00001KV3BQ7Q3-panel-e2e-evidence`
|
||||||
|
Branch: `impl/00001KV3BQ7Q3-panel-e2e-evidence`
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
| Target merge behavior | Status | E2E scenario / assertion |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `802fa1f00f8725fe35336e083cd05652fee1409e` / `merge: rewind live refresh` | Pass for current fixture PTY E2E | `single_pod_rewind_picker_applies_without_escape_and_suppresses_duplicate_enter` spawns the real `yoi` binary under PTY, opens the rewind picker, applies the target without Esc/restart/restore, observes `rewind_applied` with restored composer text, and now also waits for the post-apply PTY stream to contain the unique live composer marker `rewind-live-refresh`. |
|
||||||
|
| `02311883f7cda116676d8e179a14ad0be9e7a244` / `merge: panel mouse selection` | Pass for current fixture PTY E2E | `panel_mouse_click_selects_row_without_dispatching_action` spawns the real `yoi panel` PTY path, injects SGR mouse click input, observes `selection_changed` for the clicked row, and asserts no `action_requested` event was emitted by click alone. |
|
||||||
|
| `db7bad7a64766c2039a4c10781801cb571027955` / `merge: panel quit latency` | Pass for bounded current fixture PTY E2E; original live-terminal latency remains outside this fixture | `panel_ctrl_c_exits_promptly_after_background_barrier` spawns the real `yoi panel` PTY path with a held `reload` background task, confirms that task is pending, sends Ctrl-C, and asserts clean process exit within `PanelHarness::default_exit_wait()` (1500 ms) plus `quit_requested` and `background_task_aborted { task: "reload" }` events. This guarantees that pending fixture background reload work is aborted and does not block quit past the threshold; it does not prove arbitrary live-terminal latency outside this fixture. |
|
||||||
|
|
||||||
|
## Commands and results
|
||||||
|
|
||||||
|
- `cargo fmt --check` — passed.
|
||||||
|
- `cargo test -p yoi-e2e --features e2e --no-run` — passed; built `yoi-e2e` unit/integration test executables.
|
||||||
|
- `cargo test -p yoi-e2e --features e2e` — passed: `yoi_e2e` unit test 1/1, `panel` integration tests 3/3, `rewind` integration test 1/1, doc-tests 0.
|
||||||
|
- `cargo check -p yoi-e2e -p yoi -p tui` — passed.
|
||||||
|
- `git diff --check` — passed.
|
||||||
|
|
||||||
|
## Residual gaps / non-claims
|
||||||
|
|
||||||
|
- These are automated fixture PTY confirmations only. They are not manual/live-terminal validation.
|
||||||
|
- The mouse path uses the harness's SGR mouse injection through a PTY. It confirms the real `yoi panel` process path receives and handles the encoded click as intended, but it is not a hardware/terminal-emulator compatibility matrix.
|
||||||
|
- The quit-latency assertion is bounded to the fixture's held `reload` task and 1500 ms threshold. It confirms pending fixture background work does not user-visibly block quit beyond that bound, but does not independently reproduce every historical live latency observation.
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
title: '対象 TUI/Panel merge commit の挙動を現行 E2E で確認する'
|
title: '対象 TUI/Panel merge commit の挙動を現行 E2E で確認する'
|
||||||
state: 'inprogress'
|
state: 'inprogress'
|
||||||
created_at: '2026-06-14T15:24:05Z'
|
created_at: '2026-06-14T15:24:05Z'
|
||||||
updated_at: '2026-06-14T16:39:19Z'
|
updated_at: '2026-06-14T16:45:57Z'
|
||||||
assignee: null
|
assignee: null
|
||||||
readiness: 'implementation_ready'
|
readiness: 'implementation_ready'
|
||||||
risk_flags: ['e2e', 'tui', 'panel', 'regression-evidence']
|
risk_flags: ['e2e', 'tui', 'panel', 'regression-evidence']
|
||||||
|
|
|
||||||
|
|
@ -113,4 +113,39 @@ Critical risks / reviewer focus:
|
||||||
|
|
||||||
Routing decision と accepted implementation/evidence plan を記録済み。先行 Panel/TUI implementation Tickets は merge/validation/done 済みで、prior waiting reason は解消。blocking relation / unresolved orchestration-plan blocker はないため、E2E validation side effects の前に `queued -> inprogress` acceptance を記録する。
|
Routing decision と accepted implementation/evidence plan を記録済み。先行 Panel/TUI implementation Tickets は merge/validation/done 済みで、prior waiting reason は解消。blocking relation / unresolved orchestration-plan blocker はないため、E2E validation side effects の前に `queued -> inprogress` acceptance を記録する。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: implementation_report author: hare at: 2026-06-14T16:45:57Z -->
|
||||||
|
|
||||||
|
## Implementation report
|
||||||
|
|
||||||
|
# E2E evidence for Ticket 00001KV3BQ7Q3
|
||||||
|
|
||||||
|
Validation date: 2026-06-14
|
||||||
|
Worktree: `/home/hare/Projects/yoi/.worktree/00001KV3BQ7Q3-panel-e2e-evidence`
|
||||||
|
Branch: `impl/00001KV3BQ7Q3-panel-e2e-evidence`
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
| Target merge behavior | Status | E2E scenario / assertion |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `802fa1f00f8725fe35336e083cd05652fee1409e` / `merge: rewind live refresh` | Pass for current fixture PTY E2E | `single_pod_rewind_picker_applies_without_escape_and_suppresses_duplicate_enter` spawns the real `yoi` binary under PTY, opens the rewind picker, applies the target without Esc/restart/restore, observes `rewind_applied` with restored composer text, and now also waits for the post-apply PTY stream to contain the unique live composer marker `rewind-live-refresh`. |
|
||||||
|
| `02311883f7cda116676d8e179a14ad0be9e7a244` / `merge: panel mouse selection` | Pass for current fixture PTY E2E | `panel_mouse_click_selects_row_without_dispatching_action` spawns the real `yoi panel` PTY path, injects SGR mouse click input, observes `selection_changed` for the clicked row, and asserts no `action_requested` event was emitted by click alone. |
|
||||||
|
| `db7bad7a64766c2039a4c10781801cb571027955` / `merge: panel quit latency` | Pass for bounded current fixture PTY E2E; original live-terminal latency remains outside this fixture | `panel_ctrl_c_exits_promptly_after_background_barrier` spawns the real `yoi panel` PTY path with a held `reload` background task, confirms that task is pending, sends Ctrl-C, and asserts clean process exit within `PanelHarness::default_exit_wait()` (1500 ms) plus `quit_requested` and `background_task_aborted { task: "reload" }` events. This guarantees that pending fixture background reload work is aborted and does not block quit past the threshold; it does not prove arbitrary live-terminal latency outside this fixture. |
|
||||||
|
|
||||||
|
## Commands and results
|
||||||
|
|
||||||
|
- `cargo fmt --check` — passed.
|
||||||
|
- `cargo test -p yoi-e2e --features e2e --no-run` — passed; built `yoi-e2e` unit/integration test executables.
|
||||||
|
- `cargo test -p yoi-e2e --features e2e` — passed: `yoi_e2e` unit test 1/1, `panel` integration tests 3/3, `rewind` integration test 1/1, doc-tests 0.
|
||||||
|
- `cargo check -p yoi-e2e -p yoi -p tui` — passed.
|
||||||
|
- `git diff --check` — passed.
|
||||||
|
|
||||||
|
## Residual gaps / non-claims
|
||||||
|
|
||||||
|
- These are automated fixture PTY confirmations only. They are not manual/live-terminal validation.
|
||||||
|
- The mouse path uses the harness's SGR mouse injection through a PTY. It confirms the real `yoi panel` process path receives and handles the encoded click as intended, but it is not a hardware/terminal-emulator compatibility matrix.
|
||||||
|
- The quit-latency assertion is bounded to the fixture's held `reload` task and 1500 ms threshold. It confirms pending fixture background work does not user-visibly block quit beyond that bound, but does not independently reproduce every historical live latency observation.
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -489,7 +489,7 @@ async fn run_e2e_rewind_fixture(
|
||||||
truncate_entries: 1,
|
truncate_entries: 1,
|
||||||
turn_index: 1,
|
turn_index: 1,
|
||||||
timestamp_ms: Some(1),
|
timestamp_ms: Some(1),
|
||||||
preview: "revise the plan".to_string(),
|
preview: "candidate rewind target".to_string(),
|
||||||
eligible: true,
|
eligible: true,
|
||||||
disabled_reason: None,
|
disabled_reason: None,
|
||||||
warning: None,
|
warning: None,
|
||||||
|
|
@ -500,7 +500,7 @@ async fn run_e2e_rewind_fixture(
|
||||||
"rewind_picker_opened",
|
"rewind_picker_opened",
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"targets": 1,
|
"targets": 1,
|
||||||
"selected_preview": "revise the plan",
|
"selected_preview": "candidate rewind target",
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -545,7 +545,7 @@ async fn run_e2e_rewind_fixture(
|
||||||
if submitted_at.elapsed() >= apply_delay {
|
if submitted_at.elapsed() >= apply_delay {
|
||||||
app.handle_pod_event(Event::RewindApplied {
|
app.handle_pod_event(Event::RewindApplied {
|
||||||
entries: Vec::new(),
|
entries: Vec::new(),
|
||||||
input: vec![Segment::text("revise the plan")],
|
input: vec![Segment::text("rewind-live-refresh")],
|
||||||
summary: RewindSummary {
|
summary: RewindSummary {
|
||||||
truncated_to_entries: 1,
|
truncated_to_entries: 1,
|
||||||
discarded_entries: 2,
|
discarded_entries: 2,
|
||||||
|
|
|
||||||
|
|
@ -638,6 +638,43 @@ impl PanelHarness {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn output_len(&self) -> usize {
|
||||||
|
self.output.lock().map(|output| output.len()).unwrap_or(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wait_for_output_contains_from(
|
||||||
|
&mut self,
|
||||||
|
start_offset: usize,
|
||||||
|
needle: &str,
|
||||||
|
timeout: Duration,
|
||||||
|
) -> Result<()> {
|
||||||
|
let start = Instant::now();
|
||||||
|
let needle = needle.as_bytes();
|
||||||
|
loop {
|
||||||
|
if self.output_after(start_offset, needle) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
if let Some(status) = self.child.try_wait()? {
|
||||||
|
self.flush_output_artifact()?;
|
||||||
|
return Err(HarnessError::Protocol(format!(
|
||||||
|
"process exited with {status} before PTY output contained {:?}",
|
||||||
|
String::from_utf8_lossy(needle)
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
if start.elapsed() >= timeout {
|
||||||
|
self.flush_output_artifact()?;
|
||||||
|
return Err(HarnessError::Timeout {
|
||||||
|
what: format!(
|
||||||
|
"PTY output containing {:?} after offset {start_offset}",
|
||||||
|
String::from_utf8_lossy(needle)
|
||||||
|
),
|
||||||
|
artifacts: self.artifacts.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
thread::sleep(Duration::from_millis(20));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn events(&mut self) -> Result<Vec<HarnessEvent>> {
|
pub fn events(&mut self) -> Result<Vec<HarnessEvent>> {
|
||||||
let text = fs::read_to_string(&self.artifacts.events_jsonl)?;
|
let text = fs::read_to_string(&self.artifacts.events_jsonl)?;
|
||||||
text.lines()
|
text.lines()
|
||||||
|
|
@ -684,6 +721,21 @@ impl PanelHarness {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn output_after(&self, start_offset: usize, needle: &[u8]) -> bool {
|
||||||
|
if needle.is_empty() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
self.output
|
||||||
|
.lock()
|
||||||
|
.map(|output| {
|
||||||
|
let start = start_offset.min(output.len());
|
||||||
|
output[start..]
|
||||||
|
.windows(needle.len())
|
||||||
|
.any(|window| window == needle)
|
||||||
|
})
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
fn mouse_capture_enabled(&self) -> bool {
|
fn mouse_capture_enabled(&self) -> bool {
|
||||||
self.output
|
self.output
|
||||||
.lock()
|
.lock()
|
||||||
|
|
|
||||||
|
|
@ -135,14 +135,20 @@ fn panel_ctrl_c_exits_promptly_after_background_barrier() -> yoi_e2e::Result<()>
|
||||||
"quit latency {elapsed:?} exceeded threshold; artifacts at {}",
|
"quit latency {elapsed:?} exceeded threshold; artifacts at {}",
|
||||||
panel.artifacts().dir.display()
|
panel.artifacts().dir.display()
|
||||||
);
|
);
|
||||||
|
let events = panel.events()?;
|
||||||
assert!(
|
assert!(
|
||||||
panel
|
events.iter().any(|event| event.event == "quit_requested"),
|
||||||
.events()?
|
|
||||||
.iter()
|
|
||||||
.any(|event| event.event == "quit_requested"),
|
|
||||||
"quit_requested observability event missing; artifacts at {}",
|
"quit_requested observability event missing; artifacts at {}",
|
||||||
panel.artifacts().dir.display()
|
panel.artifacts().dir.display()
|
||||||
);
|
);
|
||||||
|
assert!(
|
||||||
|
events.iter().any(|event| {
|
||||||
|
event.event == "background_task_aborted"
|
||||||
|
&& event.data.get("task").and_then(serde_json::Value::as_str) == Some("reload")
|
||||||
|
}),
|
||||||
|
"pending reload task should be aborted before quit completes; artifacts at {}",
|
||||||
|
panel.artifacts().dir.display()
|
||||||
|
);
|
||||||
drop(panel);
|
drop(panel);
|
||||||
assert_fixture_cleanup(fixture.cleanup()?);
|
assert_fixture_cleanup(fixture.cleanup()?);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ fn single_pod_rewind_picker_applies_without_escape_and_suppresses_duplicate_ente
|
||||||
tui.assert_no_full_drag_mouse_capture()?;
|
tui.assert_no_full_drag_mouse_capture()?;
|
||||||
tui.expect_event("rewind_fixture_ready", Duration::from_secs(5))?;
|
tui.expect_event("rewind_fixture_ready", Duration::from_secs(5))?;
|
||||||
|
|
||||||
|
let before_rewind_output = tui.output_len();
|
||||||
tui.press(KeyPress::CtrlR)?;
|
tui.press(KeyPress::CtrlR)?;
|
||||||
tui.expect_event("rewind_picker_opened", Duration::from_secs(5))?;
|
tui.expect_event("rewind_picker_opened", Duration::from_secs(5))?;
|
||||||
|
|
||||||
|
|
@ -34,10 +35,15 @@ fn single_pod_rewind_picker_applies_without_escape_and_suppresses_duplicate_ente
|
||||||
.data
|
.data
|
||||||
.get("composer_text")
|
.get("composer_text")
|
||||||
.and_then(serde_json::Value::as_str),
|
.and_then(serde_json::Value::as_str),
|
||||||
Some("revise the plan"),
|
Some("rewind-live-refresh"),
|
||||||
"rewind should update the visible composer state without Esc/restart; artifacts at {}",
|
"rewind should update the visible composer state without Esc/restart; artifacts at {}",
|
||||||
tui.artifacts().dir.display()
|
tui.artifacts().dir.display()
|
||||||
);
|
);
|
||||||
|
tui.wait_for_output_contains_from(
|
||||||
|
before_rewind_output,
|
||||||
|
"rewind-live-refresh",
|
||||||
|
Duration::from_secs(5),
|
||||||
|
)?;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
tui.count_events("rewind_submit_sent")?,
|
tui.count_events("rewind_submit_sent")?,
|
||||||
submit_count,
|
submit_count,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user