This is the fourth in a series of posts that discuss using Appium with Sauce Labs.
Chapter 1 covered Language Bindings; Chapter 2 discusses Touch Actions, and Chapter 3 covers Testing Hybrid Apps & Mobile Web.
Appium, by and large, supports the desired capabilities you’re familiar with from Selenium. However, given that it also exposes mobile-specific functionality, we have some mobile-specific desired capabilities. We even have capabilities that are specific to one mobile platform: iOS or Android. For the full list of capabilities Appium supports, please see the capabilities doc.
The main differences you’ll recognize will have to do with selecting which type of mobile environment you want to automate. Appium has adopted the “Selenium 4″ style of session capabilities, which means that, instead of capabilities like this, for example:
[code language=”javascript”] { browserName: ‘safari’, version: ‘6.0’ } [/code]
You would specify the following, for Mobile Safari on iOS 7.1:
[code language=”javascript”] { platformName: ‘iOS’, platformVersion: ‘7.1’, deviceName: ‘iPhone Simulator’, browserName: ‘safari’ } [/code]
The new capabilities platformName
, platformVersion
, and deviceName
are _required_ for every session. Valid platforms are iOS
and Android
.
To automate a native or hybrid app instead of a mobile browser, you omit the browserName
capability and include the app
capability, which is a fully-resolved local path or URL to your application (.app, .ipa, or .apk). For example:
[code language=”javascript”] { platformName: ‘Android’, platformVersion: ‘5.0’, deviceName: ‘Android Emulator’, app: ‘/path/to/my/android_app.apk’ } [/code]
Sometimes we might want to run an Android test on an older device, and so we need to use Appium’s built-in Selendroid support for older devices. To specify that you want to use the Selendroid backend, keep everything else the same, but specify the automationName
capability, as follows:
[code language=”javascript”] { automationName: ‘Selendroid’, platformName: ‘Android’, platformVersion: ‘5.0’, deviceName: ‘Android Emulator’, app: ‘/path/to/my/android_app.apk’ } [/code]
And of course, if you’re running any tests on Sauce Labs, make sure to specify the version of Appium you’d like to use with the appiumVersion
capability:
[code language=”javascript”] { platformName: ‘Android’, platformVersion: ‘5.0’, deviceName: ‘Android Emulator’, browserName: ‘Browser’, appiumVersion: ‘1.3.4’ } [/code]
Here are some other cross-platform capabilities that affect how your session will run.
autoLaunch
Whether to have Appium install and launch your app.
language
Change the default language on the device. (Emulator only)
locale
Change the default locale on the device. (Emulator only)
udid
Unique device identifier for a connected physical device. Helps Appium determine the device on which to execute tests.
orientation
Change the starting orientation of the device. (Emulator only)
autoWebview
When your test starts, have Appium move directly into Webview context.
noReset
Don’t reset app state before this session.
fullReset
(iOS) Delete the entire simulator folder. (Android) Reset app state by uninstalling app instead of clearing app data. On Android, this will also remove the app after the session is complete.
Managing Packages and Activities
Android application execution sometimes involves multiple packages and activities. For example, your application might have a splash screen or users might launch one activity from another. Appium offers appWaitActivity
and appWaitPackage
to allow you to test these more complex scenarios. If you want to wait for activity A to be visible before launching activity B, as you would in the case of a splash screen, your capabilities might look like this:
[code language=”javascript”] { automationName: ‘Selendroid’, platformName: ‘Android’, platformVersion: ‘5.0’, deviceName: ‘Android Emulator’, app: ‘/path/to/my/android_app.apk’, // appActivity/Package are autodiscovered, so no need to specify appWaitActivity: ‘.SplashActivity’, appWaitPackage: ‘com.corp.foo’ } [/code]
If you’re would like to run a test suite that is Instrumentation-compatible, you can specify it using the androidCoverage
capability:
[code language=”javascript”] { … androidCoverage: ‘my.fq.class.name’ } [/code]
Signing Packages
Running tests using Android’s Instrumentation (which is what powers the Selendroid automation backend) requires that binaries containing tests be signed with the same certificate as the apk. Appium normally uses a self-signed certificate under the hood to take care of this, but you can override Appium’s default behavior by using capabilities to specify the keystore of your choice.
[code language=”javascript”] { … useKeystore: true, keystorePath: ‘/my/keystore’, keyAlias: ‘androiddebugkey’, keystorePassword: ‘foo’, keyPassword: ‘bar’, noSign: false // only works with UiAutomator; defaults to false } [/code]
Chrome and Chromedriver
Appium on Android uses Chromedriver for testing mobile browsing and hybrid applications. To facilitate these tests, Appium offers capabilities that expose many of Chromedrivers features. If you’ve written a hybrid application and would like to capture Chromedriver’s performance logging, set enablePerformanceLogging
to true
. If you’re using a Chromium embedded browser, you can specify which socket port Chromedriver should use to connect as a devtools client via androidDeviceSocket
. Appium also allows you to override the Chromedriver version by supplying the chromedriverExecutable
capability. Finally, you can control the amount of time that Chromedriver, and hence Appium, waits for a webview to become active by using the autoWebviewTimeout
capability.
[code language=”javascript”] { … enablePermanceLogging: true, androidDeviceSocket: ‘crazy_fast_socket’, chromedriverExecutable: ‘/my/bleeding/edge/build’, autoWebviewTimeout: 2000 // this is the default, in ms } [/code]
Android Intents
Many Android applications are launched by intent messages. To enable these scenarios in a testing environment, Appium provides a group of capabilities that allow you launch your application by intent. The intent options are straightforward and map closely to ADB’s intent
command line interface.
[code language=”javascript”] { … intentAction: ‘android.intent.action.MAIN’, intentCategory: ‘android.intent.category.APP_CONTACTS’, intentFlags: ‘0x10200000’, // Bit flag hex value supplied as string optionalIntentArguments: ‘–ez <EXTRA_KEY> <EXTRA_BOOLEAN_VALUE>’ // ADB command line arguments } [/code]
Miscellany
Appium also provides a few other pieces of capability magic to help power your tests. stopAppOnReset
ensures that your app is completely stopped between sessions, and is on by default. At the beginning of each session, Appium stops the application process using ADB, and then restarts it. This behavior may not be desireable in cases where a controlling instance launches child processes. Set this capability to false
when you want to keep the controlling process alive between Appium sessions.
Many applications rely on unicode characters. Unfortunately, Android’s UI Automator framework silently strips everything that is not in the ASCII range. So sending, say, sūkṣma
, just sends skma
to the application under test. By setting unicodeKeyboard
to true
, Appium encodes strings using modified UTF-7, then decodes it back into the full character in the IME on the device. That prevents data loss when working with unicode strings. You can use resetKeyboard
to ensure the keyboard is returned to its original state (i.e., returned to the keyboard that was active before we switched to the Unicode keyboard) after running Unicode tests with the unicodeKeyboard
capability.
Finally, Appium provides the ignoreUnimportantViews
which calls the setCompressedLayoutHierarchy()
UiAutomator function. This capability can speed up test execution, since Accessibility commands will run faster, ignoring some elements. The ignored elements will not be findable, which is why this capability has also been implemented as a toggle-able setting as well as a capability.
[code language=”javascript”] { … stopAppOnReset: true, unicodeKeyboard: true, resetKeyboard: true, ignoreUnimportantViews: false } [/code]
For iOS tests, there is a pretty large difference between what we can control on an iOS simulator versus and iOS real device. This is because we can modify the various settings files that determine the simulator behavior on the host machine, whereas we have no access to the bits of a real device that determine the equivalent behavior. Thus the iOS section is divided into two: capabilities that work everywhere and capabilities that work only on simulators.
General iOS Capabilities
Sometimes, we want to test against an app that is already installed on the simulator or device, and we don’t have an .app or .ipa file locally. In that case, we can use the bundleId
capability to tell Appium to simply try to automate the existing app (of course it needs to be signed and valid for your device):
[code language=”javascript”] { bundleId: ‘io.appium.TestApp’ } [/code]
When launching iOS tests, we use Apple’s Instruments binary. Instruments is unfortunately quite flakey and sometimes simply hangs on startup. The only thing we can do about that is time out and try again. You can fine-tune the timeout value (in milliseconds) after observing how long it should take on your particular configuration for Instruments to launch the simulator successfully:
[code language=”javascript”] { launchTimeout: 20000 } [/code]
After Instruments launches a test session, Appium tries to make sure that it doesn’t let your test code keep running until it knows your app has actually launched. To determine this, it checks to see whether there are any elements in your app. For whatever reason, some apps have trouble reporting that there are any such elements, and so the session never begins. In these cases, you can actually customize the UIAutomation script we use to determine the test can proceed. For example, you might just tell Appium to wait 5 seconds and continue, assuming the app is ready:
[code language=”javascript”] { waitForAppScript: “$.delay(5000); true;” } [/code]
If you’re starting up a webview-based app, or a web test using Safari, Appium will attempt to establish a remote debugger session with the webview so that it can send it the necessary web automation commands. Sometimes the connection isn’t established instantly and we have to retry. You can customize the number of times we retry before giving up. The default is 8.
[code language=”javascript”] { webviewConnectRetries: 12 } [/code]
Sometimes your application or the operating system will throw up alerts during execution. If you don’t care about handling these intentionally using the WebDriver alerts API, you can tell Appium to simply accept or dismiss any that it can, with these two capabilities (which are mutually exclusive, of course):
[code language=”javascript”] { autoAcceptAlerts: true, // autoDismissAlerts: true } [/code]
If your app doesn’t have its localized strings in en.lproj
, you can specify exactly where Appium should look to find them (if you want to use the string translation features):
[code language=”javascript”] { localizableStringsDir: “de.lproj” } [/code]
You can send command-line arguments that your app can use to modify its behavior (say if you want to set a backend server address, or something), using the processArguments
cap:
[code language=”javascript”] { processArguments: “-myflag” } [/code]
Appium uses Apple’s UIAutomation’s typing methods to send keystrokes to your app’s input fields. Sometimes we’ve observed that UIAutomation tries to do it too quickly and messes up (just like a real user, perhaps?). In some cases, telling it to slow down helps. You can do this using the interKeyDelay
capability (in milliseconds). This will cause extra time to be added between each keystroke. You can also change the strategy that Appium uses to send keys entirely. There are three: oneByOne
(each key is typed on its own), grouped
(the default, where we send the whole string to UIAutomation and let it figure it out), and setValue
, where we bypass the keyboard entirely and simply set the value of the field.
[code language=”javascript”] { interKeyDelay: 100,sendKeyStrategy: “oneByOne” } [/code]
It is sometimes convenient to debug what’s going on with your app by looking at your app’s logs or the iOS simulator logs. You can extract these using the WebDriver log API in your test code. You can also tell Appium to simply show you those logs, interleaved with its own logs:
[code language=”javascript”] { showIOSLog: true } [/code]
Sometimes, when trying to take a screenshot of an app with a lot of elements, it can take UIAutomation an extremely long time to generate the image. Sometimes it takes so long that it runs afoul of a timeout we have set to make sure the screenshot command doesn’t hang forever. If you get timeouts waiting for screenshots, you might want to bump the default (which is 10 seconds) to something higher. Note that this capability is in seconds!
[code language=”javascript”] { screenshotWaitTimeout: 20 } [/code]
Simulator-only iOS Capabilities
The following capabilities will only work on the simulator, because they involve changing files on the system that hosts the simulator (which obviously won’t work on a real device).
First of all, we use a special tool called instruments-without-delay to work around a limitation of Apple’s UIAutomation, which inserts an artificial 1-second delay between _every_ automation command. Usually there are no problems injecting this tool into the automation engine, but if you want to be absolutely sure it’s not causing problems, you can always turn it off with:
[code language=”javascript”] { nativeInstrumentsLib: true } [/code]
You can set the calendar format the device uses:
[code language=”javascript”] { calendarFormat: “gregorian” } [/code]
You can make sure that the device has location services enabled, and authorized for your app (which requires you to also tell us the bundleId
of your app). This prevents a nasty authorization box from popping up in the middle of your test!
[code language=”javascript”] { locationServicesEnabled: true, locationServicesAuthorized: true } [/code]
We also have a number of options specifically to do with the Safari browser on an iOS simulator. You can ensure that native tap events (rather than JS events) are getting triggered when you tap on web elements. You can set an initial url for Safari to visit. You can tell Safari to allow new popup windows to be opened by JavaScript. You can tell Safari to ignore its fraud warning (for example when visiting a site with a self-signed SSL certificate). You can also instruct Safari to open new links in the background rather than going to them automatically. Some examples:
[code language=”javascript”] { nativeWebTap: true, safariInitialUrl: “http://www.saucelabs.com”, safariAllowPopups: true, safariIgnoreFraudWarning: true, safariOpenLinksInBackground: true } [/code]
Finally, if you have a test setup where you rely on keychain data for a simulator, you can instruct Appium not to wipe this data during its reset process (where it would normally trash it for total cleanliness):
[code language=”javascript”] { keepKeyChains: true } [/code]
That wraps up this deeper dive into the wide and weird world of Appium’s capabilities! As mentioned earlier, keep an eye on the Appium docs for any new ones that crop up. Also note that for good and practical reasons, some capabilities aren’t allowed on Sauce Labs (for example, you won’t have an Android keystore on any of our freshly-created VMs, so it doesn’t make sense to say that it’s supported). Happy testing!