Replacing sleep()
Wait for the screen to change, then wait for it to stop changing.
sleep(2) is wrong in both directions. Too short and you capture a half-drawn menu; too long and you have wasted two seconds on every interaction in a test suite. The fix is not a better constant — it is having the tool watch the screen and return when it has settled, instead of guessing how long that takes.
eensh observe --display :1 --stable-for 300ms --timeout 10s --json --base64Three observation commands, and how they differ
wait-change, wait-stable and observe differ in exactly one way: what each frame is compared against. Getting that backwards returns a confident answer to the wrong question, which is worse than an error.
| Command | Compares | Question |
|---|---|---|
| wait-change | Each frame against a fixed baseline | Has the visible state changed? |
| wait-stable | Consecutive frames | Has it stopped changing? |
| observe | Baseline, then consecutive | What did it settle into? |
- capture baseline
- compare against baseline
- changed!
- compare consecutive
- still
- return frame
Why the comparison target matters
A fixed baseline notices gradual change. Imagine a progress bar filling one percent at a time. Each step moves far fewer pixels than the area threshold, so no single consecutive comparison sees a meaningful change. But every frame differs from the original, and cumulative drift eventually crosses the threshold. Compare against a fixed baseline and you notice; compare consecutively and you wait forever.
Consecutive comparison notices stillness. The mirror image. A fixed baseline tells you the screen looks different from how it started — it cannot tell you whether it is still moving. For that you need to know whether the last two frames differed, which is a question only consecutive comparison answers.
Why observe waits past the first change
The first frame that differs from the baseline is usually not the state you want. It is a half-drawn menu, an animation step, or a layout that has not finished laying out. Returning it would be technically correct and practically useless.
So observe switches modes: a fixed baseline until a change is seen, then consecutive comparison until the scene holds still for --stable-for. The result is the state the transition settled into, which is almost always what a caller meant.
The options worth setting
| Option | Default | Meaning |
|---|---|---|
| --stable-for | 300ms | How long the scene must hold still. Too short and you catch a pause between animation frames; too long and every observation pays for it. |
| --timeout | 5s | Total deadline. Exceeding it exits 100, which is an outcome rather than an error. |
| --interval | 100ms | Target cadence between samples. Sampling is scheduled from a fixed origin, so a slow capture cannot accumulate drift. |
| --area-threshold | 0.005 | Smallest changed fraction treated as meaningful. Note this is stricter than diff's default of 0.0, deliberately — see below. |
Durations need a unit
100ms, 300ms, 1s, 5s. A bare 5 is rejected rather than guessed at, because --timeout 5 is ambiguous between five seconds and five milliseconds — and a silently wrong timeout is worse than a parse error.
Next: what a single observation still cannot tell you — motion from stillness, and why one frame is never enough.