When writing custom steps, you'll need to use the Ruby API to interact with your application. This document describes the API at a high level. If you want to see details you can look at the source code in the files at ruby-gem/lib/calabash-android. There are functions in the source code which aren't documented here. Those are way more likely to change (so be warned if you rely on those).
Calabash Android has a client-server architecture. The Calabash Ruby API is the client side which speaks HTTP with the test server that running on the device along with your app. To get an architectural overview of Calabash Android please read the blog posts:
AN OVERVIEW OF CALABASH ANDROID
Starts the test server and the app under test (AUT). If the app is already running it will be restarted.
Will reinstall both the test server and the AUT to be sure the newest versions are installed.
Query returns an array of its results. The query function gives powerful query capability from your test code. You can find views and other application objects, and make assertions about them or extract data from them.
The syntax for queries is really important, and described in a separate document: TODO: Query Syntax. (Until the documention for query gets written the best reference is this blog post)
Calabash Android tries to return results that carry useable information by default. For view objects this includes coordinates, class and contentdescription:
irb(main):002:0> query("button index:1")
=> [{"id"=>"save", "enabled"=>true, "contentDescription"=>nil, "class"=>"android.widget.Button", "text"=>"Save", "rect"=>{"center_y"=>724.0, "center_x"=>645.5, "height"=>64, "y"=>692, "width"=>71, "x"=>610}, "description"=>"android.widget.Button{4267b4a0 VFED..C. ........ 497,243-568,307 #7f070023 app:id/save}"}]
A view is represented as a ruby Hash (hash map) so you can look into the result
irb(main):003:0> query("button index:1").first.keys
=> ["id", "enabled", "contentDescription", "class", "text", "rect", "description"]
The *args
parameter lets you perform methods on the query result before it is returned to your Ruby script code (remember that the query is evaluated as Java code inside the app and the result is sent back to the Ruby code). The form *args
is Ruby-speak for a variable number of args. For example, if you have a button you can do
irb(main):005:0> query("button", "text")
=> ["Optional Settings", "Save", "Cancel", "Get a free blog at WordPress.com"]
This calls a 'getter' method "text" (that is text(), getText() or isText()) on each of the buttons in the view (it always returns an array). You can perform a sequence of methods:
irb(main):007:0> query("button", "text", "length")
=> [17, 4, 6, 32]
irb(main):008:0> query("button", "text", "toLowerCase")
=> ["optional settings", "save", "cancel", "get a free blog at wordpress.com"]
For methods with arguments you can use hashes. In Ruby 1.9 this has quite nice syntax:
irb(main):033:0> query("edittext index:1", setText:"1234")
=> ["<VOID>"]
On Ruby 1.8 you can't use key:val as literal Hash syntax so you must do:
irb(main):034:0> query("edittext index:1", :setText => "1234")
=> ["<VOID>"]
Behind the scenes the Java method setText
will be execute with the argument "12345"
on all view elements that were matched by the query.
Notice that the string <VOID>
is Calabash's way of returning from a Java method with return type void
.
For more complex methods you use Arrays of Hashes. Here is a complex Ruby 1.9 example:
TODO: Example
The element_exists
function returns true if an element exists matching query uiquery
.
The element_does_not_exist
function returns true if an element matching query uiquery
does not exist.
The function view_with_mark_exists(expected_mark)
is shorthand for
element_exists("* marked:'#{expected_mark}'")
Waits for a condition to occur. Takes a hash of options and a block to be called repeatedly. The options (which are described below) have the following defaults:
{
:timeout => 10, #maximum number of seconds to wait
:retry_frequency => 0.2, #wait this long before retrying the block
:post_timeout => 0.1, #wait this long after the block returns true
:timeout_message => "Timed out waiting...", #error message in case options[:timeout] is exceeded
:screenshot_on_error => true # take a screenshot in case of error
}
The timeout
argument should be a number indicating the maximal number of seconds you are willing to wait (after that amount of time the step will cause your test to fail). The :post_timeout
(0.1 by default) is an number of seconds to wait after the condition becomes true.
The &block
parameter is Ruby syntax for saying that this method takes a block of code. This block specifies the condition to wait for. The block should return true
when the the condition occurs.
The :retry_frequency
is a small sleep that is made between each call to the specified block. This describes how often Calabash should poll for the condition to be true.
Here is a simple example:
irb(main):030:0> wait_for(:timeout => 5) { query("button marked:'Save'").size > 0 }
This will check for the existence of a view matching: "button marked:'Save'". It will wait at most 5 seconds (failing if more than 5 seconds pass). It will issue the query repeatedly until it is found or 5 seconds pass.
A typical form uses element_exists
.
irb(main):031:0> wait_for(:timeout => 5) { element_exists("button marked:'Save'") }
In Ruby short blocks are written with braces (like: { element_exists("button marked:'Save'") }
), and more complicated blocks are written using do
-end
. For example:
wait_for(:timeout => 30) do
res = query("checkbox marked:'Geotag Posts'", 'checked')
res.first == true
end
A Ruby block always returns the value of its last expression (res.first == true
in this case).
Notes: Waiting for a condition to occur is superior to using the sleep
function. With sleep
you end up either specifying too long waits which slows the test down or you become sensitive to timing issues. Sometimes you do need sleep (to wait for animations to complete), but try to use waiting as much as possible.
A high-level waiting function. This captures the common practice of waiting for UI elements, i.e., combining wait_for
and element_exists
.
Takes an array of queries and waits for all of those queries to return results. Calls wait_for
supplying options
.
irb(main):008:0> wait_for_elements_exist( ["button marked:'Save'", "* marked:'Please sign in'"], :timeout => 2)
Similar to wait_for_elements_exist
, but waits for all of the elements to not exist.
Will fail the test with message msg
. Takes a screenshot.
Asserts that an element exists using the query function on the parameter query
.
The function check_view_with_mark_exists(expected_mark)
is shorthand for
check_element_exists("view marked:'#{expected_mark}'")
Touches a view found by performing the query uiquery
. It is recommended that uiquery
only produce one match, but the default is to just touch the first of the results if there are several.
The touch
method is one of the most used in Calabash. It is mostly used in its simplest form:
irb(main):037:0> touch("* marked:'Save'")
Which uses content descriptions, ids or texts. This form is so common that there is a short-hand for it: tap
:
irb(main):038:0> tap 'Save'
For flexibility you can also pass in a hash representation of a view and the the touch event will be calculated based on those values and no query will be executed. touch
will also accept a list of hashes in which case Calabash will touch the first one view in the list.
The following are all equivalent
touch("button index:0")
touch("button")
touch(query("button index:0"))
touch(query("button").first)
touch(query("button"))
Takes a screenshot of the app.
screenshot({:prefix => "/tmp", :name=>"my.png"})
If prefix and name are nil it will use default values (which is currently the line in the current feature).
Takes a screenshot of the app and embeds to cucumber reporters (e.g. html reports).
screenshot_embed({:prefix => "/tmp", :name=>"my.png", :label => "Mine"})
If prefix and name are nil it will use default values (which is currently the line in the current feature).
Label is the label used in the cucumber report output (equals to name if not specified).