diff --git a/skills/agentic-end-to-end-testing/driving-cli-tui.md b/skills/agentic-end-to-end-testing/driving-cli-tui.md index d4e72a06..8a5ea48a 100644 --- a/skills/agentic-end-to-end-testing/driving-cli-tui.md +++ b/skills/agentic-end-to-end-testing/driving-cli-tui.md @@ -1,16 +1,29 @@ # Driving a CLI / TUI (tmux) -Each scenario gets its own named tmux session (cleanup needs a deterministic -name). Fix the size for deterministic capture; prefer the app's plain-text/inline -mode if it has one. +Each scenario gets its own run-unique tmux session. Record ownership only after +creation succeeds, then clean up only that owned session. Fix the size for +deterministic capture; prefer the app's plain-text/inline mode if it has one. -## The four-command recipe +## Ownership-safe recipe ```bash -tmux new-session -d -s -x 200 -y 50 " 2>/tmp/-stderr.log" -tmux send-keys -t -l "literal text" # -l = no key-name parsing (paths, slashes) -tmux send-keys -t Enter -tmux capture-pane -t -p # -p = plain text; add -e only for styling +scenario="widget-form" +session="${scenario}-$(date +%s)-$$" +stderr_log="/tmp/${session}-stderr.log" +session_owned=0 + +cleanup() { + if [ "$session_owned" -eq 1 ]; then + tmux kill-session -t "$session" 2>/dev/null || true + fi +} +trap cleanup EXIT + +tmux new-session -d -s "$session" -x 200 -y 50 " 2>\"$stderr_log\"" +session_owned=1 +tmux send-keys -t "$session" -l "literal text" # -l = no key-name parsing (paths, slashes) +tmux send-keys -t "$session" Enter +tmux capture-pane -t "$session" -p # -p = plain text; add -e only for styling ``` - `-x 200 -y 50` fixes the pane size so `capture-pane` output is deterministic @@ -19,13 +32,10 @@ tmux capture-pane -t -p # -p = plain text; add -e only for `/foo/bar` gets parsed as arrow-key escapes instead of typed characters. - Redirect stderr to a file — panics, log lines, and debug probes land there, not in the pane, so they won't show up in a `capture-pane` snapshot at all. - -Kill any leftover session with the same name before starting a new one, so -reruns don't attach to a stale process: - -```bash -tmux kill-session -t 2>/dev/null # idempotent: fine if nothing to kill -``` +- The timestamp/PID suffix keeps the name readable and run-unique. If creation + still reports a collision, choose another unique name or report the failure. + Never kill the colliding session: `session_owned` remains `0`, so cleanup + cannot touch it. ## Form fill: send-keys patterns @@ -33,19 +43,19 @@ tmux kill-session -t 2>/dev/null # idempotent: fine if nothing to kill `-l` for literal text. A typical field-by-field fill mixes both: ```bash -tmux send-keys -t "n" # tap a key to open the form +tmux send-keys -t "$session" "n" # tap a key to open the form sleep 1 -tmux send-keys -t BTab # shift-tab back one field +tmux send-keys -t "$session" BTab # shift-tab back one field sleep 0.3 -tmux send-keys -t C-u # clear the current line +tmux send-keys -t "$session" C-u # clear the current line sleep 0.3 -tmux send-keys -t -l "some/literal/path" # literal — no key parsing +tmux send-keys -t "$session" -l "some/literal/path" # literal — no key parsing sleep 0.3 -tmux send-keys -t Tab # forward to next field +tmux send-keys -t "$session" Tab # forward to next field sleep 0.3 -tmux send-keys -t -l "text the user would type" +tmux send-keys -t "$session" -l "text the user would type" sleep 0.3 -tmux send-keys -t Enter # submit +tmux send-keys -t "$session" Enter # submit ``` `sleep 0.3` between keys is usually enough; bump to 0.5–1.0s for field @@ -60,7 +70,7 @@ glyph: ```bash for i in $(seq 1 30); do - pane=$(tmux capture-pane -t -p) + pane=$(tmux capture-pane -t "$session" -p) echo "$pane" | grep -q "state: processing" && break sleep 1 done @@ -78,11 +88,11 @@ immediate capture you can't tell "rendered then reconciled" from "never rendered": ```bash -tmux send-keys -t -l "trigger the optimistic action" -tmux send-keys -t Enter -echo "=== synchronous ===" ; tmux capture-pane -t -p | grep -E "pending-glyph" +tmux send-keys -t "$session" -l "trigger the optimistic action" +tmux send-keys -t "$session" Enter +echo "=== synchronous ===" ; tmux capture-pane -t "$session" -p | grep -E "pending-glyph" sleep 6 -echo "=== reconciled ===" ; tmux capture-pane -t -p | grep -E "pending-glyph" || echo "[no pending — reconciled]" +echo "=== reconciled ===" ; tmux capture-pane -t "$session" -p | grep -E "pending-glyph" || echo "[no pending — reconciled]" ``` ## Plain-text mode over the alt-screen buffer