Mobile Software Test Automation: Advanced Queries

Last week we went over some wait helpers and how to implement them into your tests.  Part of utilizing these wait methods properly involves specifying an element, and then waiting for it to appear or disappear.  This works great if that element is something distinct, and assuming the app's developer(s) did a good job of labeling them.  But what do we do when that's not the case?

Query Syntax

We went over some of this at a high level back when we were writing Android page support, but I want to dive in a little deeper now.  When navigating the console, we already learned how to perform a search for every object in view, and for all objects of a specific type, such as a button.  

Filtering

Now, let's get a little more specific.  Let's say we want to find more than just all the buttons, but we want to find a specific button.  That's where filtering comes in.  It basically selects a subset of views described (button for example) with specified properties.  The filter follows the general form of: 


prop:val

"prop" is the name of a property to filter by, and "val" is the value, usually of type string, but can also be an integer or boolean value.  When using the "val" of type string, the value is denoted by single quotes.  Using the Android page object post from earlier, here is an example:


query("android.widget.Button id:'button9'")

First, notice we are searching for everything with a class of "android.widget.Button".  Then, we are expanding the expression to filter the results.  This is the prop:val argument in action.  "id" is the property we are searching for, and "button9" is the string value of that property that we want.  Also notice that the string value is surrounded by single quotes.  This will return all buttons that match this specific prop:val statement, which is only one in our example.

There are also some specially created properties, and those are marked and index.  The property marked will search through id, contentDescription, and text for Android, and accessibility label or accessibility id for iOS.  Using the example above, we could rewrite this using marked like this:  


query("android.widget.Button marked:'button9'")

Although not shorter, it does allow you to specify more generally, which allows you to abstract out your value into the step definition and feature file, and thus, creates a much cleaner code base.

The other commonly used property is index.  I don't recommend using this all the time, as it can lead to a fragile test with even the slightest UI change, but sometimes it's necessary.  It is written in the same format as the others, but without single quotes, since its value is an integer.  As an example, let's assume there are two buttons on the screen that have an id of 'button9", and we want to interact with the second one.  We would write that locator like this:


query("android.widget.Button marked:'button9' index:1")

There are a couple important things to notice here.  First, notice that the index for the second element has an index of 1.  That is because index starts with 0.  Likewise, an index of 3 would return the 4th element.  Also, notice that we added on to the existing expression.  You can add as many prop:val components to your argument as necessary to get the specific element you want.  The official documentation on this can be found here.

Predicate

Sometimes, we to search by something a little more vague.  This could be because you are passing in a name from another method, or maybe it's because the UI is changing and you want the test to be able to encompass the change.  Maybe you want to write a method that searches for a specific text box, and in order to re-use that method across multiple tests, you are only interested in part of the displayed text.  There are many other reasons you may want to do this, but those are just a few.  Generally, it follows the form:


{selector OP val}

The selector is the name of the selector to perform on an object.  Most commonly, this is the property "text".  val is the string or integer value.  As before, string values are wrapped in single quotes.  OP is the operation.  This can be any of the following:


BEGINSWITH

ENDSWITH

CONTAINS

LIKE

So, here is an example using each of the OP's:


query("android.widget.Button {text BEGINSWITH 'one hun'}")
query("android.widget.Button {text ENDSWITH 'undred'}")
query("android.widget.Button {text CONTAINS 'equal'}")
query("android.widget.Button {text LIKE 'equ*l'}")

The first example could return a button with the text 'one hundred' or 'one hundred thousand'.  The second example could return a button with text 'one hundred' or 'two hundred'.  The third example could return a button with the text 'equal' or 'equals'.  The fourth example could return a button with text 'equals', 'equal', or even 'sequel'.  I know this may not apply to our specific example app, but you should get the idea.

It's important to note that this type of search is case-sensitive.  Android and iOS also have a case insensitive lookup, as shown below:


query("android.widget.Button {text CONTAINS[c] 'equal'}") #Android
query("android.widget.Button {text CONTAINS[cd] 'equal'}") #iOS

For each platform, this would find a button with text 'equal', 'Equal', or even 'EquaL'.  If you need a little bit more information on predicates, Apple has pretty good documentation here.  Or, the official documentation for calabash implementation is here.

WebView/DOM support

Some apps, especially those that are created for a company, have at least some webview component.  One advantage I feel that the calabash framework has over it's competitors, is the ease of webview support.  To look into a webview, you use the same query function, and syntax basically identical to what we discussed above.  First, to get all the HTML associated with the webview, run the following in the console:


query("webView css:'*'")

From here, you can find the elements that you need. Writing the expression should look very familiar to the filtering and predicate sections above, respectively:


query("webView css:'#header'")

query("webView css:'div' {textContent LIKE 'Change My Password'}")

The val string can be any css selector, and just like before, you can add predicate syntax to your expression.  I have found that using the predicate operations LIKE and CONTAINS tend to fare much better here, as often times a webview text block has more in it than just text, but also has some formatting elements, which become too much to type.  Also, it's important to note that unlike traditional web automation, with mobile web, you will only be able to see elements that are visible on the screen.

Wrapping up

That finishes out this week's section on advanced queries.  And now that we should be able to find any specific element using the query function, I will go through a 2 part series on interacting with those elements.  Although the basic touch command is the same across both iOS and Android, the advanced touches, swipes, and other gestures do vary a bit depending on the platform.  So next week we will dive into advanced touches for Android, followed by the same on iOS, so stay tuned!