Calabash Android Tutorial


Calabash Android Tutorial

Ruby Installation

You should have Ruby (>= 1.8.7 version) installed on your machine. (If you do not have Ruby, http://www.ruby-lang.org/en/downloads/ is a good reference.)

Calabash-android

calabash-android is a ruby gem that helps in writing cucumber style automation scripts, to test the app on your emulator or device. Calabash-android can be installed on your machine by running the ‘gem’ command.

gem install calabash-android
calabash-android documentation has other requirements listed (ANDROID_HOME, android SDK & Apache ANT). Once you have the gem installed, you can generate the cucumber skeleton folder-structure as below.
MacBook-Pro-2:HelloWorldAutomation $ gem install calabash-android
MacBook-Pro-2:HelloWorldAutomation $ calabash-android gen
MacBook-Pro-2:HelloWorldAutomation $ ls -RF
features/
./features:
my_first.feature    step_definitions/    support/
./features/step_definitions:
calabash_steps.rb./features/support:
app_installation_hooks.rb    env.rb
app_life_cycle_hooks.rb        hooks.rb

Steps To SetUp Automation

1) Setup environment variables for APK_FILE & the AVD_NAME

export APK_FILE=~/HelloWorld/target/hello-world-1.0-SNAPSHOT.apk
export AVD_NAME=Gingerbread-LDPI

Note :

  • HelloWorld is the test application assumed here.
  • ‘Gingerbread-LDPI’ is the name of the AVD. This can be obtained from the android AVD manager.

2) Re-sign the APK file.

calabash-android resign $APK_FILE

3) Open calabash console. Calabash console is a nifty feature that allows to do real-time interaction with your app running on the emulator. Calabash console runs on interactive ruby shell (irb).

calabash-android console $APK_FILE

Note: When you run this command, a response similar to the one below should appear.

     MacBook-Pro-2:HelloWorldAutomation $ calabash-android console $APK_FILE
No test server found for this combination of app and calabash version. Recreating test server.
Done signing the test server. Moved it to test_servers/ce2fc62dff44091832619f2f96f981bc_0.4.3.apk
irb(main):001:0>

4) Start your android emulator
Note:
1) You should have ANDROID_HOME on your system path.
2) The ‘&’ starts the emulator as a background process.

emulator -avd $AVD_NAME &

5) From from calabash console, re-install the apps on the emulator. This will uninstall your app and reinstall it with the latest APK

irb(main):001:0> reinstall_apps
678 KB/s (14730 bytes in 0.021s)
2981 KB/s (528363 bytes in 0.173s)
nil

6) Start the calabash test server from background. This should start the android application on the emulator.

irb(main):002:0> start_test_server_in_background
nil
irb(main):003:0>

Note: At times you might encounter the following errors.

6.1) Instrumentation failed – This can be fixed by ensuring you have the right APK installed on the right version of Android (on emulator).

irb(main):002:0> start_test_server_in_background
android.util.AndroidException: INSTRUMENTATION_FAILED: com.example.test/sh.calaba.instrumentationbackend.CalabashInstrumentationTestRunner
RuntimeError: App did not start

6.2) App did not start – This can be fixed by adding INTERNET permission to your android application, as calabash talks to the test server on localhost. (Add <uses-permission android:name=”android.permission.INTERNET”/> in AndroidManifest.xml)

irb(main):002:0> start_test_server_in_background
RuntimeError: App did not start
from ~/.rvm/gems/ruby-1.9.3-p194/gems/calabash-android-0.4.3/lib/calabash-android/operations.rb:364:in `block in start_test_server_in_background’

–>

Interactive Calabash Console

From this point, calabash-console can be used interactively. A simple attempt would be to use the ‘query’ command from the console to retrieve data or perform operations like a click.

For ex., if the app has a TextView with id ‘hello_label’, the value of it can be retrieved as below.

irb(main):003:0> query(“textView id:’hello_label'”)
[
[0] {
“id” => “hello_label”,
“enabled” => true,
“contentDescription” => nil,
“class” => “android.widget.TextView”,
“text” => “Hello World, HomeActivity”,
“rect” => {
“center_y” => 59.5,
“center_x” => 160.0,
“height” => 19,
“y” => 50,
“width” => 320,
“x” => 0
},
“description” => “android.widget.TextView@40571770”
}
]

There is a lot more that can be done using query that is detailed in calabash-android-Query Syntax documentation.

Running The Automation

The test-suite consisting of all the feature files can be run from the automation root folder using the following command. (Note: You should have your AVD running at this point)

calabash-android run $APK_FILE

Running a specific scenario

To run a specific Scenario, you can tag your scenario with a meaningful name and run it as follows.

Feature file
@myscenario
Scenario: test scenario

Command 
calabash-android run $APK_FILE –tags @myscenario

Now let us consider a WordPress blog example for explaining calabash :

Cross-platform Acceptance Testing Best Practices

Test automation is programming – hence, well-established practices of programming apply to test automation. Ruby is object-oriented, and most Calabash tests should also follow good object-oriented design (e.g., principles of abstraction, separation of concerns, modularity, reuse…).
A well-established pattern in test engineering is the Page-Object Pattern (POP). While originating in web testing, the ideas of POP apply equally well to native mobile. In this short article, we’ll illustrate how to use the page object pattern to better architect test code and obtain better cross-platform code reuse.
We’ve created a sample project: X-Platform-Example using the open source WordPress app. If you want to follow along and try things out, go to the project page https://github.com/calabash/x-platform-example and follow install and run instructions. You can also choose to just read about the principles in this article and try to implement them in your own app.
LessPainful also provides both on-site and public training courses where we teach the use of Calabash as well as cross-platform and automated testing best practices. If you’re interested in a two-day, hands-on course, please contact us at contact@lesspainful.com.

Page Objects

page object is an object that represents a single screen (page) in your application. For mobile, “screen object” would possibly be a better word, but Page Object is an established term that we’ll stick with.
A page object should abstract a single screen in your application. It should expose methods to query the state and data of the screen as well as methods to take actions on the screen.
As a trivial example, a “login screen” consisting of username and password text fields and a “Login” button could expose a method login(user,pass) method that would abstract the details of entering username, password, touching the “Login” button, as well as ‘transitioning’ to another page (after login). A screen with a list of talks for a conference could expose atalks() method to return the visible talks and perhaps a details(talk) method to navigate to details for a particular talk.
The most obvious benefit of this is abstraction and reuse. If you have several steps needing to navigate to details, the code fordetails(talk) is reused. Also, callers of this method need not worry about the details (e.g. query and touch) or navigating to this screen.

Cross-platform Core Idea

Let’s go into more detail with this last example. Consider the following sketch of a class (don’t do it exactly like this – read on a bit):

class TalksScreen
def talks
# query all talks…
end def details(talk)
#touch talk…
end
end

Suppose you’re building the same app for iPhone and Android phones. Most likely the interface of the TalksScreen class makes complete sense on both platforms. This means that the calling code, which is usually in a step definition, is independent of a platform – hence it can be reused across platforms.
Working this way gets you complete reuse of Cucumber features as well as step definitions: the details of interacting with the screen is pushed to page object implementations.
The idea is that you provide an implementation of page objects for each platform you need to support (e.g. iPhone, iPad, Android phone, Android tablet, mobile web, desktop web,…).

Cross-platform in practice

So… The idea and design look good. The question now is how to implement this is practice. Here we describe the mechanics and below you’ll find an example extracted from X-Platform-Example.
There are a couple of ingredients we need.

  1. For each screen you want to automate, decide on an interface for a page object class (e.g. like TalksScreen above).
  2. Use only custom steps, and in each step definition only use page objects and their methods (no direct calls to Calabash iOS or Calabash Android APIs).
  3. For each supported platform, define a class with implementations of the page-object methods.
  4. Create a Cucumber profile (config/cucumber.yml). Define a profile for each platform (e.g. android and ios), and ensure that the profile only loads the page object classes for the platform.
  5. Rejoice and profit!

Let’s see what these steps look like in a concrete example on the X-Platform-Example.

Example

Step 1 – Interface

For the wordpress app, let’s focus on the Login/Add-WordPress Blog screen. This method has a single method: login(user)which takes a hash {:email => 'username', :password => 'somepass'} representing a user:

    def login(user)
      #…
    end 

    def assert_invalid_login_message()
      #…
    end

For this simple screen the interface consists of just these two methods.

Step 2 – Step definitions

We have a feature

Scenario: Invalid login to WordPress.com blog
  Given I am about to login
  When I enter invalid credentials
  Then I am presented with an error message to correct credentials

Below are step definitions that only use the page objects and no Calabash methods like touch, query…
For the following, assume we have also a Page Object class WelcomePage with a method wordpress_blog that transitions to the AddWordPressBlog screen:

## Invalid login ##
Given /^I am about to login$/ dowelcome = page(WelcomePage).await
@page = welcome.wordpress_blogendWhen /^I enter invalid credentials$/ do
@page = @page.login(USERS[:invalid])
endThen /^I am presented with an error message to correct credentials$/ do
@page.assert_invalid_login_message
screenshot_embed
end

The page method is a helper method in Calabash which initializes a page object from a class. The await method just returns the page object after waiting for the page to be loaded.
We store the page object in an instance variable in the cucumber world (@page) and use it in the subsequent steps.
Notice how the steps only use page-object methods. This feature, as well as the step definitions, can be 100% reused across platforms. Great!

Step 3 – Platform implementations

Now we need to give an implementation of the WordPressComPage on iOS and Android (and all other supported platforms). We put those implementations in separte directories features/ios/pages and features/android/pages and name them word_press_com_page_ios.rb and word_press_com_android.rb.
Here is the implementation for iOS. It uses an abstract Page Object Class defined in Calabash iOS (Calabash::IBase).

require 'calabash-cucumber/ibase'

class WordPressComPage < Calabash::IBase

def title
"Sign In"
end

def login(user)
touch("view marked:'Username'")
await_keyboard

keyboard_enter_text user[:email]

touch("view marked:'Password'")

keyboard_enter_text user[:password]
done

wait_for_elements_do_not_exist(["tableViewCell activityIndicatorView"],
:timeout => 120)

if element_exists(invalid_login_query)
self
else
page(MainPage).await
end
end

def assert_invalid_login_message
 check_element_exists(trait)
 check_element_exists(invalid_login_query)
end

def invalid_login_query
"label {text LIKE '*Sign in failed*'}"
end

end</pre>
</div>
And for Android:
<div>
<pre>class WordPressComPage < Calabash::ABase

def trait
"* id:'username'"
end

def await(opts={})
wait_for_elements_exist([trait])
self
end

def login(user)
query("* id:'username'",{:setText => user[:email]})
query("* id:'password'",{:setText => user[:password]})

performAction('scroll_down')

touch(login_button_query)

sleep(1)#Chance to show Dialog

wait_for(:timeout => 60, :timeout_message => "Timed out logging in") do
current_dialogs = query("DialogTitle",:text)

empty_dialog = current_dialogs.empty?
error_dialog = current_dialogs.include?("Error")
no_network_dialog = current_dialogs.include?("No network available")

empty_dialog or error_dialog or no_network_dialog
end

main_page = page(MainPage)

if main_page.current_page?
main_page.await
else
self
end
end

def invalid_login_query
login_button_query
end

def login_button_query
 "android.widget.Button marked:'Log In'"
end

def assert_invalid_login_message
 check_element_exists(invalid_login_query)
end
end

 

 

Step 4 – Conditional loading

The final missing part is conditionally loading page-object implementations based on which platform we’re running. This is done using Cucumber profiles. We create a file config/cucumber.yml


android: RESET_BETWEEN_SCENARIOS=1 PLATFORM=android -r features/support 
-r features/android/support -r features/android/helpers -r features/step_definitions -r features/android/pages/

ios: APP_BUNDLE_PATH=ios-source/3.3.1/build/Applications/WordPress.app 
RESET_BETWEEN_SCENARIOS=1 PLATFORM=ios -r features/support -r
 features/ios/support -r features/ios/helpers -r
 features/step_definitions -r features/ios/pages

using Cucumbers -r option to only load a subset of Ruby files. We can then execute the tests as specified here.

iOS:

 
cucumber -p ios features/login.feature

Android:

calabash-android run path_to.apk -p android features/login.feature

Comments 0

Your email address will not be published. Required fields are marked *

log in

Become a part of our community!

reset password

Back to
log in