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.
This commit is contained in:
@@ -99,6 +99,59 @@ func TestPickBestMatchRejectsDisabledOnly(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// The Maps search box keeps a stable resource id but a rotating label, so it is
|
||||
// addressed with the "id:" selector. This covers the full and short id, the
|
||||
// prefix dispatch, and that a plain selector is still matched by text.
|
||||
const sampleSelectorDump = `<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
|
||||
<hierarchy rotation="0">
|
||||
<node index="0" text="" resource-id="com.google.android.apps.maps:id/search_omnibox_text_box" class="android.widget.EditText" package="com.google.android.apps.maps" content-desc="Try petrol stations, ATMs" clickable="true" enabled="true" bounds="[31,142][663,268]"/>
|
||||
<node index="1" text="Search" resource-id="com.example:id/other" class="android.widget.TextView" package="com.example" clickable="true" enabled="true" bounds="[0,0][10,10]"/>
|
||||
</hierarchy>`
|
||||
|
||||
func parseSelectorSample(t *testing.T) *Hierarchy {
|
||||
t.Helper()
|
||||
h, err := parseHierarchy([]byte(sampleSelectorDump))
|
||||
if err != nil {
|
||||
t.Fatalf("parseHierarchy: %v", err)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func TestFindIDSelector(t *testing.T) {
|
||||
h := parseSelectorSample(t)
|
||||
|
||||
// Full resource id, and the short name after the "/".
|
||||
for _, sel := range []string{
|
||||
"id:search_omnibox_text_box",
|
||||
"id:com.google.android.apps.maps:id/search_omnibox_text_box",
|
||||
} {
|
||||
matches := h.Find(sel, false)
|
||||
if len(matches) != 1 {
|
||||
t.Fatalf("Find(%q): want 1 match, got %d", sel, len(matches))
|
||||
}
|
||||
if matches[0].Class != "android.widget.EditText" {
|
||||
t.Fatalf("Find(%q) matched %q", sel, matches[0].Class)
|
||||
}
|
||||
}
|
||||
|
||||
// Exact matching of the short id.
|
||||
if got := len(h.Find("id:search_omnibox_text_box", true)); got != 1 {
|
||||
t.Fatalf("exact id: want 1, got %d", got)
|
||||
}
|
||||
// A plain selector is not diverted to the id match.
|
||||
if got := len(h.Find("search_omnibox_text_box", false)); got != 0 {
|
||||
t.Fatalf("plain text must not match a resource id, got %d", got)
|
||||
}
|
||||
// Text still works through Find.
|
||||
if got := len(h.Find("Search", true)); got != 1 {
|
||||
t.Fatalf("text via Find: want 1, got %d", got)
|
||||
}
|
||||
// An unknown id is a clean miss, not a fallthrough to text.
|
||||
if got := h.Find("id:no_such_id", false); len(got) != 0 {
|
||||
t.Fatalf("unknown id: want 0, got %d", len(got))
|
||||
}
|
||||
}
|
||||
|
||||
func TestEscapeInputText(t *testing.T) {
|
||||
cases := map[string]string{
|
||||
"hello world": "'hello%sworld'",
|
||||
|
||||
Reference in New Issue
Block a user