Appium
Automation for Apps
...in my case, hybrid Cordova apps
Who am I ?
Tommy-Carlos Williams
tommy@devgeeks.org
@theRealDevgeeks
http://blog.devgeeks.org
http://github.com/devgeeks
What is Appium?
Appium is an open source test automation
framework for use with native and hybrid mobile apps.
It drives iOS and Android apps using the
WebDriver JSON wire protocol.
Why Automated Testing?
or: "I do unit testing already, why do I need this?"
Integration and Functional tests verify that the components
of your application work together
of your application work together
Unit Test - testing an individual unit, such as a method (function)
in a class, with all dependencies mocked up.
Functional Test - AKA Integration Test, testing a slice of
functionality in a system. This will test many methods and
may interact with dependencies like Databases
or Web Services.
OK, But Why Appium?
- Open Source
- Cross platform
- No SDKs or recompilation of your app
- Tests written in the language of your choice
- Installable from npm
- Can be used with SauceLabs cloud testing
What can we automate?
- Clicks/Taps
- Form input
- Gestures
- Alert acceptance/dismissal
- etc...
Selenium JSON Wire Protocol
A description of the protocol used by WebDriver to communicate with remote instances
A REST API to communicate with your app
Testing libraries in various languages to communicate to the API
Test Using JS
WD.js – Webdriver/Selenium 2 client
Node-based selenium client - use either async or promises
/**/
browser.init({browserName:'chrome'}, function() {
browser.get("http://admc.io/wd/test-pages/guinea-pig.html", function() {
browser.title(function(err, title) {
title.should.include('WD');
browser.elementById('i am a link', function(err, el) {
browser.clickElement(el, function() {
/* jshint evil: true */
browser.eval("window.location.href", function(err, href) {
href.should.include('guinea-pig2');
browser.quit();
});
});
});
});
});
});
Getting Started / Boilerplate
Must set up the driver / browser object with info on the app
/**/
path = require('path');
var projectRoot = path.resolve(__dirname, '..');
//- var chai = require("chai");
//- var chaiAsPromised = require("chai-as-promised");
//- chai.use(chaiAsPromised);
//- chai.should();
var wd = require('wd')
describe('Encryptr', function() {
this.timeout(50000);
var browser, appURL = projectRoot + "/platforms/android/bin/Encryptr-debug.apk";
var waitTimeout = 10000;
before(function() {
browser = wd.promiseChainRemote("localhost", 4723);
return browser
.init({
device: 'Selendroid', // or 'iPhone Simulator'
'app-package': 'org.devgeeks.encryptr',
'app-activity': '.MyApp', // only for Android
name: 'MyApp', app: '/path/to/MyApp-debug.apk', version: '',
browserName: '', implicitWaitMs: 500
});
});
});
Hybrid Specific Info
Before interacting with the web view, we need to switch context
/**/
// Android
browser.window("WEBVIEW");
// iOS
browser.windowHandles()
.then(function(handles) {
return browser.window(handles[0]);
});
Our First Test
Super simple. Start the app, wait for the existence of an
element, then check the text is correct
element, then check the text is correct
/**/
describe("Registration", function() {
it("should have a registration link", function() {
return browser
.waitForElementByCss(".signupButton", waitTimeout)
.then(function() {
return browser.elementByCss(".signupButton");
}).should.eventually.be.ok;
});
it("should have the text: 'Register for an account »'", function() {
return browser.elementByCss(".signupButton")
.then(function(el) {
return el.text();
}).should.eventually.equal("Register for an account »");
});
});
Demo
Something a Bit More Useful
Finding an element is all well and good, but clicking and
entering text is a lot more useful.
entering text is a lot more useful.
/**/
it("should be able to enter a new username", function() {
return browser.waitForElementByCss("input[name=newusername]", 10000)
.then(function(el) {
el.sendKeys("testusername");
return el.value;
}).should.eventually.equal("testusername");
});
it("should be able to enter a new passphrase", function() {
return browser.waitForElementByCss("input[name=passphrase]")
.then(function(el) {
el.sendKeys("testpassphrase");
return el.value;
}).should.eventually.equal("testpassphrase");
});
it("should be able to register", function() {
return browser
.waitForElementByCss(".button.signupButton", 10000)
.then(function() {
return browser.elementByCss(".button.signupButton");
})
.then(function(el) {
return el.click();
})
.then(function() {
return browser
.waitForElementByCss(".login.dismissed", 10000);
}).should.eventually.be.ok;
});
Demo
Some Helpful Libraries
...to make it all just that little bit nicer
- https://github.com/wookiehangover/wd-query
jQuery style selectors for wd promises - http://chaijs.com/plugins/chai-as-promised
Chai as Promised extends Chai with a fluent language
for asserting facts about promises
Adding to Your Workflow
- https://github.com/pghalliday/grunt-mocha-test
A grunt task for running server side mocha tests
/**/
mochaTest: {
test: {
options: {
reporter: 'spec'
},
timeout: 10e3,
src: ['functional-tests/functionalTests.js']
}
}
- https://github.com/wookiehangover/grunt-mocha-appium
Run functional tests with Mocha, wd and Appium.
Big Demo
Thanks!
Feel free to ask me any Cordova / PhoneGap questions you may have, as long as they have nothing to do with jQuery Mobile.
Just kidding.
I'm totally not kidding...