A walkthrough of the key aspects of MAF - internal mobile automation API over Appium providing access to multiple devices on iOS and Android at runtime is described. The article covers some generic information about the Appium automation service, a personal challenge in the production environment (alongside the adopted solution) and a step-by-step tutorial covering the API installation and configuration.
Appium is an open source test automation framework for mobile apps. Currently supported platforms are iOS, Android and Windows which make use of the WebDriver protocol. It is also cross-platform meaning that it allows you to write tests for the previously mentioned platforms based on the same API. It enables code reusability across platform test cases as long as the UI of the applications is similar. To this end, it leads to a well-structured architecture of your testing framework. Another advantage of Appium is the flexibility of choosing the programming language for developing test cases. The currently supported ones are Java, Ruby, Python, PHP, JavaScript, and C#.
The main downside of Appium is that it is not currently designed to support multithreaded sessions, meaning that you cannot run the same tests on multiple devices at the same time.
Currently benefitting from the advantage of cross-platform functionality, a feature was developed for running the test cases concurrently on different physical devices.
The API is based on an abstract architecture consisting of a Client class with three main composition classes: TouchGestures, ElementValidation and ElementLogging, where each interaction class is connected to a Driver class that links the logic to the device.
To implement this solution three main aspects must be covered:
Driver class - a manager responsible for handling different connections between devices and the actual clients
Common utilities specific to any mobile automation projects
Gestures - a class handling different screen interactions on both iOS and Android devices like tap a UI element on screen, double tap, swipe, pinch
Logger - a class responsible for getting UI-related information such as button id/accessibility identifier, element tree log or the location of an element on the device screen
This section covers some aspects related to the installation and configuration of the API, the key actions that MAF API provides, leading up to a short presentation about the implementation of the tests. The current version supports automation on two platforms (Android and iOS) and it can be executed on Mac OS.
Initial setup - Make sure the initial setup contains the following requirements
Requirements
Appium Version: 1.6.3
Mac OS: El Capitan
iPhone OS >= 10.0
Android SDK
Python 3
Xcode8
Appium installation - is required so please find the complete guide here
carthage
libimobiledevice: consider the latest released version
ios-deploy: used for installing iOS applications of a physical device
carthage: brew install carthage
brew uninstall libimobiledevice && brew install --HEAD ibimobiledevice
brew install ios-deploy
iOS related - the Xcode WebDriverAgent needs to be configured with some valid provisioning profiles. Since iOS 10, this project (application) acts as a bridge between your test cases and the actual application on the device.
Once the environment configuration is done, it is time to implement the tests specification. Usually, while using Appium, the first thing to do is to start the server through an external UI. To skip this step an API that handles these actions was developed.
appium_instance_ios = AppiumServer()
appium_instance_android = AppiumServer()
appium_instance_ios.start(port=9000) appium-python-client
appium_instance_android.start(port=8000)
This code snippet is the perfect example for showing how to make use of the same test on two different devices. The AppiumServer class represents the Python implementation of the Appium server API. Going further, an iOS instance and an Android instance is created, both of them calling the same start() method. The key point for running the test simultaneously on two devices is represented by the port parameter. In this case, for iOS, port:9000 is used, while, for Android, port :8000 is used.
The driver class acts as a factory used by the client class implementation in order to establish the connection with the designated Appium server instance. Note that there is a one-to-one relation between a client instance and the server. The driver class makes use of the appium-python-client library which provides a web-driver instance in order to transmit the commands to the Appium service. First, a client calls a web-driver instance; the driver class creates the connection and maps the instance to the unique identifier of the client. In this way when the client requests another connection, the driver class provides the already mapped instance. In brief, the driver class manages singleton connections to Appium services.
Each custom client class contains the implementations for the three abstract utility classes: Gestures
, Logger
, and Validator
corresponding to their specific driver, in this case, the appium-python-client
driver. Besides the mandatory abstract methods, the classes can be extended with some custom client specific functions, e.g. if the text value of a certain element is required, then a method called "get_text_from" will be defined in the Logger implementation, since it handles information about the UI elements. If it is necessary to validate a text attribute of an element, a method called "validate_text" will be created in the Validator
implementation since the method returns a True
or False
value.
The @test_case("test_ID")
decorator creates a dynamic link between the test data file and the test implementation using the test_ID as a matcher. Keeping test data in a separate file and structure relative to the unique test identifier provides an easy way to manage the changes in the long run. The data includes input values and expected reference data in order to validate/invalidate the test run. The data is provided through the mandatory method argument "kwargs" - a dictionary data structure. Access to this data is given via the 'test_data' key for the dictionary that the test is called with. Furthermore, an error handling structure is responsible for providing a crash-save flow.
Test case example
Considering everything mentioned before, here is a simple test flow as an example that performs some simple screen interactions.
@test_case("TEST_ID_01")
def my_first_test(**kwargs):
# kwargs['test_data'] holds all the test specific data
text_input = kwargs['test data']['input_text']
try:
# test case steps for android client
appium_service_android = AppiumServer()
appium_service_android.start(port=8000)
android_client = AndroidClient()
# test case steps for iOS client
appium_service_iOS = AppiumServer()
appium_service_iOS.start(port=9000)
iOS_client = iOSClient()
android_client.gestures.tap('input_box')
android_client.gestures.send_data_to(
'input_box', text_input)
android_client.gestures.tap('send_button')
# the data is send to the iOS application
# check the iOS app if received the data.
request_message = iOS_client.logger
.get_text_from('request_message')
# validate the step
assert iOS_client.validator
.check_value('request_text', text_input)
print("Test case ended with status: OK")
except Exception as ex:
print("Error executing test case: "
+ str(ex), file=sys.stderr)
finally:
appium_service_android.close()
appium_service_iOS.close()
Hours of work and research over Appium have proved that it is a reliable framework for client automation projects. The flexibility of choosing your favorite programming language, as well as the cross-platform functionality can make the continuous delivery process an easy job for any developer. In the Telenav context, now implemented, the currently described API saved hours of manual testing effort and also introduced the easiest solution for writing new tests cases.
Bibliography
by Andrei Oneț
by Raul Boldea
by Dan Sabadis