-
Notifications
You must be signed in to change notification settings - Fork 369
Query Language
The Calabash iOS Ruby API has 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".
The syntax for queries is based on UIScript, but is a new implementation with additional features added (and some removed).
UIScript gives 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 you can start your app from XCode using the -cal
scheme.
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>>"
}
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 expressions are strings. We've already seen one example "tabBarButton", this selects all view objects that are UITabBarButtons (or inherit from this class in Apple's UIKit).
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 one of the types: ClassName
, direction
, Filter
, Predicate
, DOM
or Visibility
. These types are described below. What is important to remember for now is: 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. Most of the time this direction is descendant
which means from a given set of views look "downwards" in the view hierarchy (this will be explained in more detail below).
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'
.
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).
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. The reason is that using index
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 an 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 a number, string or BOOL.
"button isEnabled:1"
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,
<
,>
, ... - Case or diacritic insensitive lookups, 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
Query can look into web views. This is described in a separate document:
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'")
Note: there is also a function query_all
which is deprecated.