13

I have been locating XCUIElements using this method:

app.staticTexts["Full Label Text"]

But what if I only know part of the label text? Part of the label text is generated dynamically (e.g. "Item #1", "Item #2", etc.) so I would like to search for it by finding an element that contains part of the text (e.g. searching by "Item"). Is there any method to do this in Swift?

C J
  • 567
  • 2
  • 4
  • 11

2 Answers2

24

You can find elements with a predicate. Use the containing(_ predicate: NSPredicate) -> XCUIElementQuery method from XCUIElementQuery.

let predicate = NSPredicate(format: "label CONTAINS[c] 'Item'")
let labels = XCUIApplication().staticTexts.containing(predicate)
N Brown
  • 507
  • 3
  • 9
  • What is the `[c]` for? – rmp251 May 28 '21 at 04:12
  • Case insensitivity. https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Predicates/Articles/pSyntax.html#//apple_ref/doc/uid/TP40001795-CJBDBHCB – N Brown May 29 '21 at 11:12
  • Please see this gist for an example of how to use this in order to click on photos in the Camera Roll: https://gist.github.com/bitops/9182b5ee96c682aba57d9fa16ca6b987 – bitops Apr 12 '22 at 18:09
  • `let labels = XCUIApplication().staticTexts.matching(predicate)` may I know the different between `containing` and `matching` usage here? – Zhou Haibo May 06 '22 at 11:06
1

The accepted answer only seems to work if you DO NOT set an accessibilityLabel in which case you might want to use .accessibilityIdentifier instead

I also ran into this issue and wasn't getting the expected results beacuse I was using accessibilityLabel and not accessibilityIdentifier .

Here is the UI,

DemoView showing the 5 UI elements

Here is the output of the test case code,

Element Info - Type: Window     Label:                                          Value: 
Element Info - Type: StaticText Label: Hello World with No Acccessibility Label Value: 
Element Info - Type: StaticText Label: AcccessibilityLabel                      Value: 
Element Info - Type: TextField  Label: TextFieldSwiftUI                         Value: Initial Text
Element Info - Type: Button     Label: Click Me!                                Value: 

Here is the code,

  1. DemoView

    struct DemoView: View {
    
        @State
        var textEntry = "Initial Text"
    
        var body: some View{
            Text("Hello World with No Acccessibility Label")
                .font(.largeTitle)
                 .accessibilityIdentifier("AcccessibilityID")
                .padding()
            Text("Hello World with an Acccessibility Label")
                .font(.largeTitle)
                .accessibilityLabel("AcccessibilityLabel")
                .padding()
    
            TextField("Title", text: $textEntry)
                .accessibilityLabel("TextFieldSwiftUI")
                .padding()
    
            Button("Click Me!") {
                print("Clicked")
            }
        }
    }
    
  2. DemoViewUITests

    class DemoViewUITests: XCTestCase {
    
        override func setUpWithError() throws {}
        override func tearDownWithError() throws {}
        func testExample() throws {
            // UI tests must launch the application that they test.
            let app = XCUIApplication()
            app.launch()
    
            // get all the elements in the app
            let allTheElements = app.descendants(matching: .any)
    
            // filter out the 'other' elements
            // These seem to be the internal views wrapping the app
            // as they all have a frame of the full window
            // looking at the Window Inspector I think they are
            // - UITransitionView
            // - UIDropShadowView
            // - HostingViewController
            // - HostingView
            let notOtherPredicate = NSPredicate(format: "elementType != %d",
                                                // can't just use the enum value
                                                // see stackoverflow.com/questions/32298645/enum-value-and-predicates
                                                // so we pass in the NSUInteger version (other is 1)
                                                // see developer.apple.com/documentation/xctest/xcuielementattributes/1500614-elementtype
                                                argumentArray: [XCUIElement.ElementType.other.rawValue])
            let elements = allTheElements.matching(notOtherPredicate)
    
            // should have 5
            // 1 - the Window
            // 2 - the Text with no accessibility label
            // 3 - the Text with an accessibility label
            // 4 - the TextField
            // 5 - the Button
            XCTAssertEqual(elements.count, 5)
    
            for elementIndex in 0..<elements.count{
                let element     = elements.element(boundBy: elementIndex)
                let type  = ELEMENT_TYPE[element.elementType.rawValue]!.padding(toLength: 10, withPad: " ", startingAt: 0)
                let label = "\(element.label)".padding(toLength: 40, withPad: " ", startingAt: 0)
                let value = "\(element.value ?? "NIL")"
                print("Element Info - Type: \(type) Label: \(label) Value: \(value)")
            }
    
        }
    }
    
    // Rest is just for pretty formatting :)
    // can't extract key representation
    // from C Struct XCUIElementType
    // (even though it's XCUIElement.ElementType it
    // points to XCUIElementType weird...)
    // so we provide a table here
    var ELEMENT_TYPE : [UInt: String] = [
        0 : "Any",
        1 : "Other",
        2 : "Application",
        3 : "Group",
        4 : "Window",
        5 : "Sheet",
        6 : "Drawer",
        7 : "Alert",
        8 : "Dialog",
        9 : "Button",
        10 : "RadioButton",
        11 : "RadioGroup",
        12 : "CheckBox",
        13 : "DisclosureTriangle",
        14 : "PopUpButton",
        15 : "ComboBox",
        16 : "MenuButton",
        17 : "ToolbarButton",
        18 : "Popover",
        19 : "Keyboard",
        20 : "Key",
        21 : "NavigationBar",
        22 : "TabBar",
        23 : "TabGroup",
        24 : "Toolbar",
        25 : "StatusBar",
        26 : "Table",
        27 : "TableRow",
        28 : "TableColumn",
        29 : "Outline",
        30 : "OutlineRow",
        31 : "Browser",
        32 : "CollectionView",
        33 : "Slider",
        34 : "PageIndicator",
        35 : "ProgressIndicator",
        36 : "ActivityIndicator",
        37 : "SegmentedControl",
        38 : "Picker",
        39 : "PickerWheel",
        40 : "Switch",
        41 : "Toggle",
        42 : "Link",
        43 : "Image",
        44 : "Icon",
        45 : "SearchField",
        46 : "ScrollView",
        47 : "ScrollBar",
        48 : "StaticText",
        49 : "TextField",
        50 : "SecureTextField",
        51 : "DatePicker",
        52 : "TextView",
        53 : "Menu",
        54 : "MenuItem",
        55 : "MenuBar",
        56 : "MenuBarItem",
        57 : "Map",
        58 : "WebView",
        59 : "IncrementArrow",
        60 : "DecrementArrow",
        61 : "Timeline",
        62 : "RatingIndicator",
        63 : "ValueIndicator",
        64 : "SplitGroup",
        65 : "Splitter",
        66 : "RelevanceIndicator",
        67 : "ColorWell",
        68 : "HelpTag",
        69 : "Matte",
        70 : "DockItem",
        71 : "Ruler",
        72 : "RulerMarker",
        73 : "Grid",
        74 : "LevelIndicator",
        75 : "Cell",
        76 : "LayoutArea",
        77 : "LayoutItem",
        78 : "Handle",
        79 : "Stepper",
        80 : "Tab",
        81 : "TouchBar",
        82 : "StatusItem",
    ]
    
Zimm3r
  • 3,369
  • 5
  • 35
  • 53