|
|
@@ -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 <name> -x 200 -y 50 "<cmd> 2>/tmp/<name>-stderr.log"
|
|
|
-tmux send-keys -t <name> -l "literal text" # -l = no key-name parsing (paths, slashes)
|
|
|
-tmux send-keys -t <name> Enter
|
|
|
-tmux capture-pane -t <name> -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 "<cmd> 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 <name> -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 <name> 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 <name> 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 <name> "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 <name> BTab # shift-tab back one field
|
|
|
+tmux send-keys -t "$session" BTab # shift-tab back one field
|
|
|
sleep 0.3
|
|
|
-tmux send-keys -t <name> C-u # clear the current line
|
|
|
+tmux send-keys -t "$session" C-u # clear the current line
|
|
|
sleep 0.3
|
|
|
-tmux send-keys -t <name> -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 <name> Tab # forward to next field
|
|
|
+tmux send-keys -t "$session" Tab # forward to next field
|
|
|
sleep 0.3
|
|
|
-tmux send-keys -t <name> -l "text the user would type"
|
|
|
+tmux send-keys -t "$session" -l "text the user would type"
|
|
|
sleep 0.3
|
|
|
-tmux send-keys -t <name> 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 <name> -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 <name> -l "trigger the optimistic action"
|
|
|
-tmux send-keys -t <name> Enter
|
|
|
-echo "=== synchronous ===" ; tmux capture-pane -t <name> -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 <name> -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
|