feat: detect a wedged app with expect_change

An app can stop consuming input while it keeps drawing: Android delivers
the tap, the app never acts on it. Phase 3 hit this three times against
Spotify and the recipe failed on the label it was waiting for, which names
the wrong problem.

The new `expect_change <duration>` step fingerprints the screen (foreground
package plus every visible label and its bounds) and fails when the screen
does not change after the previous step, with the distinct reason
"the app stopped consuming input". It supports `retry N`, which re-presses
the step above a bounded number of times, so a dropped tap is still
recovered and a wedged app still ends the run (FR-11).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
2026-10-16 15:56:59 +11:00
parent 36b4dd2391
commit 43cd4282d0
6 changed files with 214 additions and 22 deletions

View File

@@ -273,6 +273,18 @@ func executeStep(r *adb.Runner, rec *Recipe, step Step, index int, params map[st
sr.Detail = fmt.Sprintf("%q is gone", args[0])
return nil
case "expect_change":
// The wedged-app check. An app that has stopped consuming input
// still draws its old screen, so a delivered tap or key changes
// nothing. Failing here names the real problem instead of blaming
// the label the next step would have waited for (Phase 5).
_, _, err := ui.ExpectChange(r, timeout)
if err != nil {
return describeFailure(r, rec, fmt.Errorf("the app stopped consuming input: %w", err))
}
sr.Detail = "screen changed"
return nil
case "tap":
return tapByText(r, rec, args[0], step.Exact, timeout, &sr)