Finding Mobile Elements with Robust Appium Locator Strategies and Selectors

Lana Begunova
11 min readNov 26, 2023

Find mobile app elements in a stable and reliable manner with Appium to reduce test flakiness and increase robustness.

Before automating any UI elements, we have to find them first. In this post, let’s learn about locator strategies and selectors, which are the key components used in finding elements with Appium.

Finding Elements

An element not being found is one of the biggest indicators of instability or flakiness. Finding elements is a commonplace for problems to crop up. When we try to find an element, we bring together our assumption about an app’s state and its actual state. Expectations and actual results may conform or conflict with each other.

When Appium cannot find an element as directed, NoSuchElementException will be thrown in our code.

We want to avoid potential problems, such as:

  • Using non-unique selectors.
  • Trying to find elements by unreliable dynamic attributes.

Knowledge of the app and its design are essential. We must know what’s likely to change and what isn’t, as well as which elements have IDs or accessibility IDs.

driver.find_element(AppiumBy.ACCESSIBILITY_ID, "foobarbaz")

Locator Strategies

What do we mean by finding elements and accessibility ID? In Appium (as well as Selenium), actions can be taken on specific objects in the app UI. These objects (corresponding to elements in a webpage, hence the name of the find_element API command) must be found before it is possible to interact with them. There are different ways of finding elements.

Appium Locator Strategies

Class Name

Take a look at the example call below:

driver.find_element(AppiumBy.CLASS_NAME, "button")

In this example, AppiumBy.CLASS_NAME represents a so-called locator strategy called Class Name, and button represents a selector. This is the strategy used to find one or more elements. The result of this call — if all goes well — is an object of type WebElement, which comes with a rich set of interaction APIs we rely on for our testing.

Class Name is one of a number of locator strategies available in Appium, and refers to a platform-specific UI object class name, for example XCUIElementTypeButton or android.widget.Button. This locator strategy isn't optimal for testing a cross-platform app. We need to have a different set of code to find an iOS button or an Android button.

There’s another concern with this strategy. Often we deal with the case when there is more than one element of any given type in the UI hierarchy. Hence we might end up finding a button with this locator strategy, but it may not be the specific button we want. So the Class Name locator strategy is a suboptimal choice because it is platform-specific (leads to branched iOS and Android code), and too general (hard to uniquely identify an element with).

All Locator Strategies

What other options are there? Here is the full list:

Many of the locator strategies were carried over from Selenium, although not all are supported or even make sense in Appium (at least when automating a native app). Appium has also introduced a number of its own strategies, such as accessibility id, to reflect the fact (and take advantage of the fact) that we’re dealing with mobile app UIs and an entirely different automation stack.

XPath

Here is the summary of the XPath locator strategy, and why it should be avoided:

  • XPath is an attractive locator strategy because it is hierarchy-based, meaning it can find any element in the DOM or app hierarchy.
  • XPath can be easily used to find an element with a selector like //*[1]/*[1]/*[3]/*[2]/*[1]/*[1], especially when there’s no other way to find that element.
  • However, relying on such a selector is a poor idea because it will be invalidated by any change to the app hierarchy. This is because the app hierarchy is typically unstable and can change between runs or versions of the app.
  • With the XCUITest driver, XPath might be very slow. This is because every time we run an XPath query, the entire app hierarchy must be recursively walked and serialized into XML, which can take a lot of time, especially if our app has many elements.
  • Even if we avoid typical XPath pitfalls by using better, more restrictive selectors, we will still incur the cost of generating the XML document to begin with.
  • The solution is to not use XPath, or use it wisely. If we can get a direct handle on our element using the id, name, or accessibility id strategies, that is to be preferred above all else.
  • If there is no unique id or label associated with the element, and XPath has turned out to be too slow, consider using the -ios predicate string or -ios class chain locator strategies.

Cross-Platform

Accessibility ID

Instead, when possible, it’s best to use the accessibility id locator strategy, because it is (a) cross-platform, (b) unique, and (c) fast. As long as our Accessibility IDs are the same on both versions of our app, we can use the same line of Appium code to find the element, no matter the test platform.

It corresponds to the label given to elements by app developers for accessibility purposes. Both iOS and Android have the concept of an accessibility label, though on iOS it’s called Accessibility Id and on Android it’s called Content Description (or content-desc).

Developers are the ones who set the accessibility label as a string, hence they can make it a unique identifier. This would be the recommended way to go, unless it hinders the actual accessibility considerations of the app.

In the Appium Python client, finding elements by accessibility ID involves using the AppiumBy strategy:

el = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "foobarbaz")

Since testers don’t always have the ability to influence the app’s development, sometimes accessibility labels are not available, or are not unique. What other options might we use?

Platform-Specific

iOS-Specific Locator Strategies

Some iOS-specific locator strategies can be used as a substitute for XPath, because they are hierarchical query-based strategies. The most robust is the -ios class chain strategy, which allows us to use a lite version of something like XPath mixed together with iOS predicate format strings.

The benefit of this locator strategy is that it allows for complex queries, while in most cases being much speedier than XPath. The drawback, of course, is that it is platform-specific. It requires branching our code (or adding further distinctions to our object models). As an example of what we can do, check out the following command.

iOS Class Chain example

driver.find_element(AppiumBy.IOS_CLASS_CHAIN, "**/XCUIElementTypeCell[`name BEGINSWITH "C"`]/XCUIElementTypeButton[10]")

What we’re doing here is finding the 10th button which is a child of a table cell anywhere in the UI hierarchy which has a name beginning with the character “C”. That’s quite a query. Because of the more rigid form of Class Chain queries, the performance guarantees are better than those of XPath.

iOS Predicate String example

driver.find_element(AppiumBy.IOS_PREDICATE, "type == 'XCUIELementTypeButton' AND value BEGINSWITH[c] 'foo' AND visible == 1")

Android-Specific Locator Strategies

A similar trick is available for Android in a form of a special parser the Appium team implemented, which supports most of the UiSelector API. They make this parser available via the -android uiautomator locator strategy. The selectors should be strings which are valid bits of Java code beginning with new UiSelector(). Let's have a look at the following example.

Android UiAutomator example

driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, 'new UiSelector().className("ScrollView").getChildByText(new UiSelector().className("android.widget.TextView"), "Tabs")')

Once again, we make use of an AppiumBy strategy since this strategy is available only for Appium. What's going on here is that we have constructed a string which could be used as valid UiAutomator test code, but in fact will be parsed and interpreted by Appium when the command is sent. According to the semantics of the UiSelector API, we're saying that we want the first TextView element we find with the text "Tabs", which is also a child of the first ScrollView in the hierarchy. It's more cumbersome than XPath, but it can be used in similar ways, and again with a better performance profile in most cases.

As with the iOS Class Chain strategy, the main downside here is that selectors are going to be platform-specific. Moreover, the Appium team doesn’t support arbitrary Java and there are limits to what they can provide from the UiSelector API.

Selector Discovery

So far we’ve seen some good recommendations on which strategies to use to find elements reliably. But how do we know which selectors to use in conjunction with those strategies? How do we know what those selectors are?

Let’s learn how to decide on locator strategies and selectors for finding elements in our mobile apps. There are two basic ways to do this. Let’s explore one way first, even though the second way is much better and easier.

Getting app source

The first way to discover locators is to simply print out the page source and examine it ourselves, learning from the various element properties what we need to know in order to find a given element.

print(driver.page_source)

This is an important skill to learn, because we may need to fall back on it at some point. In certain circumstances or environments we might not have access to the tools that make our life easier in determining selectors. But if we can run an Appium session, we can get the page source.

An XML document representing the structure of the UI at the time we requested the page source.

This is an XML document representing the structure of the UI at the time we requested the page source. Every element that Appium knows about is represented as an XML node here. So if we don’t see an element in this document, then Appium will not be able to find it as a normal UI element.

Reviewing the XML output here is also useful for coming up with XPath selectors, because when we run an XPath query to find an element, it is this document which is consulted in order to do so. We can walk through this document and figure out what a good XPath query might be for a particular element in the document that we want to find with Appium.

We can also discover all the attributes an element contains and their values with code using the element.get_attribute() method.

This is useful, but not particularly pleasant or easy.

Appium Inspector

Let’s now try the second and preferred selector discovery strategy to use when we have the option — Appium Inspector. It lets us explore our app structure using a point-and-click interface.

Knowledge of the app is essential in order to do this correctly. How do we get that knowledge of our app? If we’re one of the app developers, we can simply have a look at the code, or maybe we remember that we gave a certain element a certain accessibility label. If we don’t have access to the code, or if we want a method that will show us exactly what Appium sees in our app, then it’s best to use Appium Inspector.

Appium Inspector is a GUI client for running Appium and inspecting apps. We can use it to launch inspector sessions with chosen capabilities. Inspector sessions show us a screenshot of our app, its UI hierarchy (as XML), and lots of metadata about any element we select. It looks like this:

Appium Inspector

One of the great things about the Inspector is that, when we click on an element in the hierarchy, it intelligently suggests locator strategies and selectors for us. In the image above, we can see that the top suggestion for the selected element is the accessibility id locator strategy, used in conjunction with the selector Login Screen.

Appium Inspector is released in two formats:

  1. As a desktop app for macOS, Windows, and Linux. We can get the most recent published version of this app at the Releases section of its repo. Simply grab the appropriate version for your OS and follow standard installation procedures.
  2. As a web application, hosted by Appium Pro. It currently has a known issue that the web version does not work on Safari. Make sure to start the Appium server with --allow-cors, to allow CORS with the web browser.

Both apps have the exact same set of features, so you might find that simply opening the web version is going to be easier and save you memory on disk space. Moreover, we can keep multiple tabs open.

Of course, there are various complex scenarios we encounter, but the Inspector is always a great place to start when figuring out what’s going on with our app hierarchy. It’s especially useful if we run into issues where we think an element should exist on a certain view. In that case, open the Inspector and manually look through the XML tree to see if the element actually exists. If it doesn’t, that means Appium (read: the underlying automation frameworks) can’t see it, and we need to ask our app developer why.

This concludes our discussion of finding elements reliably in Appium — or at least one aspect of it. Just because we can find an element with the correct locator strategy doesn’t mean it will always be there when we look. Stay tuned for upcoming posts about other aspects of making mobile tests fast, reliable and repeatable.

Happy testing! I welcome any comments and contributions to the subject. Connect with me on LinkedIn, X , GitHub, or Insta.

--

--

Lana Begunova

I am a QA Automation Engineer passionate about discovering new technologies and learning from it. The processes that connect people and tech spark my curiosity.