mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
fix(selectors): tag names the whole tag, not a substring of it
matchSelectorKind had no case for tag, so it fell through to the raw attribute path and matched by substring. web-runtime.ts compiles tag to a CSS type selector, so tag:li resolved to <todo-list> on the Go side and to nothing on the web side.
This commit is contained in:
1 parent
81fe5f66f2
commit
9012ffa304
3 files changed
+74
-1
No files matched your search
@@ -60,6 +60,7 @@ Selectors are passed to `ax.find()`, `ax.findAll()`, and element-scoped `.find()
|
||||
| `text:<value>` | Substring match on text content, innermost match only |
|
||||
| `desc:<value>` | Exact match on accessibility description; also matches when description starts with `<value>, ` (iOS merged labels) |
|
||||
| `descPrefix:<prefix>` | Starts-with match on accessibility description |
|
||||
| `tag:<value>` | Exact match on the element's tag name (web) |
|
||||
| `<attr>:<value>` | Substring match on any raw attribute by name |
|
||||
|
||||
Boolean attributes (`"true"` / `"false"`) use exact match rather than substring.
|
||||
@@ -75,7 +76,7 @@ s.ax.find({ accessibilityText: "LoginScreen" })
|
||||
s.ax.find({ testTag: "AccountCard", clickable: true })
|
||||
```
|
||||
|
||||
Every key-value pair must match. A key means the same thing here as in the string form: `id`, `desc`, `idPrefix` and `descPrefix` keep their matching rules, and every other key is an attribute name, with substring and boolean rules per attribute.
|
||||
Every key-value pair must match. A key means the same thing here as in the string form: `id`, `desc`, `idPrefix`, `descPrefix` and `tag` keep their matching rules, and every other key is an attribute name, with substring and boolean rules per attribute.
|
||||
|
||||
Known attribute names are typed; you get autocomplete on `testTag`, `text`, `content-desc`, the boolean states (`clickable`, `enabled`, `focused`, `checked`, `selected`), and the cross-platform aliases (`identifier`, `accessibilityIdentifier`, `accessibilityText`, `accessibilityLabel`, `label`, `resource-id`, `class`, `elementType`, `package`, `placeholderValue`, `hintText`). Boolean state attributes accept a native `true` / `false`. Other attribute keys still type-check as a string-valued fallback so raw driver attributes remain reachable.
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
// text:<value> - substring on text attribute, innermost match only
|
||||
// desc:<value> - substring on content-desc / accessibilityText
|
||||
// descPrefix:<prefix> - starts-with on content-desc / accessibilityText
|
||||
// tag:<value> - exact match on the element's tag name (web)
|
||||
//
|
||||
// Object selectors (multi-attribute AND, element-scoped or global):
|
||||
// { attr: value, ... } - all key/value pairs must match, each key resolved by
|
||||
@@ -394,6 +395,13 @@ func matchSelectorKind(element *Element, kind, value string) (bool, bool) {
|
||||
strings.HasPrefix(element.Description, value+", "), true
|
||||
case "descPrefix":
|
||||
return strings.HasPrefix(element.Description, value), true
|
||||
case "tag":
|
||||
// Both DOM resolvers compile this to a CSS type selector, which is the
|
||||
// whole tag name. A substring rule here made {tag: "li"} name
|
||||
// <todo-list> and {tag: "a"} name <todo-app>, so a selector meant for a
|
||||
// row resolved to the container holding it.
|
||||
tag, ok := element.Attributes["tag"]
|
||||
return ok && tag == value, true
|
||||
default:
|
||||
return false, false
|
||||
}
|
||||
|
||||
@@ -1551,3 +1551,67 @@ func TestSelectorFormsResolveTheSameElement(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// customElementDump is the shape a page built from custom elements reports: a
|
||||
// container's tag name contains the tag name of what it holds, so "todo-list"
|
||||
// carries "li" and "todo-app" carries "a".
|
||||
const customElementDump = `{
|
||||
"attributes": {"tag": "todo-app", "resource-id": "app", "bounds": "[0,0,800,600]"},
|
||||
"children": [
|
||||
{
|
||||
"attributes": {"tag": "todo-list", "resource-id": "list", "bounds": "[0,0,800,400]"},
|
||||
"children": [
|
||||
{"attributes": {"tag": "li", "resource-id": "todo_1", "bounds": "[0,0,800,50]"}, "children": []},
|
||||
{"attributes": {"tag": "li", "resource-id": "todo_2", "bounds": "[0,50,800,100]"}, "children": []}
|
||||
]
|
||||
},
|
||||
{
|
||||
"attributes": {"tag": "a", "resource-id": "filter_all", "bounds": "[0,400,800,450]"},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
func TestTagNamesTheWholeTagNotASubstringOfIt(t *testing.T) {
|
||||
tree, _ := Parse(customElementDump)
|
||||
for _, test := range []struct {
|
||||
value string
|
||||
want []string
|
||||
}{
|
||||
{"li", []string{"todo_1", "todo_2"}},
|
||||
{"a", []string{"filter_all"}},
|
||||
} {
|
||||
t.Run(test.value, func(t *testing.T) {
|
||||
sel := Selector{Filters: []AttrFilter{{Attr: "tag", Value: test.value}}}
|
||||
if got := resourceIDsOf(tree.FindAllBySelector(sel)); !slices.Equal(got, test.want) {
|
||||
t.Errorf("{tag: %q} matched %v, want %v", test.value, got, test.want)
|
||||
}
|
||||
stringForm := "tag:" + test.value
|
||||
if got := resourceIDsOf(tree.FindAllNodes(stringForm)); !slices.Equal(got, test.want) {
|
||||
t.Errorf("%q matched %v, want %v", stringForm, got, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTagMatchesTheElementItNames(t *testing.T) {
|
||||
tree, _ := Parse(customElementDump)
|
||||
for _, test := range []struct {
|
||||
value string
|
||||
want []string
|
||||
}{
|
||||
{"todo-app", []string{"app"}},
|
||||
{"todo-list", []string{"list"}},
|
||||
} {
|
||||
t.Run(test.value, func(t *testing.T) {
|
||||
sel := Selector{Filters: []AttrFilter{{Attr: "tag", Value: test.value}}}
|
||||
if got := resourceIDsOf(tree.FindAllBySelector(sel)); !slices.Equal(got, test.want) {
|
||||
t.Errorf("{tag: %q} matched %v, want %v", test.value, got, test.want)
|
||||
}
|
||||
stringForm := "tag:" + test.value
|
||||
if got := resourceIDsOf(tree.FindAllNodes(stringForm)); !slices.Equal(got, test.want) {
|
||||
t.Errorf("%q matched %v, want %v", stringForm, got, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user