From d211a16780b5a01df4042486ff0507b47fd420e3 Mon Sep 17 00:00:00 2001 From: Ernest Janssen Date: Wed, 1 Jul 2026 15:54:51 +0200 Subject: [PATCH] fix: include multiline TextView in iOS accessibility dump Multiline React Native TextInput renders as UITextView (XCUIElementTypeTextView), which was missing from acceptedTypes so it was dropped from the dump and could not be located or filled. Add TextView to the accepted and always-include sets, matching TextField. Fixes #293 Co-Authored-By: Claude Opus 4.8 --- devices/wda/source.go | 4 ++-- devices/wda/source_test.go | 41 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/devices/wda/source.go b/devices/wda/source.go index 58a131f..ce78536 100644 --- a/devices/wda/source.go +++ b/devices/wda/source.go @@ -42,7 +42,7 @@ func filterSourceElements(source sourceTreeElement) []types.ScreenElement { childElements = append(childElements, filterSourceElements(child)...) } - acceptedTypes := []string{"TextField", "Button", "Switch", "Icon", "SearchField", "StaticText", "Image", "SecureTextField", "WebView"} + acceptedTypes := []string{"TextField", "TextView", "Button", "Switch", "Icon", "SearchField", "StaticText", "Image", "SecureTextField", "WebView"} // strip XCUIElementType prefix if present elementType := strings.TrimPrefix(source.Type, "XCUIElementType") @@ -60,7 +60,7 @@ func filterSourceElements(source sourceTreeElement) []types.ScreenElement { } hasIdentifier := source.Label != nil || source.Name != nil || source.RawIdentifier != nil || source.PlaceholderValue != nil - alwaysInclude := elementType == "TextField" || elementType == "SecureTextField" || elementType == "Button" || elementType == "Switch" || elementType == "SearchField" || elementType == "WebView" + alwaysInclude := elementType == "TextField" || elementType == "TextView" || elementType == "SecureTextField" || elementType == "Button" || elementType == "Switch" || elementType == "SearchField" || elementType == "WebView" if !hasIdentifier && !alwaysInclude { return childElements } diff --git a/devices/wda/source_test.go b/devices/wda/source_test.go index 5877b71..39a39da 100644 --- a/devices/wda/source_test.go +++ b/devices/wda/source_test.go @@ -221,6 +221,47 @@ func TestFilterSourceElementsKeepsNestedWebViewsWithDifferentRects(t *testing.T) } } +func TestFilterSourceElementsIncludesMultilineTextView(t *testing.T) { + // A multiline React Native TextInput renders as a UITextView + // (XCUIElementTypeTextView). It must appear in the dump so it can be + // located and filled, just like a single-line TextField. + tree := sourceTreeElement{ + Type: "XCUIElementTypeOther", + Rect: visibleRect(0, 0, 402, 874), + Children: []sourceTreeElement{ + { + Type: "XCUIElementTypeTextView", + Label: strPtr("Notes"), + Rect: visibleRect(24, 200, 354, 120), + }, + }, + } + + output := filterSourceElements(tree) + + if len(output) != 1 { + t.Fatalf("expected 1 top-level element (TextView), got %d: %+v", len(output), output) + } + + textView := output[0] + if textView.Type != "TextView" || elementLabel(textView) != "Notes" { + t.Errorf("expected a TextView labeled 'Notes', got %+v", textView) + } +} + +func TestFilterSourceElementsIncludesTextViewWithoutIdentifier(t *testing.T) { + // An auto-focused multiline input may carry no label/name yet still needs + // to be present in the tree, so TextView is always included like TextField. + output := filterSourceElements(sourceTreeElement{ + Type: "XCUIElementTypeTextView", + Rect: visibleRect(24, 200, 354, 120), + }) + + if len(output) != 1 || output[0].Type != "TextView" { + t.Fatalf("expected an unlabeled TextView to be included, got %+v", output) + } +} + func TestFilterSourceElementsOmitsChildrenFromJsonWhenEmpty(t *testing.T) { // Leaf elements must not serialize an empty "children" array, so the // JSON output stays unchanged for consumers that expect leaves.