From 0cd14dd8fd5804a904f404c6a5b6f9042da28a4e Mon Sep 17 00:00:00 2001 From: Jonathan Lipps Date: Thu, 27 Feb 2014 16:59:39 -0800 Subject: [PATCH] rearchitect configuration methods --- lib/appium.js | 112 +++++++++++++------------- lib/devices/android/android-common.js | 54 +++++++++++++ lib/devices/android/android.js | 72 ++--------------- lib/devices/android/chrome.js | 7 +- lib/devices/android/selendroid.js | 8 +- lib/devices/device.js | 50 ++++++++---- lib/devices/ios/ios.js | 65 ++++++++------- lib/devices/ios/safari.js | 11 +-- 8 files changed, 197 insertions(+), 182 deletions(-) diff --git a/lib/appium.js b/lib/appium.js index 41ca1dd3e..5428b9ba1 100644 --- a/lib/appium.js +++ b/lib/appium.js @@ -13,6 +13,12 @@ var routing = require('./server/routing.js') , FirefoxOs = require('./devices/firefoxos/firefoxos.js') , status = require("./server/status.js"); +var DT_IOS = "ios" + , DT_ANDROID = "android" + , DT_SELENDROID = "selendroid" + , DT_MOCK_IOS = "mock_ios" + , DT_FIREFOX_OS = "firefoxos"; + var Appium = function (args) { this.args = args; this.serverArgs = _.clone(args); @@ -24,7 +30,6 @@ var Appium = function (args) { this.desiredCapabilities = {}; this.oldDesiredCapabilities = {}; this.session = null; - this.tempFiles = []; this.preLaunched = false; this.fullReset = this.args.fullReset; this.fastReset = !this.args.fullReset && !this.args.noReset; @@ -34,7 +39,6 @@ var Appium = function (args) { this.commandTimeoutMs = this.defCommandTimeoutMs; this.origCommandTimeoutMs = this.commandTimeoutMs; this.commandTimeout = null; - this.origAppPath = null; }; Appium.prototype.attachTo = function (rest) { @@ -76,31 +80,39 @@ Appium.prototype.start = function (desiredCaps, cb) { }.bind(this)); }; -Appium.prototype.getDeviceType = function (args, desiredCaps) { +Appium.prototype.getDeviceType = function (args, caps) { + if (args.ipa) { + return DT_IOS; + } else if (args.safari) { + return DT_IOS; + } else if (args.androidPackage || args.avd) { + return DT_ANDROID; + } + try { + return this.getDeviceTypeFromApp(args.app || caps.app); + } catch (e) { + return this.getDeviceTypeFromCaps(caps); + } +}; +Appium.prototype.getDeviceTypeFromCaps = function (caps) { var errMsg = "A valid device type is required in the capabilities list"; - if (args.safari) { - return "ios"; - } else if (args.forceIphone) { - return "ios"; - } else if (args.forceIpad) { - return "ios"; - } - - if (desiredCaps.device) { - if (desiredCaps.device.toLowerCase().indexOf('iphone') !== -1) { - return "ios"; - } else if (desiredCaps.device.toLowerCase().indexOf('ipad') !== -1) { - return "ios"; - } else if (desiredCaps.device.toLowerCase().indexOf('selendroid') !== -1) { - return "selendroid"; - } else if (desiredCaps.device.toLowerCase().indexOf('firefox') !== -1) { - return "firefoxos"; - } else if (desiredCaps.device.toLowerCase().indexOf('android') !== -1) { - return "android"; - } else if (desiredCaps.device === "mock_ios") { - return "mock_ios"; + if (caps.device) { + if (caps.device.toLowerCase().indexOf('iphone') !== -1) { + return DT_IOS; + } else if (caps.device.toLowerCase().indexOf('ipad') !== -1) { + return DT_IOS; + } else if (caps.device.toLowerCase().indexOf('selendroid') !== -1) { + return DT_SELENDROID; + } else if (caps.device.toLowerCase().indexOf('firefox') !== -1) { + return DT_FIREFOX_OS; + } else if (caps.device.toLowerCase().indexOf('android') !== -1) { + return DT_ANDROID; + } else if (caps.device === DT_MOCK_IOS) { + return DT_MOCK_IOS; + } else { + throw new Error(errMsg); } } @@ -108,32 +120,28 @@ Appium.prototype.getDeviceType = function (args, desiredCaps) { }; Appium.prototype.getDeviceTypeFromApp = function (app) { - if (this.args.ipa) { - return "ios"; - } else if (this.args.androidPackage || this.args.avd) { - return "android"; - } else if (/\.app/.test(app)) { - return "ios"; + if (/\.app/.test(app)) { + return DT_IOS; } else if (/\.apk/.test(app)) { - return "android"; - } else if ((app && app.toLowerCase() === "safari") || this.args.safari) { - return "ios"; + return DT_ANDROID; + } else if ((app && app.toLowerCase() === "safari")) { + return DT_IOS; } else if ((app && app.toLowerCase() === "settings")) { - return "ios"; + return DT_IOS; } - throw new Error("Could not determine your device type from --app"); + throw new Error("Could not determine your device type from app '" + app + "'"); }; Appium.prototype.isMockIos = function () { - return this.deviceType === "mock_ios"; + return this.deviceType === DT_MOCK_IOS; }; Appium.prototype.isIos = function () { - return this.deviceType === "ios"; + return this.deviceType === DT_IOS; }; Appium.prototype.isAndroid = function () { - return this.deviceType === "android"; + return this.deviceType === DT_ANDROID; }; Appium.prototype.isChrome = function (args, caps) { @@ -153,11 +161,11 @@ Appium.prototype.isChrome = function (args, caps) { }; Appium.prototype.isSelendroid = function () { - return this.deviceType === "selendroid"; + return this.deviceType === DT_SELENDROID; }; Appium.prototype.isFirefoxOS = function () { - return this.deviceType === "firefoxos"; + return this.deviceType === DT_FIREFOX_OS; }; Appium.prototype.getAppExt = function () { @@ -165,14 +173,9 @@ Appium.prototype.getAppExt = function () { }; Appium.prototype.configure = function (args, desiredCaps, cb) { - this.origAppPath = null; var deviceType; try { - if (args.launch) { - deviceType = this.getDeviceTypeFromApp(args.app); - } else { - deviceType = this.getDeviceType(desiredCaps); - } + deviceType = this.getDeviceType(args, desiredCaps); } catch (e) { logger.error(e.message); return cb(e); @@ -195,7 +198,6 @@ Appium.prototype.configure = function (args, desiredCaps, cb) { }; Appium.prototype.invoke = function (cb) { - if (this.sessionOverride || this.sessionId === null) { this.sessionId = UUID.create().hex; logger.info('Creating new appium session ' + this.sessionId); @@ -239,20 +241,19 @@ Appium.prototype.invoke = function (cb) { } else { return cb(new Error("Requested a new session but one was in progress")); } - }; Appium.prototype.getDevice = function (deviceType) { var caps = this.desiredCapabilities; var Device = (function () { switch (deviceType) { - case "ios": + case DT_IOS: return (caps.safari || caps.iwebview) ? Safari : IOS; - case "android": + case DT_ANDROID: return this.isChrome(this.args, caps) ? Chrome : Android; - case "selendroid": + case DT_SELENDROID: return Selendroid; - case "firefoxos": + case DT_FIREFOX_OS: return FirefoxOs; default: throw new Error("Tried to start a device that doesn't exist: " + @@ -261,13 +262,9 @@ Appium.prototype.getDevice = function (deviceType) { }); return new Device(); - if (deviceType === "ios") { + if (deviceType === DT_IOS) { var iosOpts = { - rest: this.rest - , webSocket: this.webSocket - , app: this.args.app - , ipa: this.args.ipa , bundleId: this.args.bundleId || this.desiredCapabilities.bundleId , udid: this.args.udid , fullReset: this.fullReset @@ -294,7 +291,6 @@ Appium.prototype.getDevice = function (deviceType) { , desiredCapabilities: this.desiredCapabilities , logNoColors: this.args.logNoColors , flakeyRetries: this.args.backendRetries - , origAppPath: this.origAppPath , autoAcceptAlerts: this.desiredCapabilities.autoAcceptAlerts , keepKeyChains: this.args.keepKeyChains || this.desiredCapabilities.keepKeyChains }; diff --git a/lib/devices/android/android-common.js b/lib/devices/android/android-common.js index a3c26f618..7008f5cdb 100644 --- a/lib/devices/android/android-common.js +++ b/lib/devices/android/android-common.js @@ -16,6 +16,60 @@ var logTypesSupported = { var androidCommon = {}; +androidCommon.configure = function (args, caps, cb) { + this._deviceConfigure(args, caps); + this.setAndroidArgs(); + if (!this.args.androidActivity) { + return cb(new Error("You need to pass in the app-activity desired " + + "capability or server param")); + } + if (!this.args.androidPackage) { + return cb(new Error("You need to pass in the app-package desired " + + "capability or server param")); + } + + if (this.args.app) { + this.configureApp(cb); + } else if (this.args.androidPackage) { + this.args.app = null; + logger.info("Didn't get app but did get Android package, will attempt to " + + "launch it on the device"); + cb(null); + } else { + var msg = "No app set; either start appium with --app or pass in an 'app' " + + "value in desired capabilities, or set androidPackage to launch pre-" + + "existing app on device"; + logger.error(msg); + cb(new Error(msg)); + } +}; + +androidCommon.configureApp = function (args, caps, cb) { + this._deviceConfigureApp(args, caps, function (err) { + if (err) { + if (this.appIsPackageOrBundle(args.app)) { + // we have a package instead of app + this.args.androidPackage = args.app; + this.args.app = null; + logger.info("App is an Android package, will attempt to run on device"); + return cb(); + } + return cb(err); + } + cb(); + }.bind(this)); +}; + +androidCommon.setAndroidArgs = function () { + this.setArgFromCap("androidPackage", "app-package"); + this.setArgFromCap("androidActivity", "app-activity"); + this.setArgFromCap("androidWaitPackage", "app-wait-package"); + this.setArgFromCap("androidWaitActivity", "app-wait-activity"); + this.setArgFromCap("androidDeviceReadyTimeout", "device-ready-timeout"); + this.setArgFromCap("androidCoverage", "androidCoverage"); + this.setArgFromCap("compressXml", "compressXml"); +}; + androidCommon.background = function (secs, cb) { this.adb.getFocusedPackageAndActivity(function (err, pack, activity) { if (err) return cb(err); diff --git a/lib/devices/android/android.js b/lib/devices/android/android.js index 14ef8a968..0ca9ff1d4 100644 --- a/lib/devices/android/android.js +++ b/lib/devices/android/android.js @@ -21,15 +21,15 @@ var errors = require('../../server/errors.js') , UnknownError = errors.UnknownError; var Android = function () { - this.init(opts); + this.init(); }; _.extend(Android.prototype, Device.prototype); -Android.prototype._device_init = Device.prototype.init; - +Android.prototype._deviceInit = Device.prototype.init; Android.prototype.init = function () { - this._device_init(); + this._deviceInit(); + this.appExt = ".apk"; this.compressXml = opts.compressXml; this.skipUninstall = opts.fastReset || !opts.reset; this.fastClear = opts.fastClear !== false; @@ -91,70 +91,8 @@ Android.prototype.init = function () { _.extend(this.capabilities, opts.desiredCapabilities); }; -Android.prototype.configure = function (args, caps, cb) { - args = this.setAndroidArgs(args, caps); - //chrome apps do not need this checking, native and normal apps do - var isChrome = args.app && _.contains(["chrome", "browser", "chromium"], - args.app.toLowerCase()); - if (!isChrome) { - if (!args.androidActivity) { - return cb(new Error("You need to pass in the app-activity desired " + - "capability or server param")); - } - if (!args.androidPackage) { - return cb(new Error("You need to pass in the app-package desired " + - "capability or server param")); - } - } - - this.setArgsAndCaps(args, caps); - - if (args.app) { - this.configureApp(args, caps, cb); - } else if (args.androidPackage) { - this.args.app = null; - logger.info("Didn't get app but did get Android package, will attempt to " + - "launch it on the device"); - cb(null); - } else { - var msg = "No app set; either start appium with --app or pass in an 'app' " + - "value in desired capabilities, or set androidPackage to launch pre-" + - "existing app on device"; - logger.error(msg); - cb(new Error(msg)); - } -}; - +Android.prototype._deviceConfigure = Device.prototype.configure; Android.prototype._deviceConfigureApp = Device.prototype.configureApp; -Android.prototype.configureApp = function (args, caps, cb) { - this._deviceConfigureApp(args, caps, function (err) { - if (err) { - if (this.appIsPackageOrBundle(args.app)) { - // we have a package instead of app - this.args.androidPackage = args.app; - this.args.app = null; - logger.info("App is an Android package, will attempt to run on device"); - return cb(); - } - return cb(err); - } - cb(); - }.bind(this)); -}; - -Android.prototype.setAndroidArgs = function (args, caps) { - var setArgFromCaps = function (arg, cap) { - args[arg] = caps[cap] || args[arg]; - }; - setArgFromCaps("androidPackage", "app-package"); - setArgFromCaps("androidActivity", "app-activity"); - setArgFromCaps("androidWaitPackage", "app-wait-package"); - setArgFromCaps("androidWaitActivity", "app-wait-activity"); - setArgFromCaps("androidDeviceReadyTimeout", "device-ready-timeout"); - setArgFromCaps("androidCoverage", "androidCoverage"); - setArgFromCaps("compressXml", "compressXml"); - return args; -}; Android.prototype.start = function (cb, onDie) { this.launchCb = cb; diff --git a/lib/devices/android/chrome.js b/lib/devices/android/chrome.js index 43e55d377..4c76dbe0e 100644 --- a/lib/devices/android/chrome.js +++ b/lib/devices/android/chrome.js @@ -20,11 +20,12 @@ _.extend(ChromeAndroid.prototype, Android.prototype); ChromeAndroid.prototype.configure = function (args, caps, cb) { logger.info("Looks like we want chrome on android"); - this.setArgsAndCaps(args, caps); - if (args.app.toLowerCase() === "chromium") { + this._deviceConfigure(args, caps); + var app = this.appString(); + if (app === "chromium") { this.args.androidPackage = "org.chromium.chrome.testshell"; this.args.androidActivity = "org.chromium.chrome.testshell.Main"; - } else if (args.app.toLowerCase() === "browser") { + } else if (app === "browser") { this.args.androidPackage = "com.android.browser"; this.args.androidActivity = "com.android.browser.BrowserActivity"; } else { diff --git a/lib/devices/android/selendroid.js b/lib/devices/android/selendroid.js index 4f4d171f2..a921c17c5 100644 --- a/lib/devices/android/selendroid.js +++ b/lib/devices/android/selendroid.js @@ -1,6 +1,8 @@ "use strict"; var ADB = require('./adb.js') + , Device = require('../device.js') + , Android = require('./android.js') , mkdirp = require('mkdirp') , _ = require('underscore') , deviceCommon = require('../common.js') @@ -17,7 +19,7 @@ var ADB = require('./adb.js') , androidCommon = require('./android-common.js') , path = require('path'); -var Selendroid = function (opts) { +var Selendroid = function () { this.opts = opts; this.opts.devicePort = 8080; this.skipUninstall = opts.fastReset || !opts.reset; @@ -62,6 +64,10 @@ var Selendroid = function (opts) { ]; }; +_.extend(Selendroid.prototype, Device.prototype); +Selendroid.prototype._deviceConfigure = Device.prototype.configure; +Selendroid.prototype._deviceConfigureApp = Device.prototype.configureApp; + Selendroid.prototype.start = function (cb) { logger.info("Starting selendroid server"); this.adb = new ADB(this.opts); diff --git a/lib/devices/device.js b/lib/devices/device.js index 5d846da7e..8eb3800d4 100644 --- a/lib/devices/device.js +++ b/lib/devices/device.js @@ -11,32 +11,52 @@ var fs = require('fs') ; var Device = function () { + throw new Error("Cannot instantiate Device directly"); }; Device.prototype.init = function () { this.appExt = null; this.tempFiles = []; + this.args = {}; + this.capabilities = {}; + this.capOverrides = [ + "app" + , "launchTimeout" + ]; }; -Device.prototype.setArgsAndCaps = function (args, caps) { +Device.prototype.configure = function (args, caps) { + _.each(this.capOverrides, function (cap) { + this.setArgFromCap(cap, cap); + }.bind(this)); _.extend(this.args, args); _.extend(this.capabilities, caps); }; -Device.prototype.configureApp = function (args, caps, cb) { - if (this.appIsLocal(args.app)) { - this.configureLocalApp(args, caps, cb); - } else if (this.appIsDownloaded(args.app)) { - this.configureDownloadedApp(args, caps, cb); - } else { - cb(new Error("Bad app: " + args.app + ". Apps need to be absolute local " + - "path, URL to compressed file, or special app name")); +Device.prototype.setArgFromCap = function (arg, cap) { + if (typeof this.capabilities[cap] !== "undefined") { + this.args[arg] = this.capabilities[cap]; } }; -Device.prototype.configureLocalApp = function (args, caps, cb) { - var appPath = args.app; - var origin = caps.app ? "desired caps" : "command line"; +Device.prototype.appString = function () { + return this.args.app ? this.args.app.toString().toLowerCase() : ''; +}; + +Device.prototype.configureApp = function (cb) { + if (this.appIsLocal(this.args.app)) { + this.configureLocalApp(cb); + } else if (this.appIsDownloaded(this.args.app)) { + this.configureDownloadedApp(cb); + } else { + cb(new Error("Bad app: " + this.args.app + ". Apps need to be absolute " + + "local path, URL to compressed file, or special app name")); + } +}; + +Device.prototype.configureLocalApp = function (cb) { + var appPath = this.args.app; + var origin = this.capabilities.app ? "desired caps" : "command line"; var ext = appPath.substring(appPath.length - 4); if (ext === this.appExt) { this.args.app = appPath; @@ -79,9 +99,9 @@ Device.prototype.appIsPackageOrBundle = function (app) { return (/^([a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]+)+$/).test(app); }; -Device.prototype.configureDownloadedApp = function (args, caps, cb) { - var origin = caps.app ? "desired caps" : "command line"; - var appUrl = args.app; +Device.prototype.configureDownloadedApp = function (cb) { + var origin = this.capabilities.app ? "desired caps" : "command line"; + var appUrl = this.args.app ? this.args.app : ''; if (appUrl.substring(appUrl.length - 4) === ".apk") { try { downloadFile(appUrl, ".apk", function (appPath) { diff --git a/lib/devices/ios/ios.js b/lib/devices/ios/ios.js index 5f0363e1c..cc698ea62 100644 --- a/lib/devices/ios/ios.js +++ b/lib/devices/ios/ios.js @@ -48,24 +48,20 @@ var IOS = function () { _.extend(IOS.prototype, Device.prototype); -IOS.prototype._device_init = Device.prototype.init; -IOS.prototype.init = function (args, caps) { - this._device_init(); - this.args = { - app: null - }; +IOS.prototype._deviceInit = Device.prototype.init; +IOS.prototype.init = function () { + this._deviceInit(); + this.appExt = ".app"; this.capabilities = { - version: '0.0' - , webStorageEnabled: false - , locationContextEnabled: false - , browserName: 'iOS' - , platform: 'MAC' - , javascriptEnabled: true - , databaseEnabled: false - , takesScreenshot: true - }; - this.origAppPath = args.origAppPath; - this.ipa = args.ipa; + version: '0.0' + , webStorageEnabled: false + , locationContextEnabled: false + , browserName: 'iOS' + , platform: 'MAC' + , javascriptEnabled: true + , databaseEnabled: false + , takesScreenshot: true + }; this.bundleId = args.bundleId || null; this.udid = args.udid; this.verbose = args.verbose; @@ -122,10 +118,11 @@ IOS.prototype.init = function (args, caps) { this.localizableStrings = {}; }; +IOS.prototype._deviceConfigure = Device.prototype.configure; IOS.prototype.configure = function (args, caps, cb) { - this.setArgsAndCaps(args, caps); - if (args.app) { - this.configureApp(args, caps, cb); + this._deviceConfigure(args, caps); + if (this.args.app) { + this.configureApp(cb); } else { var msg = "No app set; either start appium with --app or use 'app' cap"; logger.error(msg); @@ -134,20 +131,21 @@ IOS.prototype.configure = function (args, caps, cb) { }; IOS.prototype._deviceConfigureApp = Device.prototype.configureApp; -IOS.prototype.configureApp = function (args, caps, cb) { +IOS.prototype.configureApp = function (cb) { this._deviceConfigureApp(function (err) { + var app = this.appString(); if (err) { - if (args.app.toLowerCase() === "iwebview") { + if (app === "iwebview") { this.capabilities.iwebview = true; this.args.app = path.resolve(__dirname, "../../../build/WebViewApp/WebViewApp.app"); return this.configureLocalApp(this.args, this.capabilities, cb); - } else if (args.app.toLowerCase() === "settings") { - return this.configurePreferences(caps, cb); - } else if (this.appIsPackageOrBundle(args.app)) { + } else if (app === "settings") { + return this.configurePreferences(cb); + } else if (this.appIsPackageOrBundle(app)) { // we have a bundle ID logger.info("App is an iOS bundle, will attempt to run as pre-existing"); - this.args.bundleId = args.app; + this.args.bundleId = app; this.args.app = null; return cb(); } @@ -157,9 +155,10 @@ IOS.prototype.configureApp = function (args, caps, cb) { }.bind(this)); }; -IOS.prototype.configurePreferences = function (caps, cb) { +IOS.prototype.configurePreferences = function (cb) { logger.info("Configuring settings app"); var prefsVer = null; + var caps = this.capabilities; if (typeof caps.version !== "undefined" && caps.version) { prefsVer = caps.version; } @@ -317,9 +316,9 @@ IOS.prototype.makeInstruments = function () { IOS.prototype.onInstrumentsLaunch = function (cb) { logger.info('Instruments launched. Starting poll loop for new commands.'); this.instruments.setDebug(true); - if (this.origAppPath) { + if (this.args.origAppPath) { logger.info("Copying app back to its original place"); - return ncp(this.args.app, this.origAppPath, cb); + return ncp(this.args.app, this.args.origAppPath, cb); } cb(); @@ -670,9 +669,9 @@ IOS.prototype.installToRealDevice = function (cb) { if (this.udid) { if (this.isSafariLauncherApp) { this.installSafariLauncher(cb); - } else if (this.ipa && this.bundleId) { + } else if (this.args.ipa && this.bundleId) { this.installIpa(cb); - } else if (this.ipa) { + } else if (this.args.ipa) { var msg = "You specified a UDID and ipa but did not include the bundle " + "id"; logger.error(msg); @@ -727,7 +726,7 @@ IOS.prototype.getIDeviceObj = function () { }; IOS.prototype.installIpa = function (cb) { - logger.info("Installing ipa found at " + this.ipa); + logger.info("Installing ipa found at " + this.args.ipa); this.realDevice = this.getIDeviceObj(); var d = this.realDevice; async.waterfall([ @@ -741,7 +740,7 @@ IOS.prototype.installIpa = function (cb) { cb(); } }.bind(this), - function (cb) { d.installAndWait(this.ipa, this.bundleId, cb); }.bind(this) + function (cb) { d.installAndWait(this.args.ipa, this.bundleId, cb); }.bind(this) ], cb); }; diff --git a/lib/devices/ios/safari.js b/lib/devices/ios/safari.js index 34992046f..20b92410c 100644 --- a/lib/devices/ios/safari.js +++ b/lib/devices/ios/safari.js @@ -18,21 +18,22 @@ _.extend(Safari.prototype, IOS.prototype); Safari.prototype.configure = function (args, caps, cb) { logger.info("Configuring Safari session"); - this.setArgsAndCaps(args, caps); + this._deviceConfigure(args, caps); this.capabilities.safari = true; - if (args.udid) { + if (this.args.udid) { this.dontCleanupSession = true; this.args.isSafariLauncherApp = true; this.args.app = path.resolve(__dirname, "../../../build/SafariLauncher/SafariLauncher.zip"); - this.configureLocalApp(this.args, this.capabilities, cb); + this.configureLocalApp(cb); } else { - this.configureSafari(this.capabilities, cb); + this.configureSafari(cb); } }; -Safari.prototype.configureSafari = function (caps, cb) { +Safari.prototype.configureSafari = function (cb) { var safariVer = null; + var caps = this.capabilities; if (typeof caps.version !== "undefined" && caps.version) { safariVer = caps.version; }