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

results matching ""

    No results matching ""