iOS 9 Swift Programming Cookbook (2015)
Chapter 9. UI Testing
9.4 Finding UI Components
Problem
You want to be able to find your UI components wherever they are, using simple to complex queries.
Solution
Construct queries of type XCUIElementQuery. Link these queries up together to create even more complicated queries and find your UI elements.
The XCUIElement class conforms to the XCUIElementTypeQueryProvider protocol. I am not going to waste space here and copy/paste Apple’s code in that protocol, but if you have a look at it yourself, you’ll see that it is made out of a massive list of properties such as groups,windows, dialogs, buttons, etc.
Here is how I recommend going about finding your UI elements using this knowledge:
1. Instantiate your app with XCUIApplication()
2. Refer to the windows property of the app object to get all the windows in the app as a query object of type XCUIElementQuery.
3. Now that you have a query object, use the childrenMatchingType(_:) method to find children inside this query.
Let’s say that you have a simple view controller. Inside that view controller’s view, you dump another view, and inside that view you dump a button so that your view hierarchy looks something like Figure 9-7. This hierarchy was created by placing a view inside the view controller’s view, and placing a button inside that view. We are now going to try to find that button and tap on it.
Figure 9-7. Hierarchy of views in this sample app
let app = XCUIApplication()
let view = app.windows.childrenMatchingType(.Unknown)
let innerView = view.childrenMatchingType(.Unknown)
let btn = innerView.childrenMatchingType(.Button).elementBoundByIndex(0)
XCTAssert(btn.exists)
btn.tap()
Discussion
Let’s write the code that we wrote just now, but in a more direct and compact way using the descendantsMatchingType(_:) method:
let app = XCUIApplication()
let btn = app.windows.childrenMatchingType(.Unknown)
.descendantsMatchingType(.Button).elementBoundByIndex(0)
XCTAssert(btn.exists)
btn.tap()
Here I am looking at the children of all my windows that are of type Unknown (view) and then finding a button inside that view, wherever that button may be and in whichever subview it may have been bundled up. Can this be written in a simpler way? You betcha.
let btn = XCUIApplication().buttons.elementBoundByIndex(0)
XCTAssert(btn.exists)
btn.tap()
NOTE
The buttons property of our app object is a query that returns all the buttons that are descendants of any window inside the app. Isn’t that awesome?
Those of you with a curious mind are probably thinking, “Can this be written in a more complex way”? Well yes, I am glad that you asked.
let mainView = XCUIApplication().windows.childrenMatchingType(.Unknown)
let viewsWithButton = mainView.descendantsMatchingType(.Unknown)
.containingType(.Button, identifier: nil)
XCTAssert(viewsWithButton.count > 0)
let btn = viewsWithButton.childrenMatchingType(.Button)
.elementBoundByIndex(0)
XCTAssert(btn.exists)
btn.tap()
Here I am first finding the main view inside the view controller that is on screen. Then I am finding all views that have a button inside them as a first child using the awesome containingType(_:identifier:) method. After I have all the views that have buttons in them, I find the first button inside the first view and then tap on it.
Now let’s take the same view hierarchy, but this time we will use predicates of type NSPredicate to find our button. There are two handy methods on XCUIElementQuery that we can use to find elements with predicates:
§ elementMatchingPredicate(_:)
§ matchingPredicate(_:)
The first method will find an element that matches a given predicate (so your result has to be unique) and the second method finds all elements that match a given predicate. I now want to find a button inside my UI with a specific title:
let app = XCUIApplication()
let btns = app.buttons.matchingPredicate(
NSPredicate(format: "title like[c] 'Button'"))
XCTAssert(btns.count >= 1)
let btn = btns.elementBoundByIndex(0)
XCTAssert(btn.exists)
Now another example. Let’s say we want to write a test script that goes through all the disabled buttons on our UI:
let app = XCUIApplication()
let disabledBtns = app.buttons.containingPredicate(
NSPredicate(format: "enabled == false"))
XCTAssert(disabledBtns.count > 1)
for n in 0..<disabledBtns.count{
let btn = disabledBtns.elementBoundByIndex(n)
XCTAssert(btn.exists)
}
See Also