Crossing the platform boundaries with Appium

Background

As part of the mobile development team here at MYOB we are working on two apps, namely PayDirect and OnTheGo. Both of these apps are available on Android and iOS. Despite being available on two different platforms, they are functionally identical.

The said apps are native apps which means even though from the user’s perspective they offer the same functionality we as a development team own two different code bases. We continue to enhance these apps and typically try to roll out a release every two weeks. As we kept churning out new features it was becoming harder and more time consuming to maintain two different test automation suites.

With that in mind, we started looking for a cross-platform test automation tool, something that would allow us to:

  1. Write tests in a ubiquitous language (BDD).
  2. Choose a scripting language of our choice.
  3. Select a cloud-based mobile testing service provider.
  4. Keep our test infrastructure up-to-date in terms of devices and OS versions.
  5. Test not only native but also hybrid and web apps (as part of our future roadmap).

After doing a spike on some of the options, we narrowed down to Appium. Appium allows you to run the same test against two different platforms. Our main reasons for selecting Appium were as follows:

  1. Appium’s unique selling proposition (USP) is that you don't need to introduce and compile your app with a third-party library.
  2. It takes advantage of Selenium’s JSON wire protocol, which allows you to write your tests in any language that works with selenium.
  3. We could run our tests on both emulators as well as real devices.
  4. It supports automation of native, web and hybrid platforms.
  5. Being sponsored by SauceLabs, it works seamlessly with their cloud offering and is compatible with most of the cloud-based testing service providers.
  6. The community support is pretty active and their roadmap looks exciting.

It's important to understand how Appium works behind the scenes, which allows it to offer so much flexibility in terms of choosing a programming language and running the same test against different platforms.

Appium is basically an HTTP server written using Node.js that exposes a REST API. It provides a common front-end API to different back-ends. It's almost the same as the Selenium server that gets HTTP requests from Selenium client libraries. A typical test command will go through the following sequence of steps:

  1. Commands are sent in JSON via HTTP
  2. Appium invokes platform-specific tools to execute the commands
  3. The client sends back a response to the Appium server
  4. The response is then logged to the console

Appium cleverly makes use of UIAutomator on Android and Instruments in iOS to control and execute commands. At the script level, it uses DesiredCapabilities the same way Selenium does for doing cross-browser automation.

We've also added a BDD layer to our Appium tests. In order to do this we are using Cucumber to write scenarios. The step definitions are written using Ruby and we keep the screen-level logic separate by making use of Page Objects.

Example - Scenario
Scenario: Login to the Paydirect app  
    Given the user has launched the app
    When the user has logged-in to MYOB
    Then the dashboard screen is displayed
Step Definitions
Given /^the user has launched the app$/ do  
  @driver.start
end

When /^the user has logged-in to MYOB$/ do  
  Screens.myob_login(@driver).login
end

Then /^the dashboard screen is displayed$/ do  
  expect(Screens.dashboard(@driver)).to be_visible
end  
Page – Android
module Screens  
  module Android

    class MyobLogin < Screens::MyobLogin

      def title
        "MYOB"
      end

      def set_email(name)
        email_field = find_element(:id, "login_view_username")
        email_field.type name
      end

      def set_password(password)
        password_field = find_element(:id, "login_view_password")
        password_field.type password
      end

    end

  end
end  
Page – iOS
module Screens  
  module IOS

    class MyobLogin < Screens::MyobLogin

      def title
        "Login"
      end

      def set_email(name)
        email_field = find_element(:name, "EmailField")
        email_field.type name
      end

      def set_password(password)
        password_field = find_element(:name, "PasswordField")
        password_field.type password
      end
    end
  end
end  

As demonstrated in these examples, there are a few differences at the page level. By keeping the screens separate for two platforms we are not affected if a new screen element is introduced in one of the apps.

We are gradually moving all our UI tests to the Appium suite and thus blurring the cross-platform boundaries with Appium.

Cover photo by Mike Rolls under the Creative Commons license