Visual Studio App Center Test is the next generation of Xamarin Test Cloud! Read the blog post.

Calabash Query Syntax

PDF for offline use

Let us know how you feel about this

Translation Quality


0/250

This document serves as a reference for the syntax of the Calabash Query Language.

Query

The Calabash Android Ruby API and Calabash iOS Ruby API both have a method query that selects one or more visible view objects in the current screen in your app. The query method takes a string argument that describes which objects to "query".

With iOS the syntax for queries is based on UIScript, but it is a new implementation with additional features added (and some removed).

Query and UIScript give a nice "CSS-selector" like approach to finding view objects in your app screens. Here are some examples from our sample app. To try it out, we recommend starting the console/Code:

Android Example

krukow:~/tmp/android$ calabash-android console login.apk
irb(main):001:0> start_test_server_in_background
=> nil
irb(main):001:0> query("button")
[{"id"=>"login_button",
  "enabled"=>true,
  "contentDescription"=>nil,
  "text"=>"Login",
  "qualified_class"=>"android.widget.Button",
  "frame"=>{"y"=>322, "height"=>73, "width"=>84, "x"=>135},
  "description"=>"android.widget.Button@40584908",
  "class"=>"Button"},

 {"id"=>"login_button_slow",
  "enabled"=>true,
  "contentDescription"=>nil,
  "text"=>"Login (slow)",
  "qualified_class"=>"android.widget.Button",
  "frame"=>{"y"=>322, "height"=>73, "width"=>143, "x"=>234},
  "description"=>"android.widget.Button@40585390",
  "class"=>"Button"}]

As you can see, query returns an Array of results. Each result is a Hash representing a view object.

iOS Example

krukow:~/github/calabash-ios-example$ calabash-ios console
irb(main):001:0> query("tabBarButton").count
=> 4
irb(main):002:0> query("tabBarButton")[0]
=> {"class"=>"UITabBarButton",
    "frame"=>{"y"=>1, "width"=>76, "x"=>2, "height"=>48},
    "UIType"=>"UIControl",
    "description"=>"<UITabBarButton: 0x856a820; frame = (2 1; 76 48); opaque = NO; layer = <CALayer: 0x856d210>>"
    }

Again, as you can see, query returns an Array of results. Each result is a Hash representing a view object.

However, often we are interested in the accessibility labels/ids of the view objects (since the tests usually use those). The query command can actually take two or more arguments: a query and selectors to perform on the result:

irb(main):003:0> query "tabBarButton", :accessibilityLabel
=> ["First", "Second", "Third", "Fourth"]

This is so commonly used (and acesibiliatyLabel is so hard to spell) that there is another command for this:

irb(main):009:0> label "tabBarButton"
=> ["First", "Second", "Third", "Fourth"]

Query Syntax

Query expressions are strings. Queries match view objects in the current activity. We've already seen one example: "button" which selects buttons in the current activity.

In general, a query expression is a sequence of sub-expressions separated by space: "e0 e1 e2 ... en". Here, each of the sub-expressions has a type which are described below. What is important to remember for now is: with Android a query starts with all root views in the current activity, and will evaluate each sub-expression in turn (left to right). For iOS a query starts with all windows in the current screen, and will evaluate each sub-expression in turn (left to right).

The second important thing is that a query has a direction. By default this direction is descendant which means from a given set of views, we will look "downwards" in the view hierarchy (this will be explained in more detail below).

Query Syntax

Type Expression Platform
Class Name android.widget.Button
Class Name view:'MyClass'
Direction "linearLayout editText"
Direction "tableViewCell label"
Filtering "prop:val"
Predicate "label {text BEGINSWITH 'Cell 1'}"
DOM/WebView Support
  1. Query for an element with id:query("webView css:'#header'")
  2. Get all the HTML associated with the webview:query("webView css:'*'")
Touching touch("webView css:'a'")
Entering Text enter_text "webView css:input.login", "ruk"
EvaluatingJavaScript
js = 'document.body.innerHTML'
    query("webView", :stringByEvaluatingJavaScriptFromString => js)
Visibility query("all button")

ClassName

ClassName exists on both iOS and Android, but it has slightly different semantics on each platform.

Android

Selects view objects that have a particular class (or is a sub-class of that class). To specify a ClassName expression, you simply write the fully qualified class name of the class

android.widget.Button

This will pick out all views in the input which have class android.widget.Button or which inherit from android.widget.Button.

There is a simple form of specifying classes. For example, if you just write button this matches all views which have a class with simple name "button" (or "Button" as this is case in-sensitive). Remember that the simple name of a class is the last segment of the fully qualified class name, e.g., for android.widget.Button it is Button.

iOS

Selects view objects that have a particular class (or is a sub-class of that class). To specify a ClassName expression, you write

view:'MyClass'

This will pick out all views in the input which have class MyClass or which inherit from MyClass.

There is a short-hand form of specifying UIKit class-names. For example, if you just write label this is automatically translated to UILabel (which is a common UIKit class). In general, ClassName expressions that don't start with view: get re-written. For example: xyzAbc will get re-written to view:'UIXyzAbc'.

Direction

Android

There are four directions descendant, child, sibling, and parent. These determine the direction in which search proceeds.

Often, query expressions are a sequence of ClassName expressions. For example:

"linearLayout editText"

This means "first find all linearLayouts, then inside of those, find all the editText views". The key here is the word inside. This is determined by the query direction.

By default the direction is descendant, which intuitively means "search amongst all subviews (or sub-views of sub-views, etc)."

iOS

There are four directions descendant, child, parent and sibling. These determines the direction in which search proceeds.

Often, query expressions are a sequence of ClassName expressions. For example:

"tableViewCell label"

this means "first find all UITableViewCell views, then inside of those, find all the UILabel views". The key here is the word inside. This is determined by the query direction.

By default the direction is descendant, which intuitively means "search amongst all subviews (or sub-views of sub-views, etc)." But you can change the traversal direction. Here is an advanced example:

label marked:'Tears in Heaven' parent tableViewCell descendant tableViewCellReorderControl

This query finds a label 'Tears in Heaven', and then proceeds to find the tableViewCell that contains this label (i.e., moving in the parent instead of descendant direction). From the tableViewCell we move down and find the tableViewCellReorderControl.

Valid directions are descendant, parent, child and sibling. Both descendant and child looks for subviews inside a view. The difference is that descendant keep searching down in the sub-view's subviews, whereas child only looks down one level. The direction sibling searches for views that are "at the same level" as the present view (this is the same as: first find the immediate parent, then find all subviews except for the view itself).

Filtering

Android

Selects a sub-set of views that have certain properties (e.g., certain text or ids). The general form of a filter is:

prop:val

where prop is the name of a "property" to be filtered by, and val is a value of type string, integer or boolean. Strings are delimited by single-quotes, for example: 'Cell 2' is the string "Cell 2". You can also filter by booleans by using true and false.

The property names map to "getter"-methods of the view objects. In general prop:val will try to call:

  • prop()
  • getProp()
  • isProp()

in that order. If no method is found, the view object is not included. If one of the methods is found, it is called, and the result is compared to val. If they are equal values, the view is included.

Some names have special meaning. These are marked, index and id, and are described below.

marked
The simplest and most common filter is marked.
"button marked:'Login'"
This filters by id, contentDescription or text. In the example, first all button views are found. Then only those which have id, contentDescription or text equal to 'Login' are selected (i.e., "filtered" out).
index
Filters by index.
"button index:0"
returns the first button found. We recommend only using index in rare cases as using it leads to fragile tests that often break if the UI changes slightly.
id
A special construct that supports views by string id.
"button id:'login_button'"

In general, you can filter on any method which returns a simple result like an integer, string or boolean.

"button isEnabled:true"

iOS

Selects a sub-set of views that have certain properties (e.g., certain text or accessibility labels). The general form of a filter is:

prop:val

where prop is the name of an Objective-C selector to be filtered by, and val is a value of type string or integer. Strings are delimited by single-quotes, for example: 'Cell 2' is the string "Cell 2". You can also filter by booleans by using 1 for true/ YES and 0 for false/ NO.

Some names have special meaning. These are marked, index and indexPath, and their meaning is explained below:

marked
The simplest and most common filter is marked.
"label marked:'Cell 8'"
This filters by accessibility label or accessibility id. In the example, first all UILabel views are found. Then only those which have accessibilityLabel or accessibilityId equal to 'Cell 8' are selected (i.e., "filtered" out). To make your application more accessible, iOS will automatically assign accessibility labels to many objects unless you explicitly have assigned one. (You can always discover all the accessibility labels present using the console by typing: label("view").)
index
Filters by index:
"label index:0"
returns the first UILabel found. We recommend only using index in rare cases as using it leads to fragile tests that often break if the UI changes slightly.
indexPath
A special construct that supports selecting cells in UITableViews by index path. The general form is:
"tableViewCell indexPath:row,sec"
where row is a number describing the row of the cell, and sec is a number describing its section.

In general, you can filter on any selector which returns a simple result like an integer, string or boolean.

"button isEnabled:1"

Predicate

iOS and Android

Calabash supports some filters that are not present in UIScript. Particularly we support filtering by simple NSPredicates. For example searching for a string prefix:

"label {text BEGINSWITH 'Cell 1'}"

which would return the labels with text Cell 1 and Cell 10.

In general you use a NSPredicate by writing a filter: {selector OP val}, where selector is the name of an Objective-C selector to perform on the object, OP is operation, and val is a string or integer value.

Common operations

  • BEGINSWITH , prefix, e.g., "label {text BEGINSWITH 'Cell 1'}"
  • ENDSWITH , suffix, e.g., "label {text ENDSWITH '10'}"
  • LIKE , wildcard searches, e.g., "label {text LIKE 'C*ll'}"
  • CONTAINS , substring, e.g., "label {text CONTAINS 'ell'}"
  • Comparison, < , > , ...
  • Android supports case insensitive lookups, e.g., "label {text CONTAINS[c] 'cell'}"
  • iOS supports case insensitive lookups for multiple characters, e.g., "label {text CONTAINS[cd] 'cell'}"

To understand what additional options are available, consult the full syntax for NSPredicate documented by Apple: NSPredicate Syntax

DOM/WebView Support

iOS and Android

Calabash Android supports querying and acting on webview content.

To look into a webview you simply use the query function and syntax. The syntax for webviews is a bit irregular (and we will clean this up at some point), but there is quite good support.

Here are some examples:

  1. Query for an element with id, class or tagname
    query("webView css:'#header'")
    query("webView css:'.js-current-repository'")
    query("webView css:'a'")
    The string after css: can be any css selector.
  2. Get all the HTML associated with the webview:
    query("webView css:'*'")

Note: query will only return DOM nodes that are visible on the screeen! (They should be visible and their center should be within the webview viewport).

Touching

iOS and Android

As usual, anything you can query, you can touch (but the element must be visible to be found).

touch("webView css:'a'")

Entering text

There are three methods that can be used to enter text into a UITextField or a TextField/ TextArea:

    enter_text "webView css:input.login", "ruk"
  • keyboard_enter_text - this method will enter text into the view that has focus.
  • keyboard_enter_text "ruk"
  • keyboard_enter_char - this method will enter a single character into the view that has focus.

The set_text method has been deprecated. It has the same syntax as enter_text.

iOS

For iOS we recommend touching an input field to show the keyboard, and using keyboard_enter_char and keyboard_enter_text.

Evaluating JavaScript

iOS and Android

You can also evaluate JavaScript in a web view:

js = 'document.body.innerHTML'
query("webView", :stringByEvaluatingJavaScriptFromString => js)

Visibility

iOS and Android

By default Calabash will query only visible views (determined by a heuristic - not 100% bullet proof). If you want to change the behavior to query all views you simply prepend the modifier all.

query("all button")
query("all view marked:'something'")

Xamarin Workbook

If it's not already installed, install the Xamarin Workbooks app first. The workbook file should download automatically, but if it doesn't, just click to start the workbook download manually.