Thursday, March 16, 2017

XCUI: How to identify elements on conditions

Identifying an element from the GUI is always the most important part of UI automation. Sometimes you can easily identify an element by name, but most of the time we identify the element by the element status. Today let's see how we can identify an element in XCUI automation.

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'
     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

Based on above structure, we have below requests to achieve.
  • 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.
To solve the problem, the first step is to understand how XCUI identifies elements and which functions we can use in the automation.

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')
As XCUI encapsulates the UI object to an XCUIElement, the NSPredicate can be used to define any conditions on XCUIElement properties as below
  • 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
The code for Q3 is just a sample for practicing on identify objects. The NSPredicate can be widely used in any place where you have an expectation on certain conditions. Here is another example for using NSPredicate in my automation. In this function, I use the NSPredicate to evaluate the condition whether we need to wait for an element to show up at the GUI.

 
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)
            }
        }
    }
}
 

Sunday, March 5, 2017

XCUI: What is XCUI?

XCUI stands for UI Testing in Xcode. It is an automation tool for UI testing introduced by Xcode. So what's UI testing? Why we need to automate the UI testing?

UI testing is testing with the user interface. It usually needs to verify the existence of the target element and then perform specific user actions on it. A same UI testing case may be repeated several times on different platforms to ensure the same behavior working across platforms. Doing these tests manually is dull and time-consuming. Needless to say there is always regression testing required for each release.

XCUI is the tool which helps to automate the UI testing on both iOS and OS X.

How XCUI works? 

You can automate the test cases either by recording or coding. It exists outside of the application and simulates the user actions without touching project code directly. Instead, it works like a proxy over the application to find the target element in the simulator and transfer the command into an interaction.

XCUI finds the target element through the system feature Accessibility. Only if the developer enables the accessibility on the element can XCUI identify the element on the GUI. That means though you are able to see the element by your eyes on the GUI, if the developer does not enable the accessibility on it, you will not be able to catch it through XCUI, neither can you perform any actions on that element in the automation.

XCUI helps you to automate the testing through the XCTest framework. This framework has been integrated with Xcode. It provides multi-classes for you to launch the application, find the object and perform actions. I will talk about this framework in detail in later chapters.

Prerequisite

To use XCUI, you need to pay attention to below prerequisite:
  • OS Versions:  above iOS 9 / OS X 10.11
  • Privacy Protection:
    • iOS : needs to be enabled for development and connected to a trusted host running Xcode. 
    • OS X : will prompt on the first run asking for granting access. Alternatively, you can enable the Xcode at Accessibility category under "Security & Privacy" from System Preference.
    •