How to Identify Locators in Appium (With Examples)
Nowadays, automation is becoming integral to the overall quality of the products being developed. Especially for mobile applications, it's even more important to implement automation robustly.
As per Statista, the number of mobile users will likely be 7.26 billion by 2022 and will increase to 7.46 billion by 2025. This indicates how mobile applications will grow in the coming years, making mobile app testing extremely important.
There are many mobile testing frameworks available for performing mobile automation tests, but in this Appium testing tutorial, we will focus on the Appium automation tool to automate Android and iOS applications. To learn more about mobile automation, you can refer to my earlier blog on Appium with TestNG to perform Android automation testing and iOS automation testing.
In my experience, I have seen many Test engineers, whether newbies or experienced, using XPaths blindly, which later resulted in flaky tests. Thus, to make automation robust, it's important to understand which locator strategies are supported in Appium and what locator strategy is ideal for better automation performance.
By the end of this tutorial on using locators in Appium testing, you'll learn about:
- Common locator strategies are provided by Appium.
- Android-specific locator strategies when the Automation name is UIAutomator2.
- Android-specific locator strategies when the Automation name is Espresso.
- iOS-specific locator strategies.
The code snippets and examples used in this blog on locators in Appium are available on GitHub. You can clone the repository and follow along.
We will be using the Proverbial app for Android and iOS for most of the locator strategies, while we will use the API-demo Android app for the UIAutomator -> UiScrollable
locator and Espresso-specific Data Matcher
and View Matcher
locator strategies.
Strategies to Use Locators in Appium
In Appium, there are some locator strategies that you can use for Android and iOS platforms. These strategies are very straightforward and almost identical to Selenium locators, which we are already used to; thus, all these locators are very easy to use.
Let's look at all the common strategies for using locators in Appium:
ID Locator in Appium
The first common locator that Appium supports is ID.
The ID
is the resource-id
defined in the Android app, while in iOS, it's the name
property for the element.
While using Java, we can use this locator in Appium, as shown below:
import io.appium.java_client.MobileBy;
import org.openqa.selenium.By;
. . .
private final By colorById = MobileBy.id ("color");
Appium Inspector also suggests using. ID
if it is declared for the element. The same is shown below:
ID locator and Accessibility ID locator are very similar, making finding the element easy due to its uniqueness. If these IDs are set for the locator, then you should always prefer using this locator in Appium over other strategies to use locators in Appium. It is preferred to use an ID locator in Appium because it makes the element unique and results in finding the element much quicker.
Normally the format of the ID locator for the Android platform is < package-name >:id/< id-name >.
So while finding the element, you can use the whole text or only the < id-name >.
In the example screenshot, you can either use com.lambdatest.proverbial:id/geoLocation
or simply geoLocation
.
Accessibility ID Locator in Appium
This is also the most preferred strategy after the ID
locator in Appium. Accessibility ID for Android is the content-desc
property of the element, while in iOS, it's the accessibility-id
property. It is also one of the fastest-performing locator strategies.
While using Java, we can use this locator in Appium, as shown below:
import io.appium.java_client.MobileBy;
import org.openqa.selenium.By;
. . .
private final By colorByAccessibilityId = MobileBy.AccessibilityId ("color");
Appium Inspector will suggest using. Accessibility id
if it is defined for the element. The same is shown below:
If you find that in your application under test if there is any element that is not dynamic but still does not have the accessibility id
set, nor does it have any ID
set, then you should ask your development team to add those attributes. This will help you save so much time that you may have to build other locators in Appium like XPath, UISelector, etc.
Class Name Locator in Appium
Class name is another common strategy to identify the element in the application. The class is the full name of the XCUI
element for iOS, which starts with XCUIElementType
, and for Android, is the fully qualified name of the element, which normally starts with android.widget.*
when you select UIAutomator2
as the Automation name in the capability.
While using Java, you can use this locator in Appium, as shown below:
import io.appium.java_client.MobileBy;
import org.openqa.selenium.By;
. . .
private final By colorByClassName = MobileBy.className ("android.widget.Button");
You can find exactly what class name there is for any element in Appium Inspector, as shown below:
Normally, you won't need the use of the class name unless the element is a dynamic one. There is only one element for that particular class name. Use cases would be many where you can use the class name
locator, but it is highly suggested to use ID
or accessibility id
wherever possible.
XPath Locator in Appium
XPath scans the whole XML source tree of the application screen. It is the only locator in Appium that is not recommended by the Appium team out of all the locator strategies that Appium supports. The reason is that it has performance issues and is also the slowest-performing locator strategy. Normally this locator is still supported when in the rare scenario when all the other locator strategies do not work, we can use XPath to find the element.
While using Java, you can use the XPath locator in Appium, as shown below:
import io.appium.java_client.MobileBy;
import org.openqa.selenium.By;
. . .
private final By colorByXpath = MobileBy.xpath (".//android.widget.Button[@text='COLOR']");
Appium Inspector also helps you with pre-built XPath expressions, which you can use directly. The same is shown below:
It is preferred not to use this locator strategy because even if there is no ID
or accessibility id,
you can still use other platform-specific locator strategies (which we will look at in some time) to find the element.
Strategies to Use Android-Specific Locators in Appium
After commonly supported strategies to use locators in Appium, there are few platforms and automation type-specific locator strategies. Let's look into the Android-specific locators in Appium in detail.
UIAutomator2: UIAutomator Selector
The UIAutomator selector locator in Appium is one of the unique locator strategies where you have to create a Java statement and pass it as the locator text to the method. In this statement, you need to use the UiSelector
class to build the Java statement.
new UiSelector().<method_name>
Shown below is an example of using this locator in our Java tests:
import io.appium.java_client.MobileBy;
import org.openqa.selenium.By;
. . .
private final By colorByUiSelector = MobileBy.AndroidUIAutomator ("new UiSelector().text(\"COLOR\")");
Here, we are creating an instance for UiSelector
and inform the server that we need to find an element that has the text COLOR
.
Some of the frequently used methods in the UiSelector
class are as follows:
checked
: This method takes the expected value for the element to find an element that is checked. Mostly it is used with a checkbox element.className
: This method takes a class name string, which you can find from Appium Inspector.classNameMatches
: This method takes a regex expression to find an element based on its class name.description
: This method takes in the content description attribute for the element, which can be found in Appium InspectordescriptionContains
: This method takes in full or partial content description attributes for the element.descriptionMatches
: This method uses a regex expression to find the element based on its content description.descriptionStartsWith
: This method takes a starting part or full content description to find the element.enabled
: This method takes a boolean value to determine whether it's enabled.index
: This method takes the element's index from the list of elements.resourceId
: This method takes in the resource id equivalent to the ID locator.resourceIdMatches
: This method takes a regex expression for resource id to find the element based on its resource id.selected
: This method takes a boolean value to find the selected or unselected element.text
: This method takes in a text value to find an element by its text value.textContains
: This method takes a partial text to find an element by its partial text value.textMatches
: This method takes a regex value to find an element.textStartsWith
: This method takes in a text value for an element to find it based on the text starting value.
You can verify if your selector is working or not by executing the locator expression in the Search for element
window in the Appium Inspector.
In Appium Inspector, click on the magnifying glass icon and select locator strategy as UIAutomator Selector
, create your selector, and click on the Search
button. You will see the element once it's found on the next screen, as shown below:
You can also use the UiScrollable
class to make the element move into view along with the UiSelector
class.
Let's check out the UiScrollable
example where normal UiSelector
cannot find the element, not in the viewport, whereas UiScrollable
was able to find the element and scroll it into view.
import io.appium.java_client.MobileBy;
import org.openqa.selenium.By;
. . .
private final By colorByUiSelector = MobileBy.AndroidUIAutomator ("new UiScrollable(new UiSelector().scrollable(true)).scrollIntoView(new UiSelector().text(\"PathEffects\"))");
The above snippet is added as an example here, but the same is not there in our demo project.
In Appium Inspector, we can validate that the UiSelector was not able to find the element as shown below:
However, with UiScrollable, it was able to find and scroll to the element without any problems; the same can be seen below:
These locator strategies are more useful while finding dynamic elements, which normally is difficult to find using other commonly used locator strategies.
Espresso: Data Matcher
In Android, when you want to find any element, only the element visible in the viewport can be found. You cannot find an element at the bottom of the list with 100s of items unless you scroll through the list until you find that element or if you're using UiScrollable
with the UiSelector
locator strategy. This is possible when you use Espresso as the Automation type in Appium.
In the Appium Espresso driver, there is a specific locator strategy that you can use to find the element anywhere in the list, and it does not matter how many items are in that list. That locator strategy is Data Matcher
.
Data matcher will only work on elements that are part of a view, a subtype of AdapterViews
For example, ScrollView,
ListView,
and GridView.
If you were to use native Espresso directly to find that element, then you would write something like shown below:
onData (hasEntry ("title", "App")
.inAdapterView (withId ("android:id/list"))
.perform (click ());
In Appium, you can write the locator similar to this by using Hamcrest exposed methods and create a JSON string and pass it to the Data matcher locator method. Let's see how to do this in Java.
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import io.appium.java_client.MobileBy;
import org.openqa.selenium.By;
import org.openqa.selenium.json.Json;
. . .
private final By appItemDataMatcher = MobileBy.androidDataMatcher (new Json ().toJson (ImmutableMap.of (
"name", "hasEntry",
"args", ImmutableList.of ("title", "App")
)));
If you compare these locator values with the native Espresso expression we used earlier, we are breaking the hasEntry
call and create the JSON string.
Let's see how you can find the element using the data matcher locator in Appium Inspector.
Since we want to find elements from the list, first, we will select the main listview and get the value from its adapters
attribute, as shown in the screenshot above.
Let's see what value is there in the adapters attribute:
{
contentDescription=Access'ibility, title=Access'ibility, intent=Intent { cmp=io.appium.android.apis/.ApiDemos (has extras)
}
},
{
contentDescription=Accessibility, title=Accessibility, intent=Intent { cmp=io.appium.android.apis/.ApiDemos (has extras)
}
},
{
contentDescription=Animation, title=Animation, intent=Intent { cmp=io.appium.android.apis/.ApiDemos (has extras)
}
},
{
contentDescription=App, title=App, intent=Intent { cmp=io.appium.android.apis/.ApiDemos (has extras)
}
},
{
contentDescription=Content, title=Content, intent=Intent { cmp=io.appium.android.apis/.ApiDemos (has extras)
}
},
{
contentDescription=Graphics, title=Graphics, intent=Intent { cmp=io.appium.android.apis/.ApiDemos (has extras)
}
},
{
contentDescription=Media, title=Media, intent=Intent { cmp=io.appium.android.apis/.ApiDemos (has extras)
}
},
{
contentDescription=NFC, title=NFC, intent=Intent { cmp=io.appium.android.apis/.ApiDemos (has extras)
}
},
{
contentDescription=OS, title=OS, intent=Intent { cmp=io.appium.android.apis/.ApiDemos (has extras)
}
},
{
contentDescription=Preference, title=Preference, intent=Intent { cmp=io.appium.android.apis/.ApiDemos (has extras)
}
},
{
contentDescription=Text, title=Text, intent=Intent { cmp=io.appium.android.apis/.ApiDemos (has extras)
}
},
{
contentDescription=Views, title=Views, intent=Intent { cmp=io.appium.android.apis/.ApiDemos (has extras)
}
}
Notice the values we used in the Java example for the data matcher locator in Appium for the args
field. As mentioned above, we have used the title
property, which is part of the adapters
attribute. You can use any combination of key/value pairs from these attributes, for example, contentDescription
or intent
apart from title
.
Bonus Tip:
Appium Inspector behaves weirdly when you try to find the element by clicking on it. Instead, you need to find the element from the XML tree. Also, Appium Inspector won't help provide you with a pre-built locator as it does for other locators in Appium. To ascertain whether the data matcher locator you created works, you can check it by using the Search for element
button in the inspector, as shown below.
Espresso: View Matcher
View matcher locator strategy is very similar to the data matcher strategy, where we create a JSON string and pass it to the locator in Appium. The only difference is we use ViewMatchers class methods instead of Hamcrest class. Three fields are passed in the JSON string:
name
: This is the method's name.args
: This will have the parameter value for the method.class
: This will have the class name which contains the method. Mostly, it will beandroidx.test.espresso.matcher.ViewMatchers.
While using Java, we can use the View Matcher locator in Appium, as shown below.
import com.google.common.collect.ImmutableMap;
import io.appium.java_client.MobileBy;
import org.openqa.selenium.By;
import org.openqa.selenium.json.Json;
. . .
private final By animationItemViewMatcher = MobileBy.androidViewMatcher (new Json ().toJson (ImmutableMap.of (
"name", "withText",
"args", "Animation",
"class", "androidx.test.espresso.matcher.ViewMatchers"
)));
Strategies to Use iOS-Specific Locators in Appium
In iOS, there is only one Automation type, XCUITest, which supports the following strategies to use locators in Appium.
XCUITest: Predicate String
Predicate string locator strategy is similar to a SQL query where you query for a particular element using a combination of its attributes.
While using Java, you can use the predicate string locator in Appium, as shown below.
import io.appium.java_client.MobileBy;
import org.openqa.selenium.By;
. . .
private final By colorByPredicate = MobileBy.iOSNsPredicateString ("label == \"Colour\" AND name == \"color\"");
Appium Inspector also helps in providing a pre-built predicate string which you simply copy and use in your test. Below is the screenshot showing how Appium Inspector shows the pre-built locator in Appium.
While creating the predicate string, you can also use the following comparison expressions,
Basic Comparisons:
=
,==
: Left-hand expression is equal to right-hand expression.>=
,=>
: Left-hand expression is greater than and equal to right-hand expression.<=
,=<
: Left-hand expression is less than and equal to right-hand expression.>
: Left-hand expression is greater than right-hand expression.<
: Left-hand expression is less than right-hand expression.!=
,<>
: Left-hand expression is not equal to right-hand expression.< INPUT > BETWEEN { lower range, upper range }
: Left-hand expression value lies between the range mentioned in the right-hand expression, with both the values inclusive.
Basic Predicates:
AND
,&&
: Logicaland
will check both the connected expressions to betrue.
OR
,||
: Logicalor
will check if any of the connected expressions aretrue.
NOT
,!
: Negating an expression will negate the outcome of the expression with which it is used.
String Comparisons:
BEGINSWITH
: Checks if the attribute starts with the provided string.CONTAINS
: Checks if the attribute contains the provided string.ENDSWITH
: Checks if the attribute ends with the provided string.LIKE
: Checks if the attribute has the provided string containing wildcards like?
and*.
Here,?
will try to match only one char while*
will try to match 0 or more chars from that position.MATCHES
: Checks if the attribute matches the regex string.
XCUITest: Class Chain
Class chain locator in Appium is similar to XPath but more stable than XPath. With Class chains, you can combine predicate strings or directly reference child or descendent indexes to get a particular element.
While using Java, you can use the iOS class chain locator in Appium, as shown below.
import io.appium.java_client.MobileBy;
import org.openqa.selenium.By;
. . .
private final By colorByClassChain = MobileBy.iOSClassChain ("**/XCUIElementTypeButton[`label == \"Colour\"`]");
Appium Inspector also helps with providing a pre-built iOS class chain locator in Appium, as shown below,
Tips when creating a class chain expression:
- You can chain multiple classes in your expression, e.g.:
**/< parent-class >/< target-class >
- You can use indexes to get a specific element from the list of locators identified with the expression before the index, e.g.:
< parent-class >[1]/< target-class >[2]
, where the parent class's first instance will be used, and then the target class's 2nd instance will be found with this expression.- Indexes here start from 1 instead of the normal 0 indexes.
- Indexes can also be negative. For example,
-1
means the last element from the list of elements. Similarly,-2
means the second last item.
- You can also combine predicate string expressions with class chains like we use indexes, e.g.:
means it will find the target element where its name contains a particular text irrespective of its casing.< target-class >[
name CONTAINS[cd] "text"]
- Predicate strings should always be enclosed in ticks, as shown in the example.
- You can also combine index and predicate strings in the same expression at any level of the hierarchy, e.g.:
**/< parent-class >[
name BEGINSWITH "Some Text"][-1]/< target-class >[3]
If you like XPath more, this locator in Appium is the perfect replacement for XPath but is more stable and one of the best-performing locator strategies. Here also, it is suggested that you can use ID
or Accessibility ID
or ask the development team to add these IDs to the element; you should use them instead of the Class chain locator in Appium.
Demo: Using Locators in Appium
Until now, we have seen the code snippets. Now let us see the page object and the test we have in the demo project shared on GitHub. You can download and clone the same and follow along.
DriverManager Class
This class is used to create driver sessions for different Automation types to demonstrate different locators in Appium specific for that particular Automation type.
import static io.appium.java_client.remote.AndroidMobileCapabilityType.AVD;
import static io.appium.java_client.remote.AutomationName.ANDROID_UIAUTOMATOR2;
import static io.appium.java_client.remote.AutomationName.ESPRESSO;
import static io.appium.java_client.remote.AutomationName.IOS_XCUI_TEST;
import static io.appium.java_client.remote.MobileCapabilityType.APP;
import static io.appium.java_client.remote.MobileCapabilityType.AUTOMATION_NAME;
import static io.appium.java_client.remote.MobileCapabilityType.DEVICE_NAME;
import static io.appium.java_client.remote.MobileCapabilityType.FULL_RESET;
import static io.appium.java_client.remote.MobileCapabilityType.PLATFORM_VERSION;
import static java.lang.System.getenv;
import static java.text.MessageFormat.format;
import static org.openqa.selenium.remote.CapabilityType.PLATFORM_NAME;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import io.appium.java_client.MobileElement;
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.ios.IOSDriver;
import io.appium.java_client.service.local.AppiumDriverLocalService;
import io.appium.java_client.service.local.AppiumServiceBuilder;
import io.appium.java_client.service.local.flags.GeneralServerFlag;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.remote.DesiredCapabilities;
public class DriverManager {
private static final String ANDROID = "Android";
private static final String IOS = "iOS";
public static DriverManager getInstance () {
return new DriverManager ();
}
private AppiumDriverLocalService service;
public AndroidDriver<MobileElement> getAndroidEspressoCloudDriver () throws MalformedURLException {
return new AndroidDriver<> (getUrl (), getAndroidEspressoCloudOptions ());
}
public AndroidDriver<MobileElement> getAndroidEspressoDriver () {
startServer ();
return new AndroidDriver<> (this.service.getUrl (), getAndroidEspressoOptions ());
}
public AndroidDriver<MobileElement> getAndroidUiAutomatorDriver () throws MalformedURLException {
return new AndroidDriver<> (getUrl (), getAndroidUiAutomatorOptions ());
}
public IOSDriver<MobileElement> getIosDriver () throws MalformedURLException {
return new IOSDriver<> (getUrl (), getIosOptions ());
}
public void stopServer () {
if (this.service != null && this.service.isRunning ()) {
this.service.stop ();
}
}
private Capabilities getAndroidEspressoCloudOptions () {
final var capabilities = new DesiredCapabilities ();
capabilities.setCapability (PLATFORM_NAME, ANDROID);
capabilities.setCapability (PLATFORM_VERSION, "11");
capabilities.setCapability (DEVICE_NAME, "Pixel 5");
capabilities.setCapability (AUTOMATION_NAME, ESPRESSO);
capabilities.setCapability (APP, getenv ("LT_APP_ANDROID_2"));
setCloudCapabilities (capabilities, ANDROID);
return capabilities;
}
private Capabilities getAndroidEspressoOptions () {
final var capabilities = new DesiredCapabilities ();
capabilities.setCapability (PLATFORM_NAME, ANDROID);
capabilities.setCapability (PLATFORM_VERSION, "10");
capabilities.setCapability (DEVICE_NAME, "Pixel_5");
capabilities.setCapability (AUTOMATION_NAME, ESPRESSO);
capabilities.setCapability (APP, new File ("src/test/resources/apps/ApiDemos-debug.apk").getAbsolutePath ());
capabilities.setCapability (AVD, "Pixel_5");
capabilities.setCapability (FULL_RESET, true);
return capabilities;
}
private Capabilities getAndroidUiAutomatorOptions () {
final var capabilities = new DesiredCapabilities ();
capabilities.setCapability (PLATFORM_NAME, ANDROID);
capabilities.setCapability (PLATFORM_VERSION, "11");
capabilities.setCapability (DEVICE_NAME, "Pixel 5");
capabilities.setCapability (AUTOMATION_NAME, ANDROID_UIAUTOMATOR2);
capabilities.setCapability (APP, getenv ("LT_APP_ANDROID"));
setCloudCapabilities (capabilities, ANDROID);
return capabilities;
}
private Capabilities getIosOptions () {
final var capabilities = new DesiredCapabilities ();
capabilities.setCapability (PLATFORM_NAME, IOS);
capabilities.setCapability (PLATFORM_VERSION, "15");
capabilities.setCapability (DEVICE_NAME, "iPhone 13 Pro");
capabilities.setCapability (AUTOMATION_NAME, IOS_XCUI_TEST);
capabilities.setCapability (APP, getenv ("LT_APP_IOS"));
setCloudCapabilities (capabilities, IOS);
return capabilities;
}
private URL getUrl () throws MalformedURLException {
final var LT_KEY = System.getenv ("LT_ACCESS_KEY");
final var LT_USER = System.getenv ("LT_USERNAME");
return new URL (format ("https://{0}:{1}@mobile-hub.lambdatest.com/wd/hub", LT_USER, LT_KEY));
}
private void setCloudCapabilities (final DesiredCapabilities capabilities, final String platform) {
capabilities.setCapability ("project", "LambdaTest project");
capabilities.setCapability ("build",
format ("TestNG {0} Sample Build for {1}", platform, capabilities.getCapability (AUTOMATION_NAME)));
capabilities.setCapability ("name", format ("{0} Test Case", platform));
capabilities.setCapability ("visual", true);
capabilities.setCapability ("network", true);
capabilities.setCapability ("video", true);
capabilities.setCapability ("autoGrantPermissions", true);
capabilities.setCapability ("autoAcceptAlerts", true);
capabilities.setCapability ("isRealMobile", true);
capabilities.setCapability ("console", true);
}
private void startServer () {
final AppiumServiceBuilder builder = new AppiumServiceBuilder ();
builder.withIPAddress ("127.0.0.1")
.usingPort (4723)
.withAppiumJS (
new File ("/path/to/.nvm/versions/node/v16.16.0/lib/node_modules/appium/build/lib/main.js"))
.withLogFile (new File (System.getProperty ("user.dir") + "/logs/appium.log"))
.withArgument (GeneralServerFlag.LOG_LEVEL, "info")
.withArgument (GeneralServerFlag.SESSION_OVERRIDE);
this.service = AppiumDriverLocalService.buildService (builder);
this.service.start ();
}
}
Here, we have created a few different create-driver methods to create different driver instances for different automation types. Only for the Espresso driver, we have two methods, one for local execution and one for the cloud. This is because most cloud platform providers are not yet supporting the Espresso driver, due to which we will encounter errors like Data matcher locator strategy not implemented
or View matcher locator strategy not implemented
.
For cloud test execution, we are using the LambdaTest cloud platform, while for local execution, we start and stop the Appium server from this class directly.
Page Object
There is a one-page object for Android and one for iOS to demonstrate different locators in Appium. First, let's see the AndroidLocators
page object.
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import io.appium.java_client.MobileBy;
import lombok.Getter;
import org.openqa.selenium.By;
import org.openqa.selenium.json.Json;
@Getter
public class AndroidLocators {
private final By animationItemViewMatcher = MobileBy.androidViewMatcher (new Json ().toJson (ImmutableMap.of ("name", "withText", "args", "Animation", "class", "androidx.test.espresso.matcher.ViewMatchers")));
private final By appItemDataMatcher = MobileBy.androidDataMatcher (new Json ().toJson (ImmutableMap.of (
"name", "hasEntry",
"args", ImmutableList.of ("title", "App")
)));
private final By colorByClassName = MobileBy.className ("android.widget.Button");
private final By colorById = MobileBy.id ("color");
private final By colorByUiSelector = MobileBy.AndroidUIAutomator ("new UiSelector().text(\"COLOR\")");
private final By colorByXpath = MobileBy.xpath (".//android.widget.Button[@text='COLOR']");
}
The class seems very straightforward, where we have declared all the Android-specific locators. Now let's see the IOSLocator
page object.
import io.appium.java_client.MobileBy;
import lombok.Getter;
import org.openqa.selenium.By;
@Getter
public class IOSLocators {
// Demoed with iOS.
private final By colorByAccessibilityId = MobileBy.AccessibilityId ("color");
private final By colorByClassChain = MobileBy.iOSClassChain ("**/XCUIElementTypeButton[`label == \"Colour\"`]");
private final By colorByPredicate = MobileBy.iOSNsPredicateString ("label == \"Colour\" AND name == \"color\"");
}
Here also, we have declared all the iOS-specific locators in Appium.
Test Classes
BaseTest
Before writing the tests, we have a common BaseTest
class, which will be extended to all of our tests. Let's see what we have in the Base test.
import static java.lang.System.currentTimeMillis;
import static java.text.MessageFormat.format;
import com.github.wasiqb.appium.core.DriverManager;
import io.appium.java_client.AppiumDriver;
import io.appium.java_client.MobileElement;
import org.openqa.selenium.By;
import org.testng.annotations.AfterClass;
public class BaseTest {
protected static final DriverManager DRIVER_MANAGER = DriverManager.getInstance ();
protected AppiumDriver<MobileElement> driver;
@AfterClass (alwaysRun = true)
public void tearDownClass () {
this.driver.quit ();
DRIVER_MANAGER.stopServer ();
}
protected MobileElement getElement (final By by, final String locatorType) {
final long start = currentTimeMillis ();
try {
return this.driver.findElement (by);
} finally {
System.out.println (format ("Time taken by {0}: {1}ms", locatorType, currentTimeMillis () - start));
}
}
}
In this class, we have only two methods, one where we quit the driver session and close the local Appium server if it's running. At the same time, the other method is used to find the element using the locator strategy passed to this method and also prints the time it took to find the element so we can identify which locator strategy is the fastest and which one is the slowest.
LocatorsAndroidEspressoTest
This class will test the locators specific to the Espresso automation type. This test will execute on the local Appium server.
import static org.testng.Assert.assertTrue;
import com.github.wasiqb.appium.pages.AndroidLocators;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
public class LocatorsAndroidEspressoTest extends BaseTest {
private AndroidLocators page;
@BeforeClass (alwaysRun = true)
public void setupClass () {
this.driver = DRIVER_MANAGER.getAndroidEspressoDriver ();
this.page = new AndroidLocators ();
}
@Test
public void testByDataMatcher () {
assertTrue (getElement (this.page.getAppItemDataMatcher (), "Data Matcher").isDisplayed ());
}
@Test
public void testByViewMatcher () {
assertTrue (getElement (this.page.getAnimationItemViewMatcher (), "View Matcher").isDisplayed ());
}
}
We have one more similar class which runs on the LambdaTest cloud platform. But that class will fail, as mentioned earlier.
LocatorsAndroidUiAutomatorTest
In this test, we will test all the locator strategies specific to the UiAutomator2
Automation type, along with a few common locators in Appium.
import static org.testng.Assert.assertTrue;
import java.net.MalformedURLException;
import com.github.wasiqb.appium.pages.AndroidLocators;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
public class LocatorsAndroidUiAutomatorTest extends BaseTest {
private AndroidLocators page;
@BeforeClass (alwaysRun = true)
public void setupClass () throws MalformedURLException {
this.driver = DRIVER_MANAGER.getAndroidUiAutomatorDriver ();
this.page = new AndroidLocators ();
}
@Test
public void testByClassName () {
assertTrue (getElement (this.page.getColorByClassName (), "Class Name").isDisplayed ());
}
@Test
public void testById () {
assertTrue (getElement (this.page.getColorById (), "ID").isDisplayed ());
}
@Test
public void testByUiSelector () {
assertTrue (getElement (this.page.getColorByUiSelector (), "Ui Selector").isDisplayed ());
}
@Test
public void testByXpath () {
assertTrue (getElement (this.page.getColorByXpath (), "Xpath").isDisplayed ());
}
}
LocatorsIOSTest
In this class, we will test all the iOS-specific locator strategies for XCUITest
automation name.
import static org.testng.Assert.assertTrue;
import java.net.MalformedURLException;
import com.github.wasiqb.appium.pages.IOSLocators;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
public class LocatorsIOSTest extends BaseTest {
private IOSLocators page;
@BeforeClass (alwaysRun = true)
public void setupClass () throws MalformedURLException {
this.driver = DRIVER_MANAGER.getIosDriver ();
this.page = new IOSLocators ();
}
@Test
public void testByAccessibilityId () {
assertTrue (getElement (this.page.getColorByAccessibilityId (), "Accessibility Id").isDisplayed ());
}
@Test
public void testByClassChain () {
assertTrue (getElement (this.page.getColorByClassChain (), "Class Chain").isDisplayed ());
}
@Test
public void testByPredicate () {
assertTrue (getElement (this.page.getColorByPredicate (), "Predicate").isDisplayed ());
}
}
Test Output
When you execute all the tests which we saw until now, you'll see an output something like the one shown below.
Time taken by View Matcher: 36ms
Time taken by Data Matcher: 209ms
Time taken by Ui Selector: 230ms
Time taken by ID: 251ms
Time taken by Predicate: 266ms
Time taken by Class Chain: 272ms
Time taken by Class Name: 337ms
Time taken by Accessibility Id: 376ms
Time taken by Xpath: 517ms
Here, all the locators are mentioned, from the fastest executing ones to the slowest ones.
Conclusion
Now, you might have a fair idea of when to use which locator in Appium, depending on your needs. There are a few other locators in Appium which were skipped from this blog on locators in Appium because it was either a deprecated or experimental feature by Appium. I hope you liked this blog and learned something new from it.