Improving Appium iOS automation performance by replacing XPaths
Are you using Appium for iOS mobile automation? Have you ever felt like your automation script is taking too long to run? If you are using a lot of XPaths in your code, you can reduce the time taken for your automation test significantly by replacing the XPaths with iOS Predicate and iOS class chain.
Out of the selectors available for iOS, accessibility_id is the fastest and the first preference goes with that. The second goes for iOS Predicate and then comes the iOS Class Chain. Here, we are aiming to replace the most time-consuming selector xpaths you have used in your code just because you need to get the selector based on one or more attribute values of the elements.
With last year’s appium-desktop 1.18.0 version, appium inspector is suggesting iOS Predicate and iOS class chain selectors along with the XPath. And with the same, you can evaluate and compare the performance improvement on using these selectors.
The above time difference is for a simulator and for real iOS devices, the time taken would be more than this and hence the improvement.
The selectors iOS Predicate and iOS Classchain are available under Appium’s MobileBy class as MobileBy.iOSNsPredicateString() and MobileBy.iOSClassChain(). Even though the latest appium inspector auto-suggests these selectors(in beta mode), we still might need to write it of our own as the suggested selectors may not be a fit for us always.
Creating iOS Predicate
Writing iOS Predicate locator is very simple as you just need to pass <attribute> == <attributeValue>. You can also nest multiple attributes using AND and OR. It also supports BEGINSWITH, ENDSWITH, CONTAINS, and != so as to ensure flexibility in creating the selectors.
Examples:
- MobileBy.iOSNsPredicateString(“ name ==’General’ ”)
- name ==’General’ AND type ==’XCUIElementTypeCell’
- name ==’General’ OR value==’General’
- name==’General’ AND type != ‘XCUIElementTypeStaticText’
- name BEGINSWITH ‘Priv’
- name ENDSWITH ‘ral’
- name CONTAINS ‘far’
In addition, you can also customize it more by making the BEGINSWITH, ENDSWITH, CONTAINS check case insensitive by using [c] and diacritic insensitive by using [d]. And, NOT can be used for eliminating a set of elements matching some criteria.
- name BEGINSWITH[c] ‘PRIV’
- name ENDSWITH[d] ‘ral’
- name CONTAINS[cd] ‘Far’
- type==”XCUIElementTypeCell” AND NOT (name CONTAINS “ral”)
Comparison can be done on the element width, height, x coordinate, and y coordinate with ==, !=, >, <, ≥, and ≤ but the same needs to be accessed as rect.width and rect.y.
- type==”XCUIElementTypeCell” AND rect.height >= 390
- type==”XCUIElementTypeCell” AND rect.x < 10
Multiple value check can be done using IN keyword. The visible and enabled attribute can also be checked against true and false or the equivalent 1 or 0.
- type IN {‘XCUIElementTypeCell’, ‘XCUIElemenTypeStaticText’} AND NOT (name IN {‘General’, ‘Privacy’})
- type IN {‘XCUIElementTypeCell’, ‘XCUIElemenTypeStaticText’} AND (visible == 1 OR enabled == true)
Creating iOS Classchain
As you might have already noticed, iOS predicate just allows creating selectors based on attributes of an element. But what if we need to refer to an element based on some other element or get nth element from a matching list of elements. The class chain allows this, not as extensive as Xpath but partially.
Similar to XPath, the class chain can be used as absolute and relative. The absolute class chain should start from the first element on the screen(mostly XCUIElementTypeWindow) and the relative to be created with **/ instead of the // in XPath.
- MobileBy.iOSClassChain(“ XCUIElementTypeWindow/XCUIElementTypeOther”)
- XCUIElementTypeWindow/**/XCUIElementTypeCell
- **/XCUIElementTypeCell/XCUIElementTypeStaticText
The attributes of the element can be used on the tags exactly similar to iOS predicate but should be within the tag as [`<predicate_string>`]. Any of the predicate formats described above can be used within iOS Class chain for specifying the element attribute values. The index can also be used similar to XPath.
- **/XCUIElementTypeCell[`name == ‘General’`]/**/XCUIElementTypeStaticText
- **/XCUIElementTypeStaticText[`value == “General” OR label == “General”`]
- **/XCUIElementTypeCell[3]
- **/XCUIElementTypeCell[1]/XCUIElementTypeButton[`visible == 1`]
In addition to this, if you need to specify the attributes of an element inside an element, then you can specify it as [$<predicate_string>$]. This will be checked against all descendants of the parent tag. This is useful when you have to replace an xpath where you are referring to a parent element based on its child/descendent element and while referring to siblings of an element.
- **/XCUIElementTypeCell[`visible == 1`][$type == XCUIElementTypeStaticText AND name == ‘General’$]/XCUIElementTypeButton — This will return you the button inside the Cell which is having a descendant element of type StaticText with the name General. This approach can be useful in case if you can’t directly specify the Cell or the button as it is missing a unique attribute
- **/XCUIElementTypeCell[$type == XCUIElementTypeStaticText AND name CONTAINS[c] ‘General’$]/**/XCUIElementTypeButton[1]
In order to get the last element from a list of matching elements, -1 can be used as the index position.
- **/XCUIElementTypeCell[-1]
- **/XCUIElementTypeCell[`visible==1`][-3] — Return the 3rd visible cell element from the bottom. Index attribute should be used only as the last one after specifying the attributes, if there are any.
In case if you don’t want to specify any specific tag name or want to get all matching tags, XCUIElementTypeAny or * can be used.
- **/XCUIElementTypeCell/**/XCUIElementTypeAny[`name == ‘General’`]
- **/XCUIElementTypeCell/**/*[`name == ‘General’`]
Even though predicate and class chain can’t replace all the XPaths as XPath do support many built-in functions which are not supported by XCUITest, most of the scenarios could be covered with them reducing XPath usage to a minimum or ZERO which will boost the overall performance of the test execution.
Happy Coding!