Write Tests
Now lets write couple of Cucumber scenarios. Since it is simple english, the scenarios don't need extra explanation.
Cucumber Scenarios (ci_android.feature)
Feature: CI Android
All screens should launch fine
@non-ci
Scenario: Settings
Given I click about phone
Then the Android version is a number
@ci_smoke
Scenario: Open ci app and navigate to login screen
Given I open login screen on app
And I click add contact
Then I verify contact screen is displayed
@ci_add_contact
Scenario: Add contact
Given I open login screen on app
And I click add contact
Then I successfully add a contact
And verify that it was added
@ci_smoke
Scenario: Take screenshots
Given I open login screen on app
And I click add contact
Then take screenshot of contact form
hooks.rb
The values for appActivity and appPackage were retrieved using aapt command as described in the section Introspecting app
require './lib/capabilities'
include DesiredCapabilities
Before('@ci_smoke') do
caps = local_capabilities(app='app-debug.apk',appActivity='com.example.android.contactmanager.ContactManager',
appPackage='com.example.android.contactmanager')
@driver = Appium::Driver.new(caps)
Appium.promote_appium_methods AppiumWorld
puts "Execute anything before scenario/test case"
@driver.start_driver
end
Before('@ci_add_contact') do
caps = local_capabilities(app='app-debug.apk',appActivity='com.example.android.contactmanager.ContactManager',
appPackage='com.example.android.contactmanager')
@driver= Appium::Driver.new(caps)
Appium.promote_appium_methods AppiumWorld
puts "Execute anything before scenario/test case"
@driver.start_driver
end
Before('@non-ci') do
caps = local_capabilities(app='',appActivity='.Settings',appPackage='com.android.settings')
@driver = Appium::Driver.new(caps)
Appium.promote_appium_methods AppiumWorld
puts "Execute anything before scenario/test case"
@driver.start_driver
end
After do |scenario|
if scenario.failed?
@driver.screenshot("#{scenario.name}_failed.png")
end
@driver.driver_quit
puts "Execute anything after scenario/test case"
end
env.rb
As you can see, we create a custom World class, that is used in hooks.rb above.
require 'rspec/expectations'
require 'appium_lib'
require 'cucumber/ast'
require 'yaml'
require 'active_support/core_ext/hash'
# Create a custom World class so we don't pollute `Object` with Appium methods
class AppiumWorld
end
World do
AppiumWorld.new
end
$devices = YAML.load(File.open('./lib/config/devices.yaml'))
$emulators = YAML.load(File.open('./lib/config/emulators.yaml'))
Devices lab
My device lab looks as below. At this point, there are only 3 devices, however you can connect many it by purchasing USB extension cord.
We need to start appium server(s) on available network ports to talk to each of these devices. For initial, we will execute only on one device.
devices.yaml
We define the devices configuration in a yaml file as below. If you have a new device to be connected, copy paste the block and replace the values (serial, name, port) as needed. Ensure that you give a different port for each device.
Note: This port should be the same value that appium server starts on (default appium server port is 4723)
devices:
huawei-nexus_6p-84B5T15A17000142:
caps:
platformName : Android
deviceName : huawei-nexus_6p-84B5T15A17000142
app: app-debug.apk
appActivity : .Settings
appPackage : com.android.settings
appium_lib:
sauce_username:
sauce_access_key:
port: 4768
motorola-google-ZX1B222FCD:
caps:
platformName : Android
deviceName : motorola-google-ZX1B222FCD
app: app-debug.apk
appActivity : .Settings
appPackage : com.android.settings
appium_lib:
sauce_username:
sauce_access_key:
port: 4778
emulators.yaml
Similar to the devices, we define the emulators configuration. You should have already defined the avds by this time. How to add avds is described in AVD manager
emulators:
Nexus_5_API_23_x86:
caps:
platformName : Android
deviceName : IGNORED
avd : Nexus_5_API_23_x86
app: app-debug.apk
appActivity : .Settings
appPackage : com.android.settings
appium_lib:
sauce_username:
sauce_access_key:
Nexus_4_API_23_x86:
caps:
platformName : Android
deviceName : IGNORED
avd: Nexus_4_API_23
app: app.debug.apk
appActivity : .Settings
appPackage : com.android.settings
appium_lib:
sauce_username:
sauce_access_key:
Capabilities.rb
Returns the desired capabilities object and is mixed in hooks.rb
module DesiredCapabilities
def local_capabilities(app={},appActivity={},appPackage={})
if ENV['DEVICE'].nil? and ENV['EMULATOR'].nil?
puts "One of the targets DEVICE or EMULATOR has to be set"
puts "Allowed devices: #{$devices['devices'].keys}"
puts "Allowed emulators: #{$emulators['emulators'].keys}"
exit
end
if ENV['DEVICE'].nil? || ENV['DEVICE'].empty?
puts "Did not specify device target. Assuming emulator is set"
caps = $emulators['emulators']["#{ENV['EMULATOR']}"]
if app.nil? || app.empty?
caps['caps'] = caps['caps'].except('app')
else
caps['caps']['app'] = File.join(Dir.pwd,"features/support/resources", app)
end
caps['caps']['appActivity'] = appActivity
caps['caps']['appPackage'] = appPackage
end
if ENV['EMULATOR'].nil? || ENV['EMULATOR'].empty?
puts "Did not specify emulator target. Assuming device is set"
caps = $devices['devices']["#{ENV['DEVICE']}"]
if app.nil? || app.empty?
caps['caps'] = caps['caps'].except('app')
else
caps['caps']['app'] = File.join(Dir.pwd,"features/support/resources", app)
end
caps['caps']['appActivity'] = appActivity
caps['caps']['appPackage'] = appPackage
end
caps
end
end
ScreenHelper.rb (not used)
For page object framework enthusiasts, feel free to use this module to define page objects using page-factory. This pattern has been used extensively on my web framework tutorials
module ScreenHelper
def visit(page_class, &block)
on page_class, true, &block
end
def on(page_class, visit=false, &block)
page_class = class_from_string(page_class) if page_class.is_a? String
page = page_class.new @browser, visit
block.call page if block
page
end
def wait_for_ajax(timeout = 10)
timeout.times do
return true if browser.execute_script('return jQuery.active').to_i == 0
sleep(1)
end
raise Watir::Wait::TimeoutError, "Timeout of #{timeout} seconds exceeded on waiting for Ajax."
end
private
def class_from_string(str)
str.split('::').inject(Object) do |mod, class_name|
mod.const_get(class_name)
end
end
end