Getting started with Appium

Getting started with Appium

Anand Bagmar

Software Quality Evangelist @ Essence of Testing

Pooja Shah

Lead Automation Engineer @ MoEngage

Bruno Alassia

Sr. Software Engineer @ SauceLabs

Getting started with Appium

Getting started with Appium

What's Appium?

Appium is an open source test automation framework for use with native, hybrid and mobile web apps. It drives iOS, Android, Windows desktop apps and more using the WebDriver protocol.

Each platform is supported by one or more "drivers", which know how to automate that particular platform.

Why using Appium?

  1. You don't have to recompile your app or modify it in any way, due to use of standard automation APIs on all platforms.

  2. You can write tests with your favorite dev tools using any WebDriver-compatible language such as Java, Objective-C, JavaScript (Node), PHP, Python, Ruby, C#, Clojure, or Perl with the Selenium WebDriver API and language-specific client libraries.

  3. You can use any testing framework.

  4. Appium has built-in mobile web and hybrid app support. Within the same script you can switch seamlessly between native app automation and webview automation, all using the WebDriver model that's already the standard for web automation.

Getting started with Appium

Getting started with Appium

Let's see it running

Getting started with Appium

Appium architecture overview

Appium client

Appium server

Device

Getting started with Appium

How Appium works?

  1. You write your tests using one of Appium client libraries

  2. Your tests calls the Webdriver API
  3. The Webdriver sends the request in form of JSon via http request to the Appium server.
  4. The Appium server, under the hood invokes vendor specific mechanisms to execute the test commands
  5. The client (devices or emulators) responds back to Appium server
  6. Appium server logs the results in console.

Getting started with Appium

1. the client talks to Appium server to start a session on an device

2. Appium instanciate the driver that will manage the communication with the device

3. The driver prepares the device, make sure it can talk to the device and once it's ready tells the appium server

4. Appium server replies back to the client that the session is ready and that it can receive the commands

And then we go thru the same process for each command we have in our test

Getting started with Appium

What can be automated with appium?

Appium gives you the possibility to automate and run tests over mobile web and native application on Android and iOS, desktop applications on Windows and MacOS, you.tv and more.

Getting started with Appium

Getting started with Appium

Installing Appium

In order to run appium you need to have node and npm installed. Newest Appium releases required you to to have node 8.x.

 

Open a terminal and install it via npm

 

 

Running

Once appium is installed let's start the appium server

 

npm install -g appium
appium

Getting started with Appium

Installing Node

The simplest and reliable way to install node is to use nvm: https://github.com/creationix/nvm

 

Once nvm is installed proceed to installed node 8.9.0

 

and set it by default

 

nvm install v8.9.0
nvm alias default v8.9.0

Getting started with Appium

Android Requirements

  • Android SDK API >= 19                                                                               
  • Java 8+

Getting familiar with Android tools:

sdkmanager: is a command line tool that allows you to view, install, update, and uninstall packages for the Android SDK

 

avdmanager: is a command line tool that allows you to create and manage Android Virtual Devices (AVDs) from the command line

 

 

emulator: tool to start and interact with the android emulators

 

sdkmanager --list
sdkmanager "system-images;android-26;google_apis;x86_64"
avdmanager list avd
avdmanager create avd -n "appium-emulator" -d "Nexus 5"\
    -k "system-images;android-26;google_apis;x86_64"\
    --tag "google_apis"
emulator -avd appium-emulator -verbose

Getting started with Appium

Running my first Appium test in NodeJS

Let's start cloning the workshop repository https://github.com/vrunoa/getting-started-with-appium and follow the README instructions to prepare the environment.

 

Once you have the environment set let's run our first appium test:

 

This will run a simple test against the Android calculator and verify it can interact with the number buttons and verify the result of a multiplication is correct.

npm run my-first-appium-test
Android Workshop calculator tests
Session ID d36f36c0-e6fa-4e02-83c8-d9444f22af3e
    ✓ Test calculator can multiply (5196ms)


  1 passing (11s)

Getting started with Appium

Preparing the repository environment

 

npm install

Getting started with Appium

Understanding the test structure

At the top of our test we have the Web driver client, this is the module that's going to talk with the Appium server. Next we have chai which is the assertion library for our test.

 

 

The next step would be to prepare the capabilities for our test

const wd = require('wd');
const chai = require('chai');
const expect = chai.expect
let caps = {
    'appPackage': 'com.android.calculator2',
    'appActivity': 'com.android.calculator2.Calculator',
    'appWaitActivity': 'com.android.calculator2.Calculator',
    'deviceName': 'Android GoogleApi Emulator',
    'platformName': 'Android',
    'platformVersion': '8.0',
    'automationName': 'uiautomator2'
}
let driver, res;

and define the endpoint where appium is running.

Getting started with Appium

The capabilities

Capabilities are the way to tell Appium which kind of session we are interested in.

https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/caps.md

 

 

 

Capability Description Values
platformName Which mobile OS platform to use Android, iOS
platformVersion Mobile OS version 12.0, 7.1
deviceName The kind of mobile device or emulator to use ​iPhone Simulator, iPad Simulator,iPhone Retina 4-inch, Android Emulator...
automationName Which automation engine to use Appium(default), Selendroid, XCuiTest
browserName Name of mobile web browser to automate. Should be an empty string if automating an app instead 'Safari' for iOS and 'Chrome', 'Chromium', or 'Browser' for Android
orientation (Sim/Emu-only) start in a certain orientation LANDSCAPE or PORTRAIT
app Path to application to be installed in the device

Getting started with Appium

For Android we have 3 options to start a test:

  • Start a browser test by setting browserName capability
  • Start an application test by setting the app capability
  • Start an application test by setting appPackage and appActivity

Browser and App testing are either exclusive, you can only start one type of session.

"browserName": "chome"

"appPackage": "com.my.app"

"appActivity": ".MainActivity"

"app": "<path_to_apk>"

Getting started with Appium

Using app capability

For Android tests the app capability is the local path or an URL where Appium can download the android APK.

APK stands for Android Package Kit (also Android Application Package) and is the file format that Android uses to distribute and install apps. It contains all the elements that an app needs to install correctly on your device.

 

 

 

 

Installs the application

on the Android device

"app": "<path_to_apk>"

Getting started with Appium

Our test case

For our first test we're using an android system application, in order to start the application we need to know it's package name.

Here's a trick to get any Android app packages/activity. Open the app you want to use for testing manually in the emulator and then in your terminal run:

 

 

 

adb shell dumpsys window windows | grep -E 'mCurrentFocus'
mCurrentFocus=Window{c640c92 u0 com.android.calculator2/com.android.calculator2.Calculator}

This will output the current application running

let caps = {
    'appPackage': 'com.android.calculator2',
    'appActivity': 'com.android.calculator2.Calculator',
    'appWaitActivity': 'com.android.calculator2.Calculator',
    'deviceName': 'Android GoogleApi Emulator',
    'platformName': 'Android',
    'platformVersion': '8.0'
}

Getting started with Appium

Server endpoint

As we have introduced before, Appium is a server so we need to set up the endpoint where we are going to be sending commands.

 

By default Appium will start in port 4723 but this can be changed when starting appium server. You can see this options running appium --help from the command-line.

let endpoint = 'http://localhost:4723/wd/hub'
appium --help
...
Optional arguments:
  -h, --help            Show this help message and exit.
  -v, --version         Show program's version number and exit.
  ...
  -p PORT, --port PORT  port to listen on
appium -p 4443
[Appium] Welcome to Appium v1.12.1
[Appium] Non-default server args:
[Appium]   port: 4443
[Appium] Appium REST http interface listener started on 0.0.0.0:4443

Getting started with Appium

Automation name

In our capabilities we have defined the automationName as uiautomator2. This tell the appium server to use uiautomator2 driver for the tests.

 

 

let caps = {
    'appPackage': 'com.android.calculator2',
    'appActivity': 'com.android.calculator2.Calculator',
    'appWaitActivity': 'com.android.calculator2.Calculator',
    'deviceName': 'Android GoogleApi Emulator',
    'platformName': 'Android',
    'platformVersion': '8.0',
    'automationName': 'uiautomator2'
}
let driver, res;

There are a number of such drivers that give you access to different kinds of automation technologies, and each come with their own particular setup requirements.

Getting started with Appium

Once we have the capabilities and appium endpoint set, we'll define that the first step of our test suite would be to create a new driver instance and once the the tests finished we tell the driver to quit.

describe('Android Workshop calculator tests', async () => {
    before(async () => {
        driver = await wd.promiseChainRemote(endpoint)
        res = await driver.init(caps)
        console.log(`Session ID ${res[0]}`);
    });
    after(async () => {
        await driver.quit()
    });
    it('Test calculator can multiply', async () => {
        let el = await driver.elementById('com.android.calculator2:id/digit_2');
        await el.click();
        el = await driver.elementById('com.android.calculator2:id/op_mul');
        await el.click();
        el = await driver.elementById('com.android.calculator2:id/digit_3');
        await el.click();
        el = await driver.elementById('com.android.calculator2:id/eq');
        await el.click()
        el = await driver.elementById('com.android.calculator2:id/result');
        expect(await el.text()).to.equal('6');
    });
});

Getting started with Appium

it('Test calculator can multiply', async () => {
    let el = await driver.elementById('com.android.calculator2:id/digit_2');
    await el.click();
    el = await driver.elementById('com.android.calculator2:id/op_mul');
    await el.click();
    el = await driver.elementById('com.android.calculator2:id/digit_3');
    await el.click();
    el = await driver.elementById('com.android.calculator2:id/eq');
    await el.click()
    el = await driver.elementById('com.android.calculator2:id/result');
    expect(await el.text()).to.equal('6');
});

Test structure

In our test, we'll interact with the application UI by sending commands to find specific elements and click on them.

 

 

 

 

 

 

http://appium.io/docs/en/commands/element/find-element/​http://appium.io/docs/en/commands/element/actions/click/

Getting started with Appium

it('Test calculator can multiply', async () => {
    let el = await driver.elementById('com.android.calculator2:id/digit_2');
    await el.click();
    el = await driver.elementById('com.android.calculator2:id/op_mul');
    await el.click();
    el = await driver.elementById('com.android.calculator2:id/digit_3');
    await el.click();
    el = await driver.elementById('com.android.calculator2:id/eq');
    await el.click()
    el = await driver.elementById('com.android.calculator2:id/result');
    expect(await el.text()).to.equal('6');
});
<?xml version="1.0" encoding="UTF-8"?>
<hierarchy rotation="0">
<android.widget.FrameLayout index="0" text="" ... resource-id="" instance="0">...
<android.widget.Button index="7" text="2" 
class="android.widget.Button" 
package="com.android.calculator2" 
content-desc="" 
checkable="false" checked="false" clickable="true" 
enabled="true" focusable="true" focused="false" 
scrollable="false" long-clickable="false" 
password="false" selected="false" bounds="[260,1214][496,1465]" 
resource-id="com.android.calculator2:id/digit_2" 
instance="7"/>
...
</android.widget.FrameLayout>
</hierarchy>

How appium finds the elements?

 view hierarchy

Getting started with Appium

<?xml version="1.0" encoding="UTF-8"?>
<hierarchy rotation="0">
<android.widget.FrameLayout index="0" text="" ... resource-id="" instance="0">...
<android.widget.Button index="7" text="2" 
class="android.widget.Button" 
package="com.android.calculator2" 
content-desc="" 
checkable="false" checked="false" clickable="true" 
enabled="true" focusable="true" focused="false" 
scrollable="false" long-clickable="false" 
password="false" selected="false" bounds="[260,1214][496,1465]" 
resource-id="com.android.calculator2:id/digit_2" 
instance="7"/>
...
</android.widget.FrameLayout>
</hierarchy>

How to get the view hierachy?

 view hierarchy response

await driver.source(); 

Looking at the view hierarchy we can research how to find and interact with elements, the attributes on each elememt can help us write our tests.

Getting started with Appium

Using Appium Desktop to help you write test

As an alternative to inspect the view hierarchy reading source code we can use Appium Desktop to inspect the views easily. Same as an automated test, Appium Desktop will start a long running session so we can inspect and interacts with elements manually.

Getting started with Appium

Getting started with Appium

Finding elements

 

 

 

 

 

 

 

 

.elementById
.elementByClassName
.elementByName
.elementByTagName

Interacting with elements

 

 

 

 

 

 

 

 

.click
.sendKeys
.clear
.getAttribute
.tap
.setValue
.getValue
.text
.isDisplayed

Getting started with Appium

More useful commands

 

 

 

 

 

 

 

 

.back
.getPageSource
.title()
.getWindowSize
.hideKeyboard

Getting started with Appium

Cycle of a command in Appium server logs

 

 

 

 

 

 

 

 

[HTTP] --> POST /wd/hub/session/67734aa5-1567-4393-9e1b-701a886d053f/element
[HTTP] {"using":"id","value":"com.android.calculator2:id/digit_2"}
[debug] [MJSONWP (67734aa5)] Calling AppiumDriver.findElement() with args: ["id","com.android.calculator2:id/digit_2","67734aa5-1567-4393-9e1b-701a886d053f"]
[debug] [BaseDriver] Valid locator strategies for this request: xpath, id, class name, accessibility id, -android uiautomator
[debug] [BaseDriver] Waiting up to 0 ms for condition
[debug] [AndroidBootstrap] Sending command to android: {"cmd":"action","action":"find","params":{"strategy":"id","selector":"com.android.calculator2:id/digit_2","context":"","multiple":false}}
[debug] [AndroidBootstrap] [BOOTSTRAP LOG] [debug] Got data from client: {"cmd":"action","action":"find","params":{"strategy":"id","selector":"com.android.calculator2:id/digit_2","context":"","multiple":false}}
[debug] [AndroidBootstrap] [BOOTSTRAP LOG] [debug] Got command of type ACTION
[debug] [AndroidBootstrap] [BOOTSTRAP LOG] [debug] Got command action: find
[debug] [AndroidBootstrap] [BOOTSTRAP LOG] [debug] Finding 'com.android.calculator2:id/digit_2' using 'ID' with the contextId: '' multiple: false
[debug] [AndroidBootstrap] [BOOTSTRAP LOG] [debug] Using: UiSelector[INSTANCE=0, RESOURCE_ID=com.android.calculator2:id/digit_2]
[debug] [AndroidBootstrap] Received command result from bootstrap
[debug] [AndroidBootstrap] [BOOTSTRAP LOG] [debug] Returning result: {"status":0,"value":{"ELEMENT":"1"}}
[debug] [MJSONWP (67734aa5)] Responding to client with driver.findElement() result: {"element-6066-11e4-a52e-4f735466cecf":"1","ELEMENT":"1"}
[HTTP] <-- POST /wd/hub/session/67734aa5-1567-4393-9e1b-701a886d053f/element 200 61 ms - 129
let el = await driver.elementById('com.android.calculator2:id/digit_2');

Getting started with Appium

[HTTP] --> POST /wd/hub/session/67734aa5-1567-4393-9e1b-701a886d053f/element
[HTTP] {"using":"id","value":"com.android.calculator2:id/digit_2"}

Appium server gets a command request from the client

[debug] [MJSONWP (67734aa5)] Calling AppiumDriver.findElement() with args: ["id","com.android.calculator2:id/digit_2","67734aa5-1567-4393-9e1b-701a886d053f"]
[debug] [BaseDriver] Valid locator strategies for this request: xpath, id, class name, accessibility id, -android uiautomator
[debug] [BaseDriver] Waiting up to 0 ms for condition
[debug] [AndroidBootstrap] Sending command to android: {"cmd":"action","action":"find","params":{"strategy":"id","selector":"com.android.calculator2:id/digit_2","context":"","multiple":false}}
[debug] [AndroidBootstrap] [BOOTSTRAP LOG] [debug] Got data from client: {"cmd":"action","action":"find","params":{"strategy":"id","selector":"com.android.calculator2:id/digit_2","context":"","multiple":false}}
[debug] [AndroidBootstrap] [BOOTSTRAP LOG] [debug] Got command of type ACTION
[debug] [AndroidBootstrap] [BOOTSTRAP LOG] [debug] Got command action: find
[debug] [AndroidBootstrap] [BOOTSTRAP LOG] [debug] Finding 'com.android.calculator2:id/digit_2' using 'ID' with the contextId: '' multiple: false
[debug] [AndroidBootstrap] [BOOTSTRAP LOG] [debug] Using: UiSelector[INSTANCE=0, RESOURCE_ID=com.android.calculator2:id/digit_2]
[debug] [AndroidBootstrap] Received command result from bootstrap
[debug] [AndroidBootstrap] [BOOTSTRAP LOG] [debug] Returning result: {"status":0,"value":{"ELEMENT":"1"}}
[debug] [MJSONWP (67734aa5)] Responding to client with driver.findElement() result: {"element-6066-11e4-a52e-4f735466cecf":"1","ELEMENT":"1"}

Appium server executes a command on the driver and waits for the results

Once Appium gets a response from the driver, replies back to the client

[HTTP] <-- POST /wd/hub/session/67734aa5-1567-4393-9e1b-701a886d053f/element 200 61 ms - 129

Getting started with Appium

Running web tests

 

 

 

 

 

 

 

 

We talked about starting native test, now let's run some web tests. To start a web tests we need to change our capabilities to use browserName

let caps = {
    'browserName': 'chrome'
    'deviceName': 'Android GoogleApi Emulator',
    'platformName': 'Android',
    'platformVersion': '8.0'
}
it('Test can open URL and title is correct', async () => {
        await driver.get("https://saucelabs.github.io/training-test-page/");
        await sleep(500);
        let title = await driver.title();
        expect(title).to.equal("I am a page title - Sauce Labs");
});

An start our test by reaching an URL using .get command

Note: driver.get command wont wait for the page to finish loading

Getting started with Appium

What is chromedriver?

ChromeDriver is a separate executable that Appium uses to control Chrome. It is maintained by the Chromium team with help from WebDriver contributors.

For different chrome versions you most probably need to use different chromedriver versions since they correspond between each other.

If your current environment fails Appium will show this message:

Original error: No Chromedriver found that can automate Chrome '58.0.3029'. 
See https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/web/chromedriver.md 
for more details.

Getting started with Appium

Which chromedriver should I use?

 

Open Chrome in your device and go to the version information: chrome://version

With the version you can go to chromedriver download

page and use the one that fits your chrome version:

https://chromedriver.storage.googleapis.com/index.html

For ex. On this emulator we have chrome 58, so a chromedriver that would work this this version would be version 2.31: https://chromedriver.storage.googleapis.com/2.31/notes.txt

Getting started with Appium

Setting chromedriver

 

Appium lets you set you the path to chromedriver on the tests capabilities

let caps = {
    'browserName': 'chrome'
    'deviceName': 'Android GoogleApi Emulator',
    'platformName': 'Android',
    'platformVersion': '8.0',
    'chromedriverExecutable': '/path/to/chromedriver.2.30'  
}
appium -p 4723 --chromedriver-executable /path/to/chromedriver2.31
[Appium] Welcome to Appium v1.12.1
[Appium] Non-default server args:
[Appium]   chromedriverExecutable: /path/to/chromedriver2.31
[Appium] Appium REST http interface listener started on 0.0.0.0:4723

or it can be set as a command-line argument

Getting started with Appium

Hybrid apps tests

 

 

 

 

 

 

 

 

It's really common to have webviews in your native applications, or use frameworks like phonegap that are webview based. Appium let's you handle this scenario by finding and changing contexts inside your tests.

http://appium.io/docs/en/writing-running-appium/web/hybrid/index.html

it('Test can reach element inside webview', async () => {
    let ctxs = await driver.contexts();
    console.log(ctxs)
    await driver.context('WEBVIEW_io.appium.appiumworkshop');
    el = await driver.elementById('section_title')
    expect(await el.isDisplayed()).to.equal(true);
    expect(await el.text()).to.equal('Terms & Conditions');
});
Same as web tests, on Android changing context depends on Chromedriver. 

Getting started with Appium

iOS Requirements (recommended)

  • MacOs Mojave                                                                 
  • Xcode 10+

Getting familiar with iOS tools:

xcrun simctl: is a command line tool that allows you to create and manage ios simulators

xcrun simctl list devices
xcrun simctl list runtimes
xcrun simctl create <sim-name> <devicetype> <runtime>

Creating a simulator

Let's create a simulator for this workshop

xcrun simctl create appium-simulator\ 
com.apple.CoreSimulator.SimDeviceType.iPhone-7\
com.apple.CoreSimulator.SimRuntime.iOS-12-2

Getting started with Appium

Installing Simulator runtimes

Simulators runtimes are installed via xcode, in order to install missing runtimes open Xcode > Preferences > Components

Getting started with Appium

The XCUITest Driver

This driver leverages Apple's XCUITest libraries under the hood in order to facilitate automation of your app . This access to XCUITest is mediated by the WebDriverAgent server. WDA is a WebDriver-compatible server that runs in the context of an iOS simulator or device and exposes the XCUITest API. Appium's XCUITest driver manages WDA as a subprocess opaque to the Appium user, proxies commands to/from WDA, and provides a host of additional functionality

More info

Getting started with Appium

iOS common capabilities

 

Capability Description Values
safariInitialUrl Safari url to open on startup For ex: https://saucelabs.github.io/training-test-page/
udid Unique device identifier of the connected physical device 1ae203187fc012g
bundleId bundle ID of the app under test. Useful for starting an app on a real device or for using other caps which require the bundle ID during test startup. To run a test on a real device using the bundle ID, you may omit the 'app' capability, but you must provide 'udid'. ​io.appium.TestApp
orientation (Sim/Emu-only) start in a certain orientation LANDSCAPE or PORTRAIT
app Path to application to be installed in the device
locationServicesEnabled (Sim-only) Force location services to be either on or off. Default is to keep current sim setting. true/false
safariAllowPopups (Sim-only) Allow javascript to open new windows in Safari. Default keeps current sim setting true/false

Getting started with Appium

Let's start with iOS by running a web test, instead of using Chrome as browserName capability we'll use Safari.

let caps = {
    'browserName': 'safari',
    'deviceName': 'appium-simulator',
    'platformName': 'iOS',
    'platformVersion': '12.2'
}

As we did for Android we need to set the required capabilities platformName, platformVersion and deviceName for iOS:

it('Test can open URL and title is correct', async () => {
        await driver.get("https://saucelabs.github.io/training-test-page/");
        await sleep(500);
        let title = await driver.title();
        expect(title).to.equal("I am a page title - Sauce Labs");
});

The body of an iOS web test is exactly the same as the one we created early for  Android.

Getting started with Appium

let caps = {
    'deviceName': 'appium-simulator',
    'platformName': 'iOS',
    'platformVersion': '12.2',
    'app': '/path/to/my.app'
}

iOS native tests

where app capability is the absolute local path or remote http URL to a .ipa file (IOS), .app folder (IOS Simulator). or a .zip file containing one of these (for .app, the .app folder must be the root of the zip file). Appium will attempt to install this app binary on the appropriate device first.

An alternative to use app capability is using bundleId for an already installed application.

let caps = {
    'deviceName': 'appium-simulator',
    'platformName': 'iOS',
    'platformVersion': '12.2',
    'bundleId': 'com.apple.MobileAddressBook'
}

Getting started with Appium

Getting simulator system application bundle ids

xcrun simctl list devices | grep Booted

In order to automate iOS simulator system apps, we need to inspect the simulator container. All the simulators are located in

~/Library/Developer/CoreSimulator/Devices/<simulator-id>

A quick trick to get the simulator id of the Booted simulator  is to run:

Every application installed on the simulator lives in the Container folder, by inspecting this folder along with using PlistBuddy you can get the list of all apps installed in the simulator.

cd ~/Library/Developer/CoreSimulator/Devices/EC9EAE96-15E4-49F3-AF59-AD01B7A8B697
cd data/Containers/Data/Application
for d in */ ; do /usr/libexec/Plistbuddy -c "Print" "$d/.com.apple.mobile_container_manager.metadata.plist"; done;

This script will give you the list of applications installed on the simulator, the MCMMetadataIdentifier is the bundleId to use in your tests.

Getting started with Appium

Touch actions

TouchAction objects contain a chain of events.

In all the appium client libraries, touch objects are created and are given a chain of events. The available events from the spec are: * press * release * moveTo * tap * wait * longPress * cancel * perform

Here's an example of creating an action in pseudocode:

TouchAction().press(el0).moveTo(el1).release()

The above simulates a user pressing down on an element, sliding their finger to another position, and removing their finger from the screen.

Made with Slides.com