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:
@@ -40,6 +40,8 @@
|
||||
// title matches the text; with no text, wait for PLAYING
|
||||
// alone (a radio plays a different track every few minutes)
|
||||
// - wait_gone "Recents" 30s wait until no element matches the text
|
||||
// - expect_change 10s fail unless the screen changes within the time
|
||||
// (the wedged-app check: a delivered input that the app never acts on)
|
||||
// - tap "Search" tap the element whose text matches (FR-4)
|
||||
// - tap exact "Pause" tap an element whose text matches exactly
|
||||
// - tap_if "Clear search query" tap it when it is on screen, else skip
|
||||
@@ -140,21 +142,22 @@ var verbs = map[string]struct {
|
||||
durArg bool // the single argument is a duration
|
||||
allowExact bool
|
||||
}{
|
||||
"launch": {0, 1, false, false},
|
||||
"foreground": {0, 1, false, false},
|
||||
"wait": {1, 2, false, true},
|
||||
"playing": {0, 2, false, false},
|
||||
"wait_gone": {1, 2, false, true},
|
||||
"tap": {1, 2, false, true},
|
||||
"tap_if": {1, 1, false, true},
|
||||
"tap_row": {1, 2, false, true},
|
||||
"menu_row": {1, 2, false, false},
|
||||
"scroll": {1, 1, false, false},
|
||||
"type": {1, 1, false, false},
|
||||
"key": {1, 1, false, false},
|
||||
"back": {0, 0, false, false},
|
||||
"home": {0, 0, false, false},
|
||||
"sleep": {1, 1, true, false},
|
||||
"launch": {0, 1, false, false},
|
||||
"foreground": {0, 1, false, false},
|
||||
"wait": {1, 2, false, true},
|
||||
"playing": {0, 2, false, false},
|
||||
"wait_gone": {1, 2, false, true},
|
||||
"expect_change": {1, 1, true, false},
|
||||
"tap": {1, 2, false, true},
|
||||
"tap_if": {1, 1, false, true},
|
||||
"tap_row": {1, 2, false, true},
|
||||
"menu_row": {1, 2, false, false},
|
||||
"scroll": {1, 1, false, false},
|
||||
"type": {1, 1, false, false},
|
||||
"key": {1, 1, false, false},
|
||||
"back": {0, 0, false, false},
|
||||
"home": {0, 0, false, false},
|
||||
"sleep": {1, 1, true, false},
|
||||
}
|
||||
|
||||
// keyNames are the key event names a recipe may use.
|
||||
@@ -367,8 +370,8 @@ func parseStep(body string, lineNo int) (*Step, error) {
|
||||
if err != nil || n < 1 {
|
||||
return nil, fmt.Errorf("line %d: retry needs a positive number, got %q", lineNo, args[i+1])
|
||||
}
|
||||
if verb != "wait" && verb != "playing" {
|
||||
return nil, fmt.Errorf("line %d: retry is only allowed on a wait or playing step", lineNo)
|
||||
if verb != "wait" && verb != "playing" && verb != "expect_change" {
|
||||
return nil, fmt.Errorf("line %d: retry is only allowed on a wait, playing or expect_change step", lineNo)
|
||||
}
|
||||
step.Retry = n
|
||||
args = append(args[:i], args[i+2:]...)
|
||||
|
||||
Reference in New Issue
Block a user