Introduce Appium
@testotips.io
Twitter id: @Kazu_cocoa
Appium
?
Appium is an open source test
automation framework for use
with native, hybrid and mobile
web apps.
It drives iOS and Android apps
using the WebDriver protocol.
from: http://appium.io/
from: http://www.3pillarglobal.com/insights/appium-a-cross-browser-mobile-automation-tool
Integration TestUnit Test
Flow of automated test
GUI Test
Feature Test
(E2E Test)
GUI / Feature testing
Integration TestUnit Test
GUI Test
Feature Test
(E2E Test)
$ npm install appium
($ npm install -g appium)
node_module/.bin
appium.js
appium-doctor.js
authorize_ios.js
$ $(npm bin)appium
node_modules/appium/bin/appium.js
var appium = require('../lib/server/main.js');	
!
var startRepl = function () {	
var help = function () {	
console.log("nWelcome to the Appium CLI".cyan);	
console.log(" - Access the appium object via the object: 'appium'".grey);	
console.log(" - appium.run is the function to start the server".grey);	
console.log(" - args is the default params data structure".grey);	
console.log(" - set args.app then run appium.run(args);n".grey);	
return 'Thanks for asking';	
};	
!
help();	
!
var r = repl.start('(appium): ');	
r.context.appium = appium;	
r.context.parser = parser();	
r.context.help = help;	
r.context.args = {	
app: '/path/to/test/app'	
, udid: null	
, address: '127.0.0.1'	
, port: 4723	
};	
!
var connections = 0;	
var server = net.createServer(function (socket) {	
connections += 1;	
socket.setTimeout(5 * 60 * 1000, function () {	
socket.destroy();	
});	
repl.start("(appium): ", socket);	
}).listen(process.platform === "win32" ? ".pipenode-repl-sock-" + process.pid : "/tmp/node-repl-sock-" +
process.pid);	
!
r.on('exit', function () {	
server.close();	
process.exit();	
});
node_modules/appium/lib/server/main.js
var http = require('http')	
, express = require('express')	
, favicon = require('serve-favicon')	
, bodyParser = require('body-parser')	
, methodOverride = require('method-override')	
, morgan = require('morgan') // logger	
, routing = require('./routing.js')	
, path = require('path')	
, appium = require('../appium.js')	
, parserWrap = require('./middleware').parserWrap	
, appiumVer = require('../../package.json').version	
, appiumRev = null	
, async = require('async')	
, helpers = require('./helpers.js')	
, logFinalWarning = require('../helpers.js').logFinalDeprecationWarning	
, getConfig = require('../helpers.js').getAppiumConfig	
, allowCrossDomain = helpers.allowCrossDomain	
, catchAllHandler = helpers.catchAllHandler	
, checkArgs = helpers.checkArgs	
, configureServer = helpers.configureServer	
, startListening = helpers.startListening	
, conditionallyPreLaunch = helpers.conditionallyPreLaunch	
, prepareTmpDir = helpers.prepareTmpDir	
, requestStartLoggingFormat = require('./helpers.js').requestStartLoggingFormat	
, requestEndLoggingFormat = require('./helpers.js').requestEndLoggingFormat	
, domainMiddleware = require('./helpers.js').domainMiddleware;
var loggerjs = require('./server/logger.js')	
, logger = loggerjs.get('appium')	
, UUID = require('uuid-js')	
, _ = require('underscore')	
, Capabilities = require('./server/capabilities')	
, IOS = require('./devices/ios/ios.js')	
, Safari = require('./devices/ios/safari.js')	
, Android = require('./devices/android/android.js')	
, Selendroid = require('./devices/android/selendroid.js')	
, Chrome = require('./devices/android/chrome.js')	
, FirefoxOs = require('./devices/firefoxos/firefoxos.js')	
, jwpResponse = require('./devices/common.js').jwpResponse	
, status = require("./server/status.js");	
!
var DT_IOS = "ios"	
, DT_SAFARI = "safari"	
, DT_ANDROID = "android"	
, DT_CHROME = "chrome"	
, DT_SELENDROID = "selendroid"	
, DT_FIREFOX_OS = "firefoxos";	
node_modules/appium/lib/appium.js
!
node_modules/appium/lib/server/capabilities.js
var _ = require('underscore')	
, logger = require('./logger.js').get('appium')	
, warnDeprecated = require('../helpers.js').logDeprecationWarning;	
!
var capsConversion = {...};	
!
var okObjects = [...];	
!
var requiredCaps = [...];	
!
var strictRequiredCaps = [...];	
!
var generalCaps = strictRequiredCaps.concat([...]);	
!
var androidCaps = [...];	
!
var iosCaps = [...];
Appium
meets
iOS
Capabilities
IOS_CAPS_81 = {
automationName: 'Appium',
platformName: :ios,
platformVersion: '8.1',
deviceName: 'iPhone 6 Plus',
app: IOS_APP_PATH,
bundleId: APP_BUNDLE_ID,
!
calendarFormat: 'gregorian',
!
sendKeyStrategy: 'grouped'
!
localizableStringsDir: 'ja.lproj',
language: 'ja',
locale: 'ja_JP',
!
screenshotWaitTimeout: 20
}
create new plist before start app
appium/lib/devices/ios/ios.js
IOS.prototype.setDeviceTypeInInfoPlist = function (cb) {	
var plist = path.resolve(this.args.app, "Info.plist");	
var dString = this.getDeviceString();	
var isiPhone = dString.toLowerCase().indexOf("ipad") === -1;	
var deviceTypeCode = isiPhone ? 1 : 2;	
parsePlistFile(plist, function (err, obj) {	
if (err) {	
logger.error("Could not set the device type in Info.plist");	
return cb(err, null);	
} else {	
var newPlist;	
obj.UIDeviceFamily = [deviceTypeCode];	
if (binaryPlist) {	
newPlist = bplistCreate(obj);	
} else {	
newPlist = xmlplist.build(obj);	
}	
fs.writeFile(plist, newPlist, function (err) {	
if (err) {	
logger.error("Could not save new Info.plist");	
cb(err);	
} else {	
logger.debug("Wrote new app Info.plist with device type");	
cb();	
}	
}.bind(this));	
}	
}.bind(this));	
};
Appium
meets
Android
Capabilities
ANDROID_CAPS = {
automationName: 'Appium',
platformName: :android,
platformVersion: '4.2',
deviceName: ‘Android’,
app: ANDROID_APP_PATH,
!
unicodeKeyboard: true,
!
fullReset: true,
screenshotWaitTimeout: 20
}
appium/lib/devices/android/android.js
Android.prototype.pushAppium = function (cb) {	
logger.debug("Pushing appium bootstrap to device...");	
var binPath = path.resolve(__dirname, "..", "..", "..", "build",	
"android_bootstrap", "AppiumBootstrap.jar");	
fs.stat(binPath, function (err) {	
if (err) {	
cb(new Error("Could not find AppiumBootstrap.jar; please run " +	
"'grunt buildAndroidBootstrap'"));	
} else {	
this.adb.push(binPath, this.remoteTempPath(), cb);	
}	
}.bind(this));	
};	
!
Android.prototype.startApp = function (args, cb) {	
if (args.androidCoverage) {	
this.adb.androidCoverage(args.androidCoverage, args.appWaitPackage,	
args.appWaitActivity, cb);	
} else {	
this.adb.startApp({	
pkg: args.appPackage,	
activity: args.appActivity,	
action: args.intentAction,	
category: args.intentCategory,	
flags: args.intentFlags,	
waitPkg: args.appWaitPackage,	
waitActivity: args.appWaitActivity,	
optionalIntentArguments: args.optionalIntentArguments,	
stopApp: args.stopAppOnReset	
}, cb);	
}	
};
appium/lib/devices/android/bootstrap/
例えば、UiDeviceのpressBack()を使う
Common
Selectors
exports.checkValidLocStrat = function (strat, includeWeb, cb) {	
if (typeof includeWeb === "undefined") {	
includeWeb = false;	
}	
var validStrats = [	
'xpath',	
'id',	
'name',	
'class name'	
];	
var nativeStrats = [	
'-ios uiautomation',	
'accessibility id',	
'-android uiautomator'	
];	
var webStrats = [	
'link text',	
'css selector',	
'tag name',	
'partial link text'	
];	
var nativeDeprecations = {};	
var webDeprecations = {};	
var deprecations = {name: 'accessibility id'};
XPath
find_element :xpath, ”なんらかのXPath"
accessibility id
find_element :accessibility_id, ”なんらかの要素"
css selector
find_element :css_selector, ”なんらかの要素"
# Ruby Client使用時
iOS: UIAutomation
find_element :uiautomator,
"new UiSelector()
.className("android.widget.TextView")
.text("#{element_name}");”
find_element :uiautomation,
”$.mainApp().alert().buttons()[‘#{text}'];"
Android: uiautomator
Run test
concurrently
Appium is just server
Appium1: $appium -p 4723 &
Appium2: $appium -p 4725 -bp 4726 &
Appium1
iOS App Android
Appium
appium_server
= “http://127.0.0.1: 4723”
appium_server
= “http://127.0.0.1: 4725”
adb: 4726
Fin
One more tip
DroidDriver
DroidDriver
• Clone from Google to SauceLab(Appium)
• https://github.com/appium/droiddriver
• UiDeviceとか、UiElementとか使ってそう
• https://github.com/appium/droiddriver/tree/master/droiddriver-
android_support_test
• android-support-test libraryと並行して使うユースケースもある
が、android-support-test libraryはまだ開発されて日が浅いので
optionとしての統合に留められている
Thanks

20150319 testotipsio

  • 1.
  • 2.
  • 3.
  • 4.
    Appium is anopen source test automation framework for use with native, hybrid and mobile web apps. It drives iOS and Android apps using the WebDriver protocol. from: http://appium.io/
  • 5.
  • 6.
    Integration TestUnit Test Flowof automated test GUI Test Feature Test (E2E Test)
  • 7.
    GUI / Featuretesting Integration TestUnit Test GUI Test Feature Test (E2E Test)
  • 8.
    $ npm installappium ($ npm install -g appium)
  • 9.
  • 10.
  • 11.
    node_modules/appium/bin/appium.js var appium =require('../lib/server/main.js'); ! var startRepl = function () { var help = function () { console.log("nWelcome to the Appium CLI".cyan); console.log(" - Access the appium object via the object: 'appium'".grey); console.log(" - appium.run is the function to start the server".grey); console.log(" - args is the default params data structure".grey); console.log(" - set args.app then run appium.run(args);n".grey); return 'Thanks for asking'; }; ! help(); ! var r = repl.start('(appium): '); r.context.appium = appium; r.context.parser = parser(); r.context.help = help; r.context.args = { app: '/path/to/test/app' , udid: null , address: '127.0.0.1' , port: 4723 }; ! var connections = 0; var server = net.createServer(function (socket) { connections += 1; socket.setTimeout(5 * 60 * 1000, function () { socket.destroy(); }); repl.start("(appium): ", socket); }).listen(process.platform === "win32" ? ".pipenode-repl-sock-" + process.pid : "/tmp/node-repl-sock-" + process.pid); ! r.on('exit', function () { server.close(); process.exit(); });
  • 12.
    node_modules/appium/lib/server/main.js var http =require('http') , express = require('express') , favicon = require('serve-favicon') , bodyParser = require('body-parser') , methodOverride = require('method-override') , morgan = require('morgan') // logger , routing = require('./routing.js') , path = require('path') , appium = require('../appium.js') , parserWrap = require('./middleware').parserWrap , appiumVer = require('../../package.json').version , appiumRev = null , async = require('async') , helpers = require('./helpers.js') , logFinalWarning = require('../helpers.js').logFinalDeprecationWarning , getConfig = require('../helpers.js').getAppiumConfig , allowCrossDomain = helpers.allowCrossDomain , catchAllHandler = helpers.catchAllHandler , checkArgs = helpers.checkArgs , configureServer = helpers.configureServer , startListening = helpers.startListening , conditionallyPreLaunch = helpers.conditionallyPreLaunch , prepareTmpDir = helpers.prepareTmpDir , requestStartLoggingFormat = require('./helpers.js').requestStartLoggingFormat , requestEndLoggingFormat = require('./helpers.js').requestEndLoggingFormat , domainMiddleware = require('./helpers.js').domainMiddleware;
  • 13.
    var loggerjs =require('./server/logger.js') , logger = loggerjs.get('appium') , UUID = require('uuid-js') , _ = require('underscore') , Capabilities = require('./server/capabilities') , IOS = require('./devices/ios/ios.js') , Safari = require('./devices/ios/safari.js') , Android = require('./devices/android/android.js') , Selendroid = require('./devices/android/selendroid.js') , Chrome = require('./devices/android/chrome.js') , FirefoxOs = require('./devices/firefoxos/firefoxos.js') , jwpResponse = require('./devices/common.js').jwpResponse , status = require("./server/status.js"); ! var DT_IOS = "ios" , DT_SAFARI = "safari" , DT_ANDROID = "android" , DT_CHROME = "chrome" , DT_SELENDROID = "selendroid" , DT_FIREFOX_OS = "firefoxos"; node_modules/appium/lib/appium.js
  • 14.
    ! node_modules/appium/lib/server/capabilities.js var _ =require('underscore') , logger = require('./logger.js').get('appium') , warnDeprecated = require('../helpers.js').logDeprecationWarning; ! var capsConversion = {...}; ! var okObjects = [...]; ! var requiredCaps = [...]; ! var strictRequiredCaps = [...]; ! var generalCaps = strictRequiredCaps.concat([...]); ! var androidCaps = [...]; ! var iosCaps = [...];
  • 15.
  • 16.
    Capabilities IOS_CAPS_81 = { automationName:'Appium', platformName: :ios, platformVersion: '8.1', deviceName: 'iPhone 6 Plus', app: IOS_APP_PATH, bundleId: APP_BUNDLE_ID, ! calendarFormat: 'gregorian', ! sendKeyStrategy: 'grouped' ! localizableStringsDir: 'ja.lproj', language: 'ja', locale: 'ja_JP', ! screenshotWaitTimeout: 20 }
  • 17.
    create new plistbefore start app appium/lib/devices/ios/ios.js IOS.prototype.setDeviceTypeInInfoPlist = function (cb) { var plist = path.resolve(this.args.app, "Info.plist"); var dString = this.getDeviceString(); var isiPhone = dString.toLowerCase().indexOf("ipad") === -1; var deviceTypeCode = isiPhone ? 1 : 2; parsePlistFile(plist, function (err, obj) { if (err) { logger.error("Could not set the device type in Info.plist"); return cb(err, null); } else { var newPlist; obj.UIDeviceFamily = [deviceTypeCode]; if (binaryPlist) { newPlist = bplistCreate(obj); } else { newPlist = xmlplist.build(obj); } fs.writeFile(plist, newPlist, function (err) { if (err) { logger.error("Could not save new Info.plist"); cb(err); } else { logger.debug("Wrote new app Info.plist with device type"); cb(); } }.bind(this)); } }.bind(this)); };
  • 18.
  • 19.
    Capabilities ANDROID_CAPS = { automationName:'Appium', platformName: :android, platformVersion: '4.2', deviceName: ‘Android’, app: ANDROID_APP_PATH, ! unicodeKeyboard: true, ! fullReset: true, screenshotWaitTimeout: 20 }
  • 20.
    appium/lib/devices/android/android.js Android.prototype.pushAppium = function(cb) { logger.debug("Pushing appium bootstrap to device..."); var binPath = path.resolve(__dirname, "..", "..", "..", "build", "android_bootstrap", "AppiumBootstrap.jar"); fs.stat(binPath, function (err) { if (err) { cb(new Error("Could not find AppiumBootstrap.jar; please run " + "'grunt buildAndroidBootstrap'")); } else { this.adb.push(binPath, this.remoteTempPath(), cb); } }.bind(this)); }; ! Android.prototype.startApp = function (args, cb) { if (args.androidCoverage) { this.adb.androidCoverage(args.androidCoverage, args.appWaitPackage, args.appWaitActivity, cb); } else { this.adb.startApp({ pkg: args.appPackage, activity: args.appActivity, action: args.intentAction, category: args.intentCategory, flags: args.intentFlags, waitPkg: args.appWaitPackage, waitActivity: args.appWaitActivity, optionalIntentArguments: args.optionalIntentArguments, stopApp: args.stopAppOnReset }, cb); } };
  • 21.
  • 23.
  • 24.
  • 25.
    exports.checkValidLocStrat = function(strat, includeWeb, cb) { if (typeof includeWeb === "undefined") { includeWeb = false; } var validStrats = [ 'xpath', 'id', 'name', 'class name' ]; var nativeStrats = [ '-ios uiautomation', 'accessibility id', '-android uiautomator' ]; var webStrats = [ 'link text', 'css selector', 'tag name', 'partial link text' ]; var nativeDeprecations = {}; var webDeprecations = {}; var deprecations = {name: 'accessibility id'};
  • 26.
    XPath find_element :xpath, ”なんらかのXPath" accessibilityid find_element :accessibility_id, ”なんらかの要素" css selector find_element :css_selector, ”なんらかの要素" # Ruby Client使用時
  • 27.
    iOS: UIAutomation find_element :uiautomator, "newUiSelector() .className("android.widget.TextView") .text("#{element_name}");” find_element :uiautomation, ”$.mainApp().alert().buttons()[‘#{text}'];" Android: uiautomator
  • 28.
  • 29.
    Appium is justserver Appium1: $appium -p 4723 & Appium2: $appium -p 4725 -bp 4726 & Appium1 iOS App Android Appium appium_server = “http://127.0.0.1: 4723” appium_server = “http://127.0.0.1: 4725” adb: 4726
  • 30.
  • 31.
  • 32.
  • 33.
    DroidDriver • Clone fromGoogle to SauceLab(Appium) • https://github.com/appium/droiddriver • UiDeviceとか、UiElementとか使ってそう • https://github.com/appium/droiddriver/tree/master/droiddriver- android_support_test • android-support-test libraryと並行して使うユースケースもある が、android-support-test libraryはまだ開発されて日が浅いので optionとしての統合に留められている
  • 34.