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.