Let's use a sample to explain the problem. See below slice of a window structure. This window is used to represent a book structure. The book has several chapters while each chapter contains pages. The chapters and pages in gray are the ones that have been disabled on GUI.
Window 0x600000160cc0: Main Window, title: 'My Book'Based on above structure, we have below requests to achieve.
SplitGroup 0x600000160d80:
ScrollView 0x600000160e40:
Outline 0x600000160f00:
OutlineRow 0x600000160fc0:
Cell 0x600000161080:
TextField 0x6000001612c0: identifier: Chapter 1
TextField 0x600000161440: identifier: Page 1
OutlineRow 0x600000161500:
Cell 0x6000001615c0:
TextField 0x600000161680: identifier: Chapter 2
TextField 0x600000161740: identifier: Page 1
OutlineRow 0x600000161800:
Cell 0x6000001618c0: Keyboard Focused
TextField 0x600000161980: identifier: Chapter 3
TextField 0x600000161980: identifier: Page 1
TextField 0x600000161a40: identifier: Page 2
- Q1: I want to identify the cell which contains the Chapter 1.
- Q2: I want to identify the TextField which represents the Chapter 1.
- Q3: I want to identify all the elements which have been disabled.
Functions for identifying elements
In XCUI, elements are identified using queries. XCUI provides the class XCUIElementQuery for filtering out the objects by conditions. Below are the functions that are used most frequently for identifying elements:/** Returns a new query that applies the specified attributes or predicate to the receiver. The predicate will be evaluated against objects of type id.*/ open func matching(_ predicate: NSPredicate) -> XCUIElementQuery open func matching(_ elementType: XCUIElementType, identifier: String?) -> XCUIElementQuery open func matching(identifier: String) -> XCUIElementQuery /** Returns a new query for finding elements that contain a descendant matching the specification. The predicate will be evaluated against objects of type id. */ open func containing(_ predicate: NSPredicate) -> XCUIElementQuery open func containing(_ elementType: XCUIElementType, identifier: String?) -> XCUIElementQuery
The matching function is applying on the current objects to filter out the ones which have the specific identify value, while the containing function looks into the descendants of current objects and returns the ones which have a child that matches the condition.
Sample Code for solving the problem Q1 - Q3
Let's come back to the questions to see how to use the functions above for solving real problems.> Q1: I want to identify the cell which contains the chapter 1.
This question requires a "cell" element which contains a descendant that can be identified by the string "Chapter 1". The function containning(XCUIElementType, identify String) is used to find the elements that containing a descendant matching the identifying string. The code below will return the Cell 0x600000161080
windows["My Book"].cells.containing(.TextField, identifier: "Chapter 1")
> Q2: I want to identify the TextField which represents the Chapter 1.
This question is similar to the first question, and it also uses the identity string to find objects. Instead of looking into the descendants, it evaluates against the elements themselves. We use the function matching(XCUIElementType, identify String) as below.This code will return TextField 0x6000001612c0.
windows["My Book"].textFields.matching(.TextField, identifier: "Chapter 1")
> Q3: I want to identify all the elements which have been disabled.
This question does not request on any identity strings, instead it requests elements on element status "Disable". The functions we used to answer the first two questions could not solve this problem anymore. In this case, we have to use the function matching(NSPredicate). Below is the sample code.
let disabledPredicates = NSPredicate(format: "isEnabled == false”) windows[“My Book”].textFields.matching(disabledPredicates)
We first created a NSPredicate instance defining the rule that the property "isEnabled" equals to false. Then we used this predicate to find out the elements which exactly matches the predicate. This method is also often used to identify elements by any properties.
Next part, let's see how we use the NSPredicate in the automation and how many properties can we use to identify the elements.
How to use NSPredicate in Automation.
The NSPredicate class is widely used to define logical conditions used to constrain a search either for a fetch or for in-memory filtering.In Cocoa, a predicate is a logical statement that evaluates to a Boolean value (true or false). There are three types of predicates:- Simple comparisons, such as grade == 7 or firstName like 'Mark'
- Case or diacritic insensitive lookups, such as name contains[cd] 'citroen'
- Logical operations, such as (firstName beginswith 'M') AND (lastName like 'Adderley')
- exists: Bool
- isHittable: Bool
- identifier: String
- frame: CGRect
- value: Any?
- title: String
- label: String
- elementType: XCUIElementType
- isEnabled: Bool
- horizontableSizeClass: XCUIUserInterfaceSizeClass
- verticalSizeClass: XCUIUserInterfaceSizeClass
- placeholderValue: String?
- isSelected: Bool
- debugDescription: String
extension XCTestCase { func waitForHittable(element: XCUIElement, waitSeconds: Double) { let isHittablePredicate = NSPredicate(format: "isHittable == true") expectation(for: isHittablePredicate, evaluatedWith: element, handler: nil) waitForExpectations(timeout: waitSeconds) { (error) -> Void in if (error != nil) { let message = "Failed to find element hittable after \(waitSeconds) seconds." XCTFail(message) } } } func waitForExists(element: XCUIElement, waitSeconds: Double) { let existsPredicate = NSPredicate(format: "exists == true") expectation(for: existsPredicate, evaluatedWith: element, handler: nil) waitForExpectations(timeout: waitSeconds) { (error) -> Void in if (error != nil) { let message = "Failed to find element existing after \(waitSeconds) seconds." XCTFail(message) } } } }
No comments :
Post a Comment