mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 11:07:10 +00:00
fix(hierarchy): reach the accessible label through every name for it
label and accessibilityLabel aliased onto accessibilityText alone, which only the ios sidecar writes, and alias expansion is ONE level: the hop from accessibilityText to content-desc was never taken, so both keys matched nothing on android and on the chrome dump, which write the fact under content-desc. ariaLabel and contentDescription aliased onto nothing at all and matched nothing anywhere. The web runtime resolves all four against the live DOM, so a selector naming a field this way found it on one host and no element at all on the other. The keys are accepted, so no unknown-key error fires, and a property over the element that was never found passes having checked nothing. Each name lists both keys rather than chaining through accessibilityText: transitive expansion would silently widen every existing key at once.
This commit is contained in:
1 parent
5a7c223464
commit
ae629d9d7d
2 files changed
+69
-3
No files matched your search
@@ -259,9 +259,14 @@ type AttrFilter struct {
|
|||||||
var attributeAliases = map[string][]string{
|
var attributeAliases = map[string][]string{
|
||||||
// Android XML legacy name; web driver uses content-desc; the sidecar normalises to accessibilityText
|
// Android XML legacy name; web driver uses content-desc; the sidecar normalises to accessibilityText
|
||||||
"content-desc": {"accessibilityText"},
|
"content-desc": {"accessibilityText"},
|
||||||
// iOS AXElement / UIKit names
|
// Every other name for the accessible label. Alias expansion is one level,
|
||||||
"label": {"accessibilityText"},
|
// so each name lists both keys a producer writes the fact under rather than
|
||||||
"accessibilityLabel": {"accessibilityText"},
|
// hopping through accessibilityText: android and the chrome dump write
|
||||||
|
// content-desc, the ios sidecar writes accessibilityText.
|
||||||
|
"label": {"accessibilityText", "content-desc"},
|
||||||
|
"accessibilityLabel": {"accessibilityText", "content-desc"},
|
||||||
|
"ariaLabel": {"accessibilityText", "content-desc"},
|
||||||
|
"contentDescription": {"accessibilityText", "content-desc"},
|
||||||
// accessibilityText is the canonical key; also check content-desc for Android/web
|
// accessibilityText is the canonical key; also check content-desc for Android/web
|
||||||
"accessibilityText": {"content-desc"},
|
"accessibilityText": {"content-desc"},
|
||||||
// resource-id canonical key; also check identifier (iOS AXElement raw field)
|
// resource-id canonical key; also check identifier (iOS AXElement raw field)
|
||||||
|
|||||||
@@ -552,6 +552,24 @@ const classAttrDump = `{
|
|||||||
]
|
]
|
||||||
}`
|
}`
|
||||||
|
|
||||||
|
const webAttrDump = `{
|
||||||
|
"attributes": {"resource-id": "page", "tag": "html", "bounds": "[0,0,1280,720]"},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"attributes": {"resource-id": "login_email", "content-desc": "login_email", "tag": "input", "bounds": "[0,0,300,40]"},
|
||||||
|
"children": [],
|
||||||
|
"editable": true,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"attributes": {"resource-id": "customer_row_a1", "data-testid": "customer-row", "tag": "div", "bounds": "[0,40,300,80]"},
|
||||||
|
"children": [],
|
||||||
|
"clickable": true,
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`
|
||||||
|
|
||||||
func TestRawResourceIDSubstringMatch(t *testing.T) {
|
func TestRawResourceIDSubstringMatch(t *testing.T) {
|
||||||
tree, _ := Parse(androidAttrDump)
|
tree, _ := Parse(androidAttrDump)
|
||||||
el := tree.Find("resource-id:row1")
|
el := tree.Find("resource-id:row1")
|
||||||
@@ -592,6 +610,49 @@ func TestClassNameAliasMatchesClass(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// One fact, four names, and only two of them reached it. Android and the chrome
|
||||||
|
// dump write the accessible name under content-desc; ios writes it under
|
||||||
|
// accessibilityText. label and accessibilityLabel aliased onto accessibilityText
|
||||||
|
// alone, and alias expansion is ONE level, so the hop from there to content-desc
|
||||||
|
// was never taken: both keys matched nothing on the two platforms that write
|
||||||
|
// content-desc. ariaLabel and contentDescription aliased onto nothing at all and
|
||||||
|
// matched nothing anywhere. The web runtime resolves all four against the live
|
||||||
|
// DOM, so a selector naming a field this way found it on one host and no element
|
||||||
|
// at all on the other, with no unknown-key error to say so.
|
||||||
|
func TestAccessibilityLabelAliasesReachContentDesc(t *testing.T) {
|
||||||
|
tree, _ := Parse(webAttrDump)
|
||||||
|
for _, key := range []string{"label", "accessibilityLabel", "ariaLabel", "contentDescription"} {
|
||||||
|
element := tree.Find(key + ":login_email")
|
||||||
|
if element == nil {
|
||||||
|
t.Fatalf("expected %s: to match the content-desc attribute via alias", key)
|
||||||
|
}
|
||||||
|
if element.ResourceID != "login_email" {
|
||||||
|
t.Fatalf("%s: matched %q, want login_email", key, element.ResourceID)
|
||||||
|
}
|
||||||
|
object := tree.FindBySelector(Selector{Filters: []AttrFilter{
|
||||||
|
{Attr: key, Value: "login_email"},
|
||||||
|
}})
|
||||||
|
if object == nil {
|
||||||
|
t.Fatalf("expected the object form of %s to match content-desc via alias", key)
|
||||||
|
}
|
||||||
|
if object.ResourceID != element.ResourceID {
|
||||||
|
t.Fatalf("the object form of %s matched %q, want %q",
|
||||||
|
key, object.ResourceID, element.ResourceID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The iOS sidecar writes the same fact under accessibilityText, which the two
|
||||||
|
// names already reached and have to keep reaching.
|
||||||
|
func TestAccessibilityLabelAliasesStillReachAccessibilityText(t *testing.T) {
|
||||||
|
tree, _ := Parse(iosAttrDump)
|
||||||
|
for _, key := range []string{"label", "accessibilityLabel", "ariaLabel", "contentDescription"} {
|
||||||
|
if tree.Find(key+":Close") == nil {
|
||||||
|
t.Fatalf("expected %s: to match the accessibilityText attribute via alias", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestContentDescAliasOnIOS(t *testing.T) {
|
func TestContentDescAliasOnIOS(t *testing.T) {
|
||||||
tree, _ := Parse(iosAttrDump)
|
tree, _ := Parse(iosAttrDump)
|
||||||
el := tree.Find("content-desc:Close")
|
el := tree.Find("content-desc:Close")
|
||||||
|
|||||||
Reference in new issue
Block a user