Bruno Alassia
Software Engineer 🐳 rosarino en Canada
Getting started with Appium
Getting started with 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.
You don't have to recompile your app or modify it in any way, due to use of standard automation APIs on all platforms.
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.
You can use any testing framework.
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
Getting started with Appium
Appium client
Appium server
Device
Getting started with Appium
You write your tests using one of Appium client libraries
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
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
One of the biggest advantages of Appium is that you can write your tests in different languages and choose the one you feel more comfortable with.
https://github.com/appium/python-client
https://github.com/appium/java-client
https://github.com/appium/ruby_lib
https://github.com/webdriverio/webdriverio
Getting started with 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
Once appium is installed let's start the appium server
npm install -g appium
appium
Getting started with Appium
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
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
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
npm install
Getting started with Appium
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
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
Browser and App testing are either exclusive, you can only start one type of session.
"
"appPackage": "com.my.app"
"appActivity": ".MainActivity"
"app": "<path_to_apk>"
Getting started with Appium
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
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
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
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');
});
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>
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>
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
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
.elementById
.elementByClassName
.elementByName
.elementByTagName
.click
.sendKeys
.clear
.getAttribute
.tap
.setValue
.getValue
.text
.isDisplayed
Getting started with Appium
.back
.getPageSource
.title()
.getWindowSize
.hideKeyboard
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"}
[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
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
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
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
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
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
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
Simulators runtimes are installed via xcode, in order to install missing runtimes open Xcode > Preferences > Components
Getting started with Appium
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
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 |
More capabilities: http://appium.io/docs/en/writing-running-appium/caps/
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'
}
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
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
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.
By Bruno Alassia