fix: address the Maps search box by id, and re-prove Phase 5

The Maps omnibox hint rotates: it read "Search here" in the Phase 5 run and
"Try petrol stations, ATMs" in the AUD-128 re-verification, so the recipe's
text match on it failed. The resource id does not rotate.

- internal/ui: add Find/FindID and the "id:" selector; Find dispatches an
  id: selector to the resource id and everything else to text as before.
- internal/recipe: wait/tap/wait_gone/tap_if and the tap helper use Find, so a
  recipe may name a resource id. Guards stay text-only.
- recipes/maps_navigate.recipe: wait/tap id:search_omnibox_text_box.
- tests for the id selector; docs and README updated.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
2026-10-17 09:12:01 +11:00
parent a7c07c645e
commit 3ef11da808
9 changed files with 206 additions and 26 deletions

View File

@@ -129,7 +129,8 @@ func pickBestMatch(matches []*Node) (*Node, error) {
}
// TapText finds an element by its text (or content description) and taps its
// centre. This is FR-4: text match first, coordinates derived from bounds.
// centre. This is FR-4: a named match first, coordinates derived from bounds.
// A selector may also name a resource id with the "id:" prefix.
//
// It fails loudly when no element matches and never falls back to tapping a
// guessed coordinate.
@@ -141,9 +142,9 @@ func TapText(r *adb.Runner, query string, exact bool) (*Node, error) {
if err != nil {
return nil, err
}
matches := h.FindText(query, exact)
matches := h.Find(query, exact)
if len(matches) == 0 {
return nil, fmt.Errorf("element with text %q not found on screen; refusing to tap blindly", query)
return nil, fmt.Errorf("element matching %q not found on screen; refusing to tap blindly", query)
}
return TapNode(r, matches)
}
@@ -172,7 +173,8 @@ func TapNode(r *adb.Runner, matches []*Node) (*Node, error) {
// WaitForText polls the screen until an element matching query appears, then
// returns the best tappable match. Use it after an action that loads a new
// screen; apps do not draw instantly.
// screen; apps do not draw instantly. A selector may name a resource id with
// the "id:" prefix.
func WaitForText(r *adb.Runner, query string, exact bool, timeout time.Duration) (*Node, error) {
if timeout <= 0 {
timeout = 10 * time.Second
@@ -182,7 +184,7 @@ func WaitForText(r *adb.Runner, query string, exact bool, timeout time.Duration)
for time.Now().Before(deadline) {
h, err := Dump(r)
if err == nil {
matches := h.FindText(query, exact)
matches := h.Find(query, exact)
if len(matches) > 0 {
sawMatch = true
if n, perr := pickBestMatch(matches); perr == nil {
@@ -193,14 +195,15 @@ func WaitForText(r *adb.Runner, query string, exact bool, timeout time.Duration)
time.Sleep(PollInterval)
}
if sawMatch {
return nil, fmt.Errorf("text %q is present but never became a usable tap target within %s (it is off screen or disabled)", query, timeout)
return nil, fmt.Errorf("element matching %q is present but never became a usable tap target within %s (it is off screen or disabled)", query, timeout)
}
return nil, fmt.Errorf("text %q did not appear within %s", query, timeout)
return nil, fmt.Errorf("element matching %q did not appear within %s", query, timeout)
}
// WaitForNoText polls the screen until no element matches query. Recipes use it
// to wait for one list to be replaced by another, for example a search result
// list replacing the "Recents" list.
// list replacing the "Recents" list. A selector may name a resource id with the
// "id:" prefix.
func WaitForNoText(r *adb.Runner, query string, exact bool, timeout time.Duration) error {
if timeout <= 0 {
timeout = 10 * time.Second
@@ -208,12 +211,12 @@ func WaitForNoText(r *adb.Runner, query string, exact bool, timeout time.Duratio
deadline := time.Now().Add(timeout)
for time.Now().Before(deadline) {
h, err := Dump(r)
if err == nil && len(h.FindText(query, exact)) == 0 {
if err == nil && len(h.Find(query, exact)) == 0 {
return nil
}
time.Sleep(PollInterval)
}
return fmt.Errorf("text %q was still on screen after %s", query, timeout)
return fmt.Errorf("element matching %q was still on screen after %s", query, timeout)
}
// ScreenSize reads the physical screen size.