Best JavaScript code snippet using appium-android-driver
ios.js
Source:ios.js
1"use strict";2var path = require('path')3 , rimraf = require('rimraf')4 , ncp = require('ncp').ncp5 , fs = require('fs')6 , _ = require('underscore')7 , which = require('which')8 , logger = require('../../server/logger.js').get('appium')9 , exec = require('child_process').exec10 , spawn = require('child_process').spawn11 , bplistCreate = require('bplist-creator')12 , bplistParse = require('bplist-parser')13 , xmlplist = require('plist')14 , Device = require('../device.js')15 , Instruments = require('./instruments.js')16 , xcode = require('../../future.js').xcode17 , errors = require('../../server/errors.js')18 , deviceCommon = require('../common.js')19 , iOSLog = require('./ios-log.js')20 , iOSCrashLog = require('./ios-crash-log.js')21 , status = require("../../server/status.js")22 , iDevice = require('node-idevice')23 , async = require('async')24 , iOSController = require('./ios-controller.js')25 , iOSHybrid = require('./ios-hybrid.js')26 , settings = require('./settings.js')27 , Simulator = require('./simulator.js')28 , prepareBootstrap = require('./uiauto').prepareBootstrap29 , CommandProxy = require('./uiauto').CommandProxy30 , UnknownError = errors.UnknownError31 , binaryPlist = true32 , Args = require("vargs").Constructor33 , logCustomDeprecationWarning = require('../../helpers.js').logCustomDeprecationWarning;34// XML Plist library helper35var parseXmlPlistFile = function (plistFilename, cb) {36 try {37 var xmlContent = fs.readFileSync(plistFilename, 'utf8');38 var result = xmlplist.parse(xmlContent);39 return cb(null, result);40 } catch (ex) {41 return cb(ex);42 }43};44var parsePlistFile = function (plist, cb) {45 bplistParse.parseFile(plist, function (err, obj) {46 if (err) {47 logger.debug("Could not parse plist file (as binary) at " + plist);48 logger.info("Will try to parse the plist file as XML");49 parseXmlPlistFile(plist, function (err, obj) {50 if (err) {51 logger.debug("Could not parse plist file (as XML) at " + plist);52 return cb(err, null);53 } else {54 logger.debug("Parsed app Info.plist (as XML)");55 binaryPlist = false;56 cb(null, obj);57 }58 });59 } else {60 binaryPlist = true;61 if (obj.length) {62 logger.debug("Parsed app Info.plist (as binary)");63 cb(null, obj[0]);64 } else {65 cb(new Error("Binary Info.plist appears to be empty"));66 }67 }68 });69};70var IOS = function () {71 this.init();72};73_.extend(IOS.prototype, Device.prototype);74IOS.prototype._deviceInit = Device.prototype.init;75IOS.prototype.init = function () {76 this._deviceInit();77 this.appExt = ".app";78 this.capabilities = {79 webStorageEnabled: false80 , locationContextEnabled: false81 , browserName: 'iOS'82 , platform: 'MAC'83 , javascriptEnabled: true84 , databaseEnabled: false85 , takesScreenshot: true86 , networkConnectionEnabled: false87 };88 this.xcodeVersion = null;89 this.iOSSDKVersion = null;90 this.iosSimProcess = null;91 this.iOSSimUdid = null;92 this.logs = {};93 this.instruments = null;94 this.commandProxy = null;95 this.initQueue();96 this.onInstrumentsDie = function () {};97 this.stopping = false;98 this.cbForCurrentCmd = null;99 this.remote = null;100 this.curContext = null;101 this.curWebFrames = [];102 this.selectingNewPage = false;103 this.processingRemoteCmd = false;104 this.remoteAppKey = null;105 this.windowHandleCache = [];106 this.webElementIds = [];107 this.implicitWaitMs = 0;108 this.asyncWaitMs = 0;109 this.pageLoadMs = 60000;110 this.asyncResponseCb = null;111 this.returnedFromExecuteAtom = {};112 this.executedAtomsCounter = 0;113 this.curCoords = null;114 this.curWebCoords = null;115 this.onPageChangeCb = null;116 this.supportedStrategies = ["name", "xpath", "id", "-ios uiautomation",117 "class name", "accessibility id"];118 this.landscapeWebCoordsOffset = 0;119 this.localizableStrings = {};120 this.keepAppToRetainPrefs = false;121 this.isShuttingDown = false;122};123IOS.prototype._deviceConfigure = Device.prototype.configure;124IOS.prototype.configure = function (args, caps, cb) {125 var msg;126 this._deviceConfigure(args, caps);127 this.setIOSArgs();128 if (this.args.locationServicesAuthorized && !this.args.bundleId) {129 msg = "You must set the bundleId cap if using locationServicesEnabled";130 logger.error(msg);131 return cb(new Error(msg));132 }133 // on iOS8 we can use a bundleId to launch an app on the simulator, but134 // on previous versions we can only do so on a real device, so we need135 // to do a check of which situation we're in136 var ios8 = caps.platformVersion &&137 parseFloat(caps.platformVersion) >= 8;138 if (!this.args.app &&139 !((ios8 || this.args.udid) && this.args.bundleId)) {140 msg = "Please provide the 'app' or 'browserName' capability or start " +141 "appium with the --app or --browser-name argument. Alternatively, " +142 "you may provide the 'bundleId' and 'udid' capabilities for an app " +143 "under test on a real device.";144 logger.error(msg);145 return cb(new Error(msg));146 }147 if (parseFloat(caps.platformVersion) < 7.1) {148 logCustomDeprecationWarning('iOS version', caps.platformVersion,149 'iOS ' + caps.platformVersion + ' support has ' +150 'been deprecated and will be removed in a ' +151 'future version of Appium.');152 }153 return this.configureApp(cb);154};155IOS.prototype.setIOSArgs = function () {156 this.args.withoutDelay = !this.args.nativeInstrumentsLib;157 this.args.reset = !this.args.noReset;158 this.args.initialOrientation = this.capabilities.deviceOrientation ||159 this.args.orientation ||160 "PORTRAIT";161 this.useRobot = this.args.robotPort > 0;162 this.args.robotUrl = this.useRobot ?163 "http://" + this.args.robotAddress + ":" + this.args.robotPort + "" :164 null;165 this.curOrientation = this.args.initialOrientation;166 this.sock = path.resolve(this.args.tmpDir || '/tmp', 'instruments_sock');167 this.perfLogEnabled = !!(typeof this.args.loggingPrefs === 'object' && this.args.loggingPrefs.performance);168};169IOS.prototype.configureApp = function (cb) {170 var _cb = cb;171 cb = function (err) {172 if (err) {173 err = new Error("Bad app: " + this.args.app + ". App paths need to " +174 "be absolute, or relative to the appium server " +175 "install dir, or a URL to compressed file, or a " +176 "special app name. cause: " + err);177 }178 _cb(err);179 }.bind(this);180 var app = this.appString();181 // if the app name is a bundleId assign it to the bundleId property182 if (!this.args.bundleId && this.appIsPackageOrBundle(app)) {183 this.args.bundleId = app;184 }185 if (app !== "" && app.toLowerCase() === "settings") {186 if (parseFloat(this.args.platformVersion) >= 8) {187 logger.debug("We're on iOS8+ so not copying preferences app");188 this.args.bundleId = "com.apple.Preferences";189 this.args.app = null;190 }191 cb();192 } else if (this.args.bundleId &&193 this.appIsPackageOrBundle(this.args.bundleId) &&194 (app === "" || this.appIsPackageOrBundle(app))) {195 // we have a bundle ID, but no app, or app is also a bundle196 logger.debug("App is an iOS bundle, will attempt to run as pre-existing");197 cb();198 } else {199 Device.prototype.configureApp.call(this, cb);200 }201};202IOS.prototype.removeInstrumentsSocket = function (cb) {203 var removeSocket = function (innerCb) {204 logger.debug("Removing any remaining instruments sockets");205 rimraf(this.sock, function (err) {206 if (err) return innerCb(err);207 logger.debug("Cleaned up instruments socket " + this.sock);208 innerCb();209 }.bind(this));210 }.bind(this);211 removeSocket(cb);212};213IOS.prototype.getNumericVersion = function () {214 return parseFloat(this.args.platformVersion);215};216IOS.prototype.startRealDevice = function (cb) {217 async.series([218 this.removeInstrumentsSocket.bind(this),219 this.detectUdid.bind(this),220 this.parseLocalizableStrings.bind(this),221 this.setBundleIdFromApp.bind(this),222 this.createInstruments.bind(this),223 this.startLogCapture.bind(this),224 this.installToRealDevice.bind(this),225 this.startInstruments.bind(this),226 this.onInstrumentsLaunch.bind(this),227 this.configureBootstrap.bind(this),228 this.setBundleId.bind(this),229 this.setInitialOrientation.bind(this),230 this.initAutoWebview.bind(this),231 this.waitForAppLaunched.bind(this),232 ], function (err) {233 cb(err);234 });235};236IOS.prototype.startSimulator = function (cb) {237 async.series([238 this.removeInstrumentsSocket.bind(this),239 this.setXcodeVersion.bind(this),240 this.setiOSSDKVersion.bind(this),241 this.checkSimAvailable.bind(this),242 this.createSimulator.bind(this),243 this.moveBuiltInApp.bind(this),244 this.detectUdid.bind(this),245 this.parseLocalizableStrings.bind(this),246 this.setBundleIdFromApp.bind(this),247 this.createInstruments.bind(this),248 this.setDeviceInfo.bind(this),249 this.checkPreferences.bind(this),250 this.runSimReset.bind(this),251 this.isolateSimDevice.bind(this),252 this.setLocale.bind(this),253 this.setPreferences.bind(this),254 this.startLogCapture.bind(this),255 this.prelaunchSimulator.bind(this),256 this.startInstruments.bind(this),257 this.onInstrumentsLaunch.bind(this),258 this.configureBootstrap.bind(this),259 this.setBundleId.bind(this),260 this.setInitialOrientation.bind(this),261 this.initAutoWebview.bind(this),262 this.waitForAppLaunched.bind(this),263 ], function (err) {264 cb(err);265 });266};267IOS.prototype.start = function (cb, onDie) {268 if (this.instruments !== null) {269 var msg = "Trying to start a session but instruments is still around";270 logger.error(msg);271 return cb(new Error(msg));272 }273 if (typeof onDie === "function") {274 this.onInstrumentsDie = onDie;275 }276 if (this.args.udid) {277 this.startRealDevice(cb);278 } else {279 this.startSimulator(cb);280 }281};282IOS.prototype.createInstruments = function (cb) {283 logger.debug("Creating instruments");284 this.commandProxy = new CommandProxy({ sock: this.sock });285 this.makeInstruments(function (err, instruments) {286 if (err) return cb(err);287 this.instruments = instruments;288 cb();289 }.bind(this));290};291IOS.prototype.startInstruments = function (cb) {292 cb = _.once(cb);293 var treatError = function (err, cb) {294 if (!_.isEmpty(this.logs)) {295 this.logs.syslog.stopCapture();296 this.logs = {};297 }298 this.postCleanup(function () {299 cb(err);300 });301 }.bind(this);302 logger.debug("Starting command proxy.");303 this.commandProxy.start(304 function onFirstConnection(err) {305 // first let instruments know so that it does not restart itself306 this.instruments.launchHandler(err);307 // then we call the callback308 cb(err);309 }.bind(this)310 , function regularCallback(err) {311 if (err) return treatError(err, cb);312 logger.debug("Starting instruments");313 this.instruments.start(314 function (err) {315 if (err) return treatError(err, cb);316 // we don't call cb here, waiting for first connection or error317 }.bind(this)318 , function (code) {319 if (!this.shouldIgnoreInstrumentsExit()) {320 this.onUnexpectedInstrumentsExit(code);321 }322 }.bind(this)323 );324 }.bind(this)325 );326};327IOS.prototype.makeInstruments = function (cb) {328 // at the moment all the logging in uiauto is at debug level329 // TODO: be able to use info in appium-uiauto330 var bootstrap = prepareBootstrap({331 sock: this.sock,332 interKeyDelay: this.args.interKeyDelay,333 justLoopInfinitely: false,334 autoAcceptAlerts: !(!this.args.autoAcceptAlerts || this.args.autoAcceptAlerts === 'false'),335 autoDismissAlerts: !(!this.args.autoDismissAlerts || this.args.autoDismissAlerts === 'false'),336 sendKeyStrategy: this.args.sendKeyStrategy || (this.args.udid ? 'grouped' : 'oneByOne')337 });338 bootstrap.then(function (bootstrapPath) {339 var instruments = new Instruments({340 // on real devices bundleId is always used341 app: (!this.args.udid ? this.args.app : null) || this.args.bundleId342 , udid: this.args.udid343 , processArguments: this.args.processArguments344 , ignoreStartupExit: this.shouldIgnoreInstrumentsExit()345 , bootstrap: bootstrapPath346 , template: this.args.automationTraceTemplatePath347 , instrumentsPath: this.args.instrumentsPath348 , withoutDelay: this.args.withoutDelay349 , platformVersion: this.args.platformVersion350 , webSocket: this.args.webSocket351 , launchTimeout: this.args.launchTimeout352 , flakeyRetries: this.args.backendRetries353 , simulatorSdkAndDevice: this.iOSSDKVersion >= 7.1 ? this.getDeviceString() : null354 , tmpDir: path.resolve(this.args.tmpDir , 'appium-instruments')355 , traceDir: this.args.traceDir356 });357 cb(null, instruments);358 }.bind(this), cb).fail(cb);359};360IOS.prototype.shouldIgnoreInstrumentsExit = function () {361 return false;362};363IOS.prototype.onInstrumentsLaunch = function (cb) {364 logger.debug('Instruments launched. Starting poll loop for new commands.');365 this.instruments.setDebug(true);366 if (this.args.origAppPath) {367 logger.debug("Copying app back to its original place");368 return ncp(this.args.app, this.args.origAppPath, cb);369 }370 cb();371};372IOS.prototype.setBundleId = function (cb) {373 if (this.args.bundleId) {374 // We already have a bundle Id375 cb();376 } else {377 this.proxy('au.bundleId()', function (err, bId) {378 if (err) return cb(err);379 logger.debug('Bundle ID for open app is ' + bId.value);380 this.args.bundleId = bId.value;381 cb();382 }.bind(this));383 }384};385IOS.prototype.setInitialOrientation = function (cb) {386 if (typeof this.args.initialOrientation === "string" &&387 _.contains(["LANDSCAPE", "PORTRAIT"],388 this.args.initialOrientation.toUpperCase())389 ) {390 logger.debug("Setting initial orientation to " + this.args.initialOrientation);391 var command = ["au.setScreenOrientation('",392 this.args.initialOrientation.toUpperCase(), "')"].join('');393 this.proxy(command, function (err, res) {394 if (err || res.status !== status.codes.Success.code) {395 logger.warn("Setting initial orientation did not work!");396 } else {397 this.curOrientation = this.args.initialOrientation;398 }399 cb();400 }.bind(this));401 } else {402 cb();403 }404};405IOS.isSpringBoard = function (uiAppObj) {406// Test for iOS homescreen (SpringBoard). AUT occassionally start the sim, but fails to load407// the app. If that occurs, getSourceForElementFoXML will return a doc object that meets our408// app-check conditions, resulting in a false positive. This function tests the UiApplication409// property's meta data to ensure that the Appium doesn't confuse SpringBoard with the app410// under test.411 return _.propertyOf(uiAppObj['@'])('name') === 'SpringBoard';412};413IOS.prototype.waitForAppLaunched = function (cb) {414 // on iOS8 in particular, we can get a working session before the app415 // is ready to respond to commands; in that case the source will be empty416 // so we just spin until it's not417 var condFn;418 if (this.args.waitForAppScript) {419 // the default getSourceForElementForXML does not fit some use case, so making this customizable.420 // TODO: collect script from customer and propose several options, please comment in issue #4190.421 logger.debug("Using custom script to wait for app start:" + this.args.waitForAppScript);422 condFn = function (cb) {423 this.proxy('try{\n' + this.args.waitForAppScript +424 '\n} catch(err) { $.log("waitForAppScript err: " + error); false; };',425 function (err, res) {426 cb(!!res.value, err);427 });428 }.bind(this);429 } else {430 logger.debug("Waiting for app source to contain elements");431 condFn = function (cb) {432 this.getSourceForElementForXML(null, function (err, res) {433 if (err || !res || res.status !== status.codes.Success.code) {434 return cb(false, err);435 }436 var sourceObj, appEls;437 try {438 sourceObj = JSON.parse(res.value);439 appEls = sourceObj.UIAApplication['>'];440 if (appEls.length > 0 && !IOS.isSpringBoard(sourceObj.UIAApplication)) {441 return cb(true);442 } else {443 return cb(false, new Error("App did not have elements"));444 }445 } catch (e) {446 return cb(false, new Error("Couldn't parse JSON source"));447 }448 return cb(true, err);449 });450 }.bind(this);451 }452 this.waitForCondition(10000, condFn, cb, 500);453};454IOS.prototype.configureBootstrap = function (cb) {455 logger.debug("Setting bootstrap config keys/values");456 var isVerbose = logger.transports.console.level === 'debug';457 var cmd = '';458 cmd += 'target = $.target();\n';459 cmd += 'au = $;\n';460 cmd += '$.isVerbose = ' + isVerbose + ';\n';461 // Not using uiauto grace period because of bug.462 // cmd += '$.target().setTimeout(1);\n';463 this.proxy(cmd, cb);464};465IOS.prototype.onUnexpectedInstrumentsExit = function (code) {466 logger.debug("Instruments exited unexpectedly");467 this.isShuttingDown = true;468 var postShutdown = function () {469 if (typeof this.cbForCurrentCmd === "function") {470 logger.debug("We were in the middle of processing a command when " +471 "instruments died; responding with a generic error");472 var error = new UnknownError("Instruments died while responding to " +473 "command, please check appium logs");474 this.onInstrumentsDie(error, this.cbForCurrentCmd);475 } else {476 this.onInstrumentsDie();477 }478 }.bind(this);479 if (this.commandProxy) {480 this.commandProxy.safeShutdown(function () {481 this.shutdown(code, postShutdown);482 }.bind(this));483 } else {484 this.shutdown(code, postShutdown);485 }486};487IOS.prototype.setXcodeVersion = function (cb) {488 logger.debug("Setting Xcode version");489 xcode.getVersion(function (err, versionNumber) {490 if (err) {491 logger.error("Could not determine Xcode version:" + err.message);492 } else {493 var minorVersion = parseFloat(versionNumber.slice(0, 3));494 var pv = parseFloat(this.args.platformVersion);495 // we deprecate Xcodes < 6.3, except for iOS 8.0 in which case we496 // support Xcode 6.0 as well497 if (minorVersion < 6.3 && (!(minorVersion === 6.0 && pv === 8.0))) {498 logCustomDeprecationWarning('Xcode version', versionNumber,499 'Support for Xcode ' + versionNumber + ' ' +500 'has been deprecated and will be removed ' +501 'in a future version. Please upgrade ' +502 'to version 6.3 or higher (or version ' +503 '6.0.1 for iOS 8.0)');504 }505 }506 this.xcodeVersion = versionNumber;507 logger.debug("Xcode version set to " + versionNumber);508 cb();509 }.bind(this));510};511IOS.prototype.setiOSSDKVersion = function (cb) {512 logger.debug("Setting iOS SDK Version");513 xcode.getMaxIOSSDK(function (err, versionNumber) {514 if (err) {515 logger.error("Could not determine iOS SDK version");516 return cb(err);517 }518 this.iOSSDKVersion = versionNumber;519 logger.debug("iOS SDK Version set to " + this.iOSSDKVersion);520 cb();521 }.bind(this));522};523IOS.prototype.setLocale = function (cb) {524 var msg;525 var setLoc = function (err) {526 logger.debug("Setting locale information");527 if (err) return cb(err);528 var needSimRestart = false;529 this.localeConfig = this.localeConfig || {};530 _(['language', 'locale', 'calendarFormat']).each(function (key) {531 needSimRestart = needSimRestart ||532 (this.args[key] &&533 this.args[key] !== this.localeConfig[key]);534 }, this);535 this.localeConfig = {536 language: this.args.language,537 locale: this.args.locale,538 calendarFormat: this.args.calendarFormat539 };540 var simRoots = this.sim.getDirs();541 if (simRoots.length < 1) {542 msg = "Cannot set locale information because the iOS Simulator directory could not be determined.";543 logger.error(msg);544 return cb(new Error(msg));545 }546 try {547 this.sim.setLocale(this.args.language, this.args.locale, this.args.calendarFormat);548 } catch (e) {549 msg = "Appium was unable to set locale info: " + e;550 logger.error(msg);551 return cb(new Error(msg));552 }553 logger.debug("Locale was set");554 if (needSimRestart) {555 logger.debug("First time setting locale, or locale changed, killing existing Instruments and Sim procs.");556 Instruments.killAllSim();557 Instruments.killAll();558 setTimeout(cb, 250);559 } else {560 cb();561 }562 }.bind(this);563 if ((this.args.language || this.args.locale || this.args.calendarFormat) && this.args.udid === null) {564 if (this.args.fullReset && this.args.platformVersion <= 6.1) {565 msg = "Cannot set locale information because a full-reset was requested. full-reset interferes with the language/locale caps on iOS 6.1 and older";566 logger.error(msg);567 return cb(new Error(msg));568 }569 if (!this.sim.dirsExist()) {570 this.instantLaunchAndQuit(false, setLoc);571 } else {572 setLoc();573 }574 } else if (this.args.udid) {575 logger.debug("Not setting locale because we're using a real device");576 cb();577 } else {578 logger.debug("Not setting locale");579 cb();580 }581};582IOS.prototype.checkPreferences = function (cb) {583 logger.debug("Checking whether we need to set app preferences");584 if (this.args.udid !== null) {585 logger.debug("Not setting iOS and app preferences since we're on a real " +586 "device");587 return cb();588 }589 var settingsCaps = [590 'locationServicesEnabled',591 'locationServicesAuthorized',592 'safariAllowPopups',593 'safariIgnoreFraudWarning',594 'safariOpenLinksInBackground'595 ];596 var safariSettingsCaps = settingsCaps.slice(2, 5);597 this.needToSetPrefs = false;598 this.needToSetSafariPrefs = false;599 _.each(settingsCaps, function (cap) {600 if (_.has(this.capabilities, cap)) {601 this.needToSetPrefs = true;602 if (_.contains(safariSettingsCaps, cap)) {603 this.needToSetSafariPrefs = true;604 }605 }606 }.bind(this));607 this.keepAppToRetainPrefs = this.needToSetPrefs;608 cb();609};610IOS.prototype.setPreferences = function (cb) {611 if (!this.needToSetPrefs) {612 logger.debug("No iOS / app preferences to set");613 return cb();614 } else if (this.args.fullReset) {615 var msg = "Cannot set preferences because a full-reset was requested";616 logger.debug(msg);617 logger.error(msg);618 return cb(new Error(msg));619 }620 var setPrefs = function (err) {621 if (err) return cb(err);622 try {623 this.setLocServicesPrefs();624 } catch (e) {625 logger.error("Error setting location services preferences, prefs will not work");626 logger.error(e);627 logger.error(e.stack);628 }629 try {630 this.setSafariPrefs();631 } catch (e) {632 logger.error("Error setting safari preferences, prefs will not work");633 logger.error(e);634 logger.error(e.stack);635 }636 cb();637 }.bind(this);638 logger.debug("Setting iOS and app preferences");639 if (!this.sim.dirsExist() ||640 !settings.locServicesDirsExist(this.sim) ||641 (this.needToSetSafariPrefs && !this.sim.safariDirsExist())) {642 this.instantLaunchAndQuit(this.needToSetSafariPrefs, setPrefs);643 } else {644 setPrefs();645 }646};647IOS.prototype.instantLaunchAndQuit = function (needSafariDirs, cb) {648 logger.debug("Sim files for the " + this.iOSSDKVersion + " SDK do not yet exist, launching the sim " +649 "to populate the applications and preference dirs");650 var condition = function () {651 var simDirsExist = this.sim.dirsExist();652 var locServicesExist = settings.locServicesDirsExist(this.sim);653 var safariDirsExist = this.args.platformVersion < 7.0 ||654 (this.sim.safariDirsExist() &&655 (this.args.platformVersion < 8.0 ||656 this.sim.userSettingsPlistExists())657 );658 var okToGo = simDirsExist && locServicesExist &&659 (!needSafariDirs || safariDirsExist);660 if (!okToGo) {661 logger.debug("We launched the simulator but the required dirs don't " +662 "yet exist. Waiting some more...");663 }664 return okToGo;665 }.bind(this);666 this.prelaunchSimulator(function (err) {667 if (err) return cb(err);668 this.makeInstruments(function (err, instruments) {669 instruments.launchAndKill(condition, function (err) {670 if (err) return cb(err);671 this.endSimulator(cb);672 }.bind(this));673 }.bind(this));674 }.bind(this));675};676IOS.prototype.setLocServicesPrefs = function () {677 if (typeof this.capabilities.locationServicesEnabled !== "undefined" ||678 this.capabilities.locationServicesAuthorized) {679 var locServ = this.capabilities.locationServicesEnabled;680 locServ = locServ || this.capabilities.locationServicesAuthorized;681 locServ = locServ ? 1 : 0;682 logger.debug("Setting location services to " + locServ);683 settings.updateSettings(this.sim, 'locationServices', {684 LocationServicesEnabled: locServ,685 'LocationServicesEnabledIn7.0': locServ,686 'LocationServicesEnabledIn8.0': locServ687 }688 );689 }690 if (typeof this.capabilities.locationServicesAuthorized !== "undefined") {691 if (!this.args.bundleId) {692 var msg = "Can't set location services for app without bundle ID";693 logger.error(msg);694 throw new Error(msg);695 }696 var locAuth = !!this.capabilities.locationServicesAuthorized;697 if (locAuth) {698 logger.debug("Authorizing location services for app");699 } else {700 logger.debug("De-authorizing location services for app");701 }702 settings.updateLocationSettings(this.sim, this.args.bundleId, locAuth);703 }704};705IOS.prototype.setSafariPrefs = function () {706 var safariSettings = {};707 var val;708 if (_.has(this.capabilities, 'safariAllowPopups')) {709 val = !!this.capabilities.safariAllowPopups;710 logger.debug("Setting javascript window opening to " + val);711 safariSettings.WebKitJavaScriptCanOpenWindowsAutomatically = val;712 safariSettings.JavaScriptCanOpenWindowsAutomatically = val;713 }714 if (_.has(this.capabilities, 'safariIgnoreFraudWarning')) {715 val = !this.capabilities.safariIgnoreFraudWarning;716 logger.debug("Setting fraudulent website warning to " + val);717 safariSettings.WarnAboutFraudulentWebsites = val;718 }719 if (_.has(this.capabilities, 'safariOpenLinksInBackground')) {720 val = this.capabilities.safariOpenLinksInBackground ? 1 : 0;721 logger.debug("Setting opening links in background to " + !!val);722 safariSettings.OpenLinksInBackground = val;723 }724 if (_.size(safariSettings) > 0) {725 settings.updateSafariSettings(this.sim, safariSettings);726 }727};728IOS.prototype.detectUdid = function (cb) {729 var msg;730 logger.debug("Auto-detecting iOS udid...");731 if (this.args.udid !== null && this.args.udid === "auto") {732 which('idevice_id', function (notFound, cmdPath) {733 var udidetectPath;734 if (notFound) {735 udidetectPath = require.resolve('udidetect');736 } else {737 udidetectPath = cmdPath + " -l";738 }739 exec(udidetectPath, { maxBuffer: 524288, timeout: 3000 }, function (err, stdout) {740 if (err) {741 msg = "Error detecting udid: " + err.message;742 logger.error(msg);743 cb(err);744 }745 if (stdout && stdout.length > 2) {746 this.args.udid = stdout.split("\n")[0];747 logger.debug("Detected udid as " + this.args.udid);748 cb();749 } else {750 msg = "Could not detect udid.";751 logger.error(msg);752 cb(new Error(msg));753 }754 }.bind(this));755 }.bind(this));756 } else {757 logger.debug("Not auto-detecting udid, running on sim");758 cb();759 }760};761IOS.prototype.setBundleIdFromApp = function (cb) {762 // This method will try to extract the bundleId from the app763 if (this.args.bundleId) {764 // We aleady have a bundle Id765 cb();766 } else {767 this.getBundleIdFromApp(function (err, bundleId) {768 if (err) {769 logger.error("Could not set the bundleId from app.");770 return cb(err);771 }772 this.args.bundleId = bundleId;773 cb();774 }.bind(this));775 }776};777IOS.prototype.installToRealDevice = function (cb) {778 // if user has passed in desiredCaps.autoLaunch = false779 // meaning they will manage app install / launching780 if (this.args.autoLaunch === false) {781 cb();782 } else {783 if (this.args.udid) {784 try {785 this.realDevice = this.getIDeviceObj();786 } catch (e) {787 return cb(e);788 }789 this.isAppInstalled(this.args.bundleId, function (err, installed) {790 if (err || !installed) {791 logger.debug("App is not installed. Will try to install the app.");792 } else {793 logger.debug("App is installed.");794 if (this.args.fullReset) {795 logger.debug("fullReset requested. Forcing app install.");796 } else {797 logger.debug("fullReset not requested. No need to install.");798 return cb();799 }800 }801 if (this.args.ipa && this.args.bundleId) {802 this.installIpa(cb);803 } else if (this.args.ipa) {804 var msg = "You specified a UDID and ipa but did not include the bundle " +805 "id";806 logger.error(msg);807 cb(new Error(msg));808 } else if (this.args.app) {809 this.installApp(this.args.app, cb);810 } else {811 logger.debug("Real device specified but no ipa or app path, assuming bundle ID is " +812 "on device");813 cb();814 }815 }.bind(this));816 } else {817 logger.debug("No device id or app, not installing to real device.");818 cb();819 }820 }821};822IOS.prototype.getIDeviceObj = function () {823 var idiPath = path.resolve(__dirname, "../../../build/",824 "libimobiledevice-macosx/ideviceinstaller");825 logger.debug("Creating iDevice object with udid " + this.args.udid);826 try {827 return iDevice(this.args.udid);828 } catch (e1) {829 logger.debug("Couldn't find ideviceinstaller, trying built-in at " +830 idiPath);831 try {832 return iDevice(this.args.udid, {cmd: idiPath});833 } catch (e2) {834 var msg = "Could not initialize ideviceinstaller; make sure it is " +835 "installed and works on your system";836 logger.error(msg);837 throw new Error(msg);838 }839 }840};841IOS.prototype.installIpa = function (cb) {842 logger.debug("Installing ipa found at " + this.args.ipa);843 if (!this.realDevice) {844 this.realDevice = this.getIDeviceObj();845 }846 var d = this.realDevice;847 async.waterfall([848 function (cb) { d.isInstalled(this.args.bundleId, cb); }.bind(this),849 function (installed, cb) {850 if (installed) {851 logger.debug("Bundle found on device, removing before reinstalling.");852 d.remove(this.args.bundleId, cb);853 } else {854 logger.debug("Nothing found on device, going ahead and installing.");855 cb();856 }857 }.bind(this),858 function (cb) { d.installAndWait(this.args.ipa, this.args.bundleId, cb); }.bind(this)859 ], cb);860};861IOS.getDeviceStringFromOpts = function (opts) {862 logger.debug("Getting device string from opts: " + JSON.stringify({863 forceIphone: opts.forceIphone,864 forceIpad: opts.forceIpad,865 xcodeVersion: opts.xcodeVersion,866 iOSSDKVersion: opts.iOSSDKVersion,867 deviceName: opts.deviceName,868 platformVersion: opts.platformVersion869 }));870 var xcodeMajorVer = parseInt(opts.xcodeVersion.substr(0,871 opts.xcodeVersion.indexOf('.')), 10);872 var isiPhone = opts.forceIphone || opts.forceIpad === null || (opts.forceIpad !== null && !opts.forceIpad);873 var isTall = isiPhone;874 var isRetina = opts.xcodeVersion[0] !== '4';875 var is64bit = false;876 var deviceName = opts.deviceName;877 var fixDevice = true;878 if (deviceName && deviceName[0] === '=') {879 return deviceName.substring(1);880 }881 logger.debug("fixDevice is " + (fixDevice ? "on" : "off"));882 if (deviceName) {883 var device = deviceName.toLowerCase();884 if (device.indexOf("iphone") !== -1) {885 isiPhone = true;886 } else if (device.indexOf("ipad") !== -1) {887 isiPhone = false;888 }889 if (deviceName !== opts.platformName) {890 isTall = isiPhone && (device.indexOf("4-inch") !== -1);891 isRetina = (device.indexOf("retina") !== -1);892 is64bit = (device.indexOf("64-bit") !== -1);893 }894 }895 var iosDeviceString = isiPhone ? "iPhone" : "iPad";896 if (xcodeMajorVer === 4) {897 if (isiPhone && isRetina) {898 iosDeviceString += isTall ? " (Retina 4-inch)" : " (Retina 3.5-inch)";899 } else {900 iosDeviceString += isRetina ? " (Retina)" : "";901 }902 } else if (xcodeMajorVer === 5) {903 iosDeviceString += isRetina ? " Retina" : "";904 if (isiPhone) {905 if (isRetina && isTall) {906 iosDeviceString += is64bit ? " (4-inch 64-bit)" : " (4-inch)";907 } else if (deviceName.toLowerCase().indexOf("3.5") !== -1) {908 iosDeviceString += " (3.5-inch)";909 }910 } else {911 iosDeviceString += is64bit ? " (64-bit)" : "";912 }913 } else if (_.contains([6, 7], xcodeMajorVer)) {914 iosDeviceString = opts.deviceName ||915 (isiPhone ? "iPhone Simulator" : "iPad Simulator");916 }917 var reqVersion = opts.platformVersion || opts.iOSSDKVersion;918 if (opts.iOSSDKVersion >= 8 && xcodeMajorVer === 7) {919 iosDeviceString += " (" + reqVersion + ")";920 } else if (opts.iOSSDKVersion >= 8) {921 iosDeviceString += " (" + reqVersion + " Simulator)";922 } else if (opts.iOSSDKVersion >= 7.1) {923 iosDeviceString += " - Simulator - iOS " + reqVersion;924 }925 if (fixDevice) {926 // Some device config are broken in 5.1927 var CONFIG_FIX = {928 'iPhone - Simulator - iOS 7.1': 'iPhone Retina (4-inch 64-bit) - ' +929 'Simulator - iOS 7.1',930 'iPad - Simulator - iOS 7.1': 'iPad Retina (64-bit) - Simulator - ' +931 'iOS 7.1',932 'iPad Simulator (8.0 Simulator)': 'iPad 2 (8.0 Simulator)',933 'iPad Simulator (8.1 Simulator)': 'iPad 2 (8.1 Simulator)',934 'iPad Simulator (8.2 Simulator)': 'iPad 2 (8.2 Simulator)',935 'iPad Simulator (8.3 Simulator)': 'iPad 2 (8.3 Simulator)',936 'iPad Simulator (8.4 Simulator)': 'iPad 2 (8.4 Simulator)',937 'iPad Simulator (7.1 Simulator)': 'iPad 2 (7.1 Simulator)',938 'iPhone Simulator (8.4 Simulator)': 'iPhone 6 (8.4 Simulator)',939 'iPhone Simulator (8.3 Simulator)': 'iPhone 6 (8.3 Simulator)',940 'iPhone Simulator (8.2 Simulator)': 'iPhone 6 (8.2 Simulator)',941 'iPhone Simulator (8.1 Simulator)': 'iPhone 6 (8.1 Simulator)',942 'iPhone Simulator (8.0 Simulator)': 'iPhone 6 (8.0 Simulator)',943 'iPhone Simulator (7.1 Simulator)': 'iPhone 5s (7.1 Simulator)'944 };945 // For xcode major version 7946 var CONFIG_FIX__XCODE_7 = {947 'iPad Simulator (8.1)': 'iPad 2 (8.1)',948 'iPad Simulator (8.2)': 'iPad 2 (8.2)',949 'iPad Simulator (8.3)': 'iPad 2 (8.3)',950 'iPad Simulator (8.4)': 'iPad 2 (8.4)',951 'iPhone Simulator (8.1)': 'iPhone 6 (8.1)',952 'iPhone Simulator (8.2)': 'iPhone 6 (8.2)',953 'iPhone Simulator (8.3)': 'iPhone 6 (8.3)',954 'iPhone Simulator (8.4)': 'iPhone 6 (8.4)',955 'iPad Simulator (9.0)': 'iPad 2 (9.0)',956 'iPad Simulator (9.1)': 'iPad 2 (9.1)',957 // Fixing ambiguous device name by adding '[' at the end so intruments958 // correctly starts iPhone 6 [udid] and not the iPhone 6 (9.0) + Apple Watch959 // for ios9.0 and above; see #5619960 'iPhone Simulator (9.0)': 'iPhone 6 (9.0) [',961 'iPhone Simulator (9.1)': 'iPhone 6 (9.1) [',962 'iPhone 6 (9.0)': 'iPhone 6 (9.0) [',963 'iPhone 6 (9.1)': 'iPhone 6 (9.1) ['964 };965 var configFix = xcodeMajorVer === 7 ? CONFIG_FIX__XCODE_7 : CONFIG_FIX;966 if (configFix[iosDeviceString]) {967 var oldDeviceString = iosDeviceString;968 iosDeviceString = configFix[iosDeviceString];969 logger.debug("Fixing device. Changed from: \"" + oldDeviceString +970 "\" to: \"" + iosDeviceString + "\"");971 }972 }973 logger.debug("Final device string is: '" + iosDeviceString + "'");974 return iosDeviceString;975};976IOS.prototype.getDeviceString = function () {977 var opts = _.clone(this.args);978 _.extend(opts, {979 xcodeVersion: this.xcodeVersion,980 iOSSDKVersion: this.iOSSDKVersion981 });982 return IOS.getDeviceStringFromOpts(opts);983};984IOS.prototype.setDeviceTypeInInfoPlist = function (cb) {985 var plist = path.resolve(this.args.app, "Info.plist");986 var dString = this.getDeviceString();987 var isiPhone = dString.toLowerCase().indexOf("ipad") === -1;988 var deviceTypeCode = isiPhone ? 1 : 2;989 parsePlistFile(plist, function (err, obj) {990 if (err) {991 logger.error("Could not set the device type in Info.plist");992 return cb(err, null);993 } else {994 var newPlist;995 obj.UIDeviceFamily = [deviceTypeCode];996 if (binaryPlist) {997 newPlist = bplistCreate(obj);998 } else {999 newPlist = xmlplist.build(obj);1000 }1001 fs.writeFile(plist, newPlist, function (err) {1002 if (err) {1003 logger.error("Could not save new Info.plist");1004 cb(err);1005 } else {1006 logger.debug("Wrote new app Info.plist with device type");1007 cb();1008 }1009 }.bind(this));1010 }1011 }.bind(this));1012};1013IOS.prototype.getBundleIdFromApp = function (cb) {1014 logger.debug("Getting bundle ID from app");1015 var plist = path.resolve(this.args.app, "Info.plist");1016 parsePlistFile(plist, function (err, obj) {1017 if (err) {1018 logger.error("Could not get the bundleId from app.");1019 cb(err, null);1020 } else {1021 cb(null, obj.CFBundleIdentifier);1022 }1023 }.bind(this));1024};1025IOS.getSimForDeviceString = function (dString, availDevices) {1026 var matchedDevice = null;1027 var matchedUdid = null;1028 _.each(availDevices, function (device) {1029 if (device.indexOf(dString) !== -1) {1030 matchedDevice = device;1031 try {1032 matchedUdid = /.+\[([^\]]+)\]/.exec(device)[1];1033 } catch (e) {1034 matchedUdid = null;1035 }1036 }1037 });1038 return [matchedDevice, matchedUdid];1039};1040IOS.prototype.checkSimAvailable = function (cb) {1041 if (this.args.udid) {1042 logger.debug("Not checking whether simulator is available since we're on " +1043 "a real device");1044 return cb();1045 }1046 if (this.iOSSDKVersion < 7.1) {1047 logger.debug("Instruments v < 7.1, not checking device string support");1048 return cb();1049 }1050 logger.debug("Checking whether instruments supports our device string");1051 Instruments.getAvailableDevicesWithRetry(3, function (err, availDevices) {1052 if (err) return cb(err);1053 var noDevicesError = function () {1054 var msg = "Could not find a device to launch. You requested '" +1055 dString + "', but the available devices were: " +1056 JSON.stringify(availDevices);1057 logger.error(msg);1058 cb(new Error(msg));1059 };1060 var dString = this.getDeviceString();1061 if (this.iOSSDKVersion >= 8) {1062 var sim = IOS.getSimForDeviceString(dString, availDevices);1063 if (sim[0] === null || sim[1] === null) {1064 return noDevicesError();1065 }1066 this.iOSSimUdid = sim[1];1067 logger.debug("iOS sim UDID is " + this.iOSSimUdid);1068 return cb();1069 } else if (!_.contains(availDevices, dString)) {1070 return noDevicesError();1071 }1072 cb();1073 }.bind(this));1074};1075IOS.prototype.setDeviceInfo = function (cb) {1076 this.shouldPrelaunchSimulator = false;1077 if (this.args.udid) {1078 logger.debug("Not setting device type since we're on a real device");1079 return cb();1080 }1081 if (!this.args.app && this.args.bundleId) {1082 logger.debug("Not setting device type since we're using bundle ID and " +1083 "assuming app is already installed");1084 return cb();1085 }1086 if (!this.args.deviceName &&1087 this.args.forceIphone === null &&1088 this.args.forceIpad === null) {1089 logger.debug("No device specified, current device in the iOS " +1090 "simulator will be used.");1091 return cb();1092 }1093 if (this.args.defaultDevice || this.iOSSDKVersion >= 7.1) {1094 if (this.iOSSDKVersion >= 7.1) {1095 logger.debug("We're on iOS7.1+ so forcing defaultDevice on");1096 } else {1097 logger.debug("User specified default device, letting instruments launch it");1098 }1099 } else {1100 this.shouldPrelaunchSimulator = true;1101 }1102 this.setDeviceTypeInInfoPlist(cb);1103};1104IOS.prototype.createSimulator = function (cb) {1105 this.sim = new Simulator({1106 platformVer: this.args.platformVersion,1107 sdkVer: this.iOSSDKVersion,1108 udid: this.iOSSimUdid1109 });1110 cb();1111};1112IOS.prototype.moveBuiltInApp = function (cb) {1113 if (this.appString().toLowerCase() === "settings") {1114 logger.debug("Trying to use settings app, version " +1115 this.args.platformVersion);1116 this.sim.preparePreferencesApp(this.args.tmpDir, function (err, attemptedApp, origApp) {1117 if (err) {1118 logger.error("Could not prepare settings app: " + err);1119 return cb(err);1120 }1121 logger.debug("Using settings app at " + attemptedApp);1122 this.args.app = attemptedApp;1123 this.args.origAppPath = origApp;1124 cb();1125 }.bind(this));1126 } else {1127 cb();1128 }1129};1130IOS.prototype.prelaunchSimulator = function (cb) {1131 var msg;1132 if (!this.shouldPrelaunchSimulator) {1133 logger.debug("Not pre-launching simulator");1134 return cb();1135 }1136 xcode.getPath(function (err, xcodePath) {1137 if (err) {1138 return cb(new Error('Could not find xcode folder. Needed to start simulator. ' + err.message));1139 }1140 logger.debug("Pre-launching simulator");1141 var iosSimPath = path.resolve(xcodePath,1142 "Platforms/iPhoneSimulator.platform/Developer/Applications" +1143 "/iPhone Simulator.app/Contents/MacOS/iPhone Simulator");1144 if (!fs.existsSync(iosSimPath)) {1145 msg = "Could not find ios simulator binary at " + iosSimPath;1146 logger.error(msg);1147 return cb(new Error(msg));1148 }1149 this.endSimulator(function (err) {1150 if (err) return cb(err);1151 logger.debug("Launching device: " + this.getDeviceString());1152 var iosSimArgs = ["-SimulateDevice", this.getDeviceString()];1153 this.iosSimProcess = spawn(iosSimPath, iosSimArgs);1154 var waitForSimulatorLogs = function (countdown) {1155 if (countdown <= 0 ||1156 (this.logs.syslog && (this.logs.syslog.getAllLogs().length > 0 ||1157 (this.logs.crashlog && this.logs.crashlog.getAllLogs().length > 0)))) {1158 logger.debug(countdown > 0 ? "Simulator is now ready." :1159 "Waited 10 seconds for simulator to start.");1160 cb();1161 } else {1162 setTimeout(function () {1163 waitForSimulatorLogs(countdown - 1);1164 }, 1000);1165 }1166 }.bind(this);1167 waitForSimulatorLogs(10);1168 }.bind(this));1169 }.bind(this));1170};1171IOS.prototype.parseLocalizableStrings = function (/* language, stringFile, cb */) {1172 var args = new Args(arguments);1173 var cb = args.callback;1174 if (this.args.app === null) {1175 logger.debug("Localizable.strings is not currently supported when using real devices.");1176 return cb();1177 }1178 var language = args.all[0] || this.args.language1179 , stringFile = args.all[1] || "Localizable.strings"1180 , strings = null;1181 if (language) {1182 strings = path.resolve(this.args.app, language + ".lproj", stringFile);1183 }1184 if (!fs.existsSync(strings)) {1185 if (language) {1186 logger.debug("No strings file '" + stringFile + "' for language '" + language + "', getting default strings");1187 }1188 strings = path.resolve(this.args.app, stringFile);1189 }1190 if (!fs.existsSync(strings)) {1191 strings = path.resolve(this.args.app, this.args.localizableStringsDir, stringFile);1192 }1193 parsePlistFile(strings, function (err, obj) {1194 if (err) {1195 logger.warn("Could not parse app " + stringFile +" assuming it " +1196 "doesn't exist");1197 } else {1198 logger.debug("Parsed app " + stringFile);1199 this.localizableStrings = obj;1200 }1201 cb();1202 }.bind(this));1203};1204IOS.prototype.deleteSim = function (cb) {1205 this.sim.deleteSim(cb);1206};1207IOS.prototype.clearAppData = function (cb) {1208 if (!this.keepAppToRetainPrefs && this.args.app && this.args.bundleId) {1209 this.sim.cleanCustomApp(path.basename(this.args.app), this.args.bundleId);1210 }1211 cb();1212};1213IOS.prototype.cleanupSimState = function (cb) {1214 if (this.realDevice && this.args.bundleId && this.args.fullReset) {1215 logger.debug("fullReset requested. Will try to uninstall the app.");1216 var bundleId = this.args.bundleId;1217 this.realDevice.remove(bundleId, function (err) {1218 if (err) {1219 this.removeApp(bundleId, function (err) {1220 if (err) {1221 logger.error("Could not remove " + bundleId + " from device");1222 cb(err);1223 } else {1224 logger.debug("Removed " + bundleId);1225 cb();1226 }1227 }.bind(this));1228 } else {1229 logger.debug("Removed " + bundleId);1230 cb();1231 }1232 }.bind(this));1233 } else if (!this.args.udid) {1234 this.sim.cleanSim(this.args.keepKeyChains, this.args.tmpDir, function (err) {1235 if (err) {1236 logger.error("Could not reset simulator. Leaving as is. Error: " + err.message);1237 }1238 this.clearAppData(cb);1239 }.bind(this));1240 } else {1241 logger.debug("On a real device; cannot clean device state");1242 cb();1243 }1244};1245IOS.prototype.runSimReset = function (cb) {1246 if (this.args.reset || this.args.fullReset) {1247 logger.debug("Running ios sim reset flow");1248 // The simulator process must be ended before we delete applications.1249 async.series([1250 this.endSimulator.bind(this),1251 function (cb) {1252 if (this.args.reset) {1253 this.cleanupSimState(cb);1254 } else {1255 cb();1256 }1257 }.bind(this),1258 function (cb) {1259 if (this.args.fullReset && !this.args.udid) {1260 this.deleteSim(cb);1261 } else {1262 cb();1263 }1264 }.bind(this)1265 ], cb);1266 } else {1267 logger.debug("Reset not set, not ending sim or cleaning up app state");1268 cb();1269 }1270};1271IOS.prototype.isolateSimDevice = function (cb) {1272 if (!this.args.udid && this.args.isolateSimDevice &&1273 this.iOSSDKVersion >= 8) {1274 this.sim.deleteOtherSims(cb);1275 } else {1276 cb();1277 }1278};1279IOS.prototype.postCleanup = function (cb) {1280 this.curCoords = null;1281 this.curOrientation = null;1282 if (!_.isEmpty(this.logs)) {1283 this.logs.syslog.stopCapture();1284 this.logs = {};1285 }1286 if (this.remote) {1287 this.stopRemote();1288 }1289 this.runSimReset(function () {1290 // ignore any errors during reset and continue shutting down1291 this.isShuttingDown = false;1292 cb();1293 }.bind(this));1294};1295IOS.prototype.endSimulator = function (cb) {1296 logger.debug("Killing the simulator process");1297 if (this.iosSimProcess) {1298 this.iosSimProcess.kill("SIGHUP");1299 this.iosSimProcess = null;1300 } else {1301 Instruments.killAllSim();1302 }1303 this.endSimulatorDaemons(cb);1304};1305IOS.prototype.endSimulatorDaemons = function (cb) {1306 logger.debug("Killing any other simulator daemons");1307 var stopCmd = 'launchctl list | grep com.apple.iphonesimulator | cut -f 3 | xargs -n 1 launchctl stop';1308 exec(stopCmd, { maxBuffer: 524288 }, function () {1309 var removeCmd = 'launchctl list | grep com.apple.iphonesimulator | cut -f 3 | xargs -n 1 launchctl remove';1310 exec(removeCmd, { maxBuffer: 524288 }, function () {1311 cb();1312 });1313 });1314};1315IOS.prototype.stop = function (cb) {1316 logger.debug("Stopping ios");1317 if (this.instruments === null) {1318 logger.debug("Trying to stop instruments but it already exited");1319 this.postCleanup(cb);1320 } else {1321 this.commandProxy.shutdown(function (err) {1322 if (err) logger.warn("Got warning when trying to close command proxy:", err);1323 this.instruments.shutdown(function (code) {1324 this.shutdown(code, cb);1325 }.bind(this));1326 }.bind(this));1327 }1328};1329IOS.prototype.shutdown = function (code, cb) {1330 this.commandProxy = null;1331 this.instruments = null;1332 this.postCleanup(cb);1333};1334IOS.prototype.resetTimeout = deviceCommon.resetTimeout;1335IOS.prototype.waitForCondition = deviceCommon.waitForCondition;1336IOS.prototype.implicitWaitForCondition = deviceCommon.implicitWaitForCondition;1337IOS.prototype.proxy = deviceCommon.proxy;1338IOS.prototype.proxyWithMinTime = deviceCommon.proxyWithMinTime;1339IOS.prototype.respond = deviceCommon.respond;1340IOS.prototype.getSettings = deviceCommon.getSettings;1341IOS.prototype.updateSettings = deviceCommon.updateSettings;1342IOS.prototype.initQueue = function () {1343 this.queue = async.queue(function (command, cb) {1344 if (!this.commandProxy) return cb();1345 async.series([1346 function (cb) {1347 async.whilst(1348 function () { return this.selectingNewPage && this.curContext; }.bind(this),1349 function (cb) {1350 logger.debug("We're in the middle of selecting a new page, " +1351 "waiting to run next command until done");1352 setTimeout(cb, 100);1353 },1354 cb1355 );1356 }.bind(this),1357 function (cb) {1358 var matched = false;1359 var matches = ["au.alertIsPresent", "au.getAlertText", "au.acceptAlert",1360 "au.dismissAlert", "au.setAlertText",1361 "au.waitForAlertToClose"];1362 _.each(matches, function (match) {1363 if (command.indexOf(match) === 0) {1364 matched = true;1365 }1366 });1367 async.whilst(1368 function () { return !matched && this.curContext && this.processingRemoteCmd; }.bind(this),1369 function (cb) {1370 logger.debug("We're in the middle of processing a remote debugger " +1371 "command, waiting to run next command until done");1372 setTimeout(cb, 100);1373 },1374 cb1375 );1376 }.bind(this)1377 ], function (err) {1378 if (err) return cb(err);1379 this.cbForCurrentCmd = cb;1380 if (this.commandProxy) {1381 this.commandProxy.sendCommand(command, function (response) {1382 this.cbForCurrentCmd = null;1383 if (typeof cb === 'function') {1384 this.respond(response, cb);1385 }1386 }.bind(this));1387 }1388 }.bind(this));1389 }.bind(this), 1);1390};1391IOS.prototype.push = function (elem) {1392 this.queue.push(elem[0], elem[1]);1393};1394IOS.prototype.isAppInstalled = function (bundleId, cb) {1395 if (this.args.udid) {1396 this.realDevice.isInstalled(bundleId, cb);1397 } else {1398 cb(new Error("You can not call isInstalled for the iOS simulator!"));1399 }1400};1401IOS.prototype.removeApp = function (bundleId, cb) {1402 if (this.args.udid) {1403 this.realDevice.remove(bundleId, cb);1404 } else {1405 cb(new Error("You can not call removeApp for the iOS simulator!"));1406 }1407};1408IOS.prototype.installApp = function (unzippedAppPath, cb) {1409 if (this.args.udid) {1410 this.realDevice.install(unzippedAppPath, cb);1411 } else {1412 cb(new Error("You can not call installApp for the iOS simulator!"));1413 }1414};1415IOS.prototype.unpackApp = function (req, cb) {1416 deviceCommon.unpackApp(req, '.app', cb);1417};1418IOS.prototype.startLogCapture = function (cb) {1419 if (!_.isEmpty(this.logs)) {1420 cb(new Error("Trying to start iOS log capture but it's already started!"));1421 return;1422 }1423 this.logs.crashlog = new iOSCrashLog();1424 this.logs.syslog = new iOSLog({1425 udid: this.args.udid1426 , simUdid: this.iOSSimUdid1427 , showLogs: this.args.showSimulatorLog || this.args.showIOSLog1428 });1429 this.logs.syslog.startCapture(function (err) {1430 if (err) {1431 logger.warn("Could not capture logs from device. Continuing without capturing logs.");1432 return cb();1433 }1434 this.logs.crashlog.startCapture(cb);1435 }.bind(this));1436};1437IOS.prototype.initAutoWebview = function (cb) {1438 if (this.args.autoWebview) {1439 logger.debug('Setting auto webview');1440 this.navToInitialWebview(cb);1441 } else {1442 cb();1443 }1444};1445IOS.prototype.getContextsAndViews = function (cb) {1446 this.listWebFrames(function (err, webviews) {1447 if (err) return cb(err);1448 var ctxs = [{id: this.NATIVE_WIN}];1449 this.contexts = [this.NATIVE_WIN];1450 _.each(webviews, function (view) {1451 ctxs.push({id: this.WEBVIEW_BASE + view.id, view: view});1452 this.contexts.push(view.id.toString());1453 }.bind(this));1454 cb(null, ctxs);1455 }.bind(this));1456};1457IOS.prototype.getLatestWebviewContextForTitle = function (titleRegex, cb) {1458 this.getContextsAndViews(function (err, contexts) {1459 if (err) return cb(err);1460 var matchingCtx;1461 _(contexts).each(function (ctx) {1462 if (ctx.view && (ctx.view.title || "").match(titleRegex)) {1463 if (ctx.view.url === "about:blank") {1464 // in the cases of Xcode < 5 (i.e., iOS SDK Version less than 7)1465 // iOS 7.1, iOS 9.0 & iOS 9.1 in a webview (not in Safari)1466 // we can have the url be `about:blank`1467 if (parseFloat(this.iOSSDKVersion) < 7 || parseFloat(this.iOSSDKVersion) >= 9 ||1468 (this.args.platformVersion === '7.1' && this.args.app && this.args.app.toLowerCase() !== 'safari')) {1469 matchingCtx = ctx;1470 }1471 } else {1472 matchingCtx = ctx;1473 }1474 }1475 }.bind(this));1476 cb(null, matchingCtx ? matchingCtx.id : undefined);1477 }.bind(this));1478};1479// Right now we don't necessarily wait for webview1480// and frame to load, which leads to race conditions and flakiness1481// , let see if we can transition to something better1482IOS.prototype.useNewSafari = function () {1483 return parseFloat(this.iOSSDKVersion) >= 8.1 &&1484 parseFloat(this.args.platformVersion) >= 8.1 &&1485 !this.args.udid &&1486 this.capabilities.safari;1487};1488IOS.prototype.navToInitialWebview = function (cb) {1489 var timeout = 0;1490 if (this.args.udid) timeout = parseInt(this.iOSSDKVersion, 10) >= 8 ? 4000 : 6000;1491 if (timeout > 0) logger.debug('Waiting for ' + timeout + ' ms before navigating to view.');1492 setTimeout(function () {1493 if (this.useNewSafari()) {1494 return this.typeAndNavToUrl(cb);1495 } else if (parseInt(this.iOSSDKVersion, 10) >= 7 && !this.args.udid && this.capabilities.safari) {1496 this.navToViewThroughFavorites(cb);1497 } else {1498 this.navToViewWithTitle(/.*/, cb);1499 }1500 }.bind(this), timeout);1501};1502IOS.prototype.typeAndNavToUrl = function (cb) {1503 var initialUrl = this.args.safariInitialUrl || 'http://127.0.0.1:' + this.args.port + '/welcome';1504 var oldImpWait = this.implicitWaitMs;1505 this.implicitWaitMs = 7000;1506 function noArgsCb(cb) { return function (err) { cb(err); }; }1507 async.waterfall([1508 this.findElement.bind(this, 'name', 'URL'),1509 function (res, cb) {1510 this.implicitWaitMs = oldImpWait;1511 this.nativeTap(res.value.ELEMENT, noArgsCb(cb));1512 }.bind(this),1513 this.findElements.bind(this, 'name', 'Address'),1514 function (res, cb) {1515 var addressEl = res.value[res.value.length -1].ELEMENT;1516 this.setValueImmediate(addressEl, initialUrl, noArgsCb(cb));1517 }.bind(this),1518 this.findElement.bind(this, 'name', 'Go'),1519 function (res, cb) {1520 this.nativeTap(res.value.ELEMENT, noArgsCb(cb));1521 }.bind(this)1522 ], function () {1523 this.navToViewWithTitle(/.*/i, function (err) {1524 if (err) return cb(err);1525 // Waits for page to finish loading.1526 this.remote.pageUnload(cb);1527 }.bind(this));1528 }.bind(this));1529};1530IOS.prototype.navToViewThroughFavorites = function (cb) {1531 logger.debug("We're on iOS7+ simulator: clicking apple button to get into " +1532 "a webview");1533 var oldImpWait = this.implicitWaitMs;1534 this.implicitWaitMs = 7000; // wait 7s for apple button to exist1535 this.findElement('xpath', '//UIAScrollView[1]/UIAButton[1]', function (err, res) {1536 this.implicitWaitMs = oldImpWait;1537 if (err || res.status !== status.codes.Success.code) {1538 var msg = "Could not find button to click to get into webview. " +1539 "Proceeding on the assumption we have a working one.";1540 logger.error(msg);1541 return this.navToViewWithTitle(/.*/i, cb);1542 }1543 this.nativeTap(res.value.ELEMENT, function (err, res) {1544 if (err || res.status !== status.codes.Success.code) {1545 var msg = "Could not click button to get into webview. " +1546 "Proceeding on the assumption we have a working one.";1547 logger.error(msg);1548 }1549 this.navToViewWithTitle(/apple/i, cb);1550 }.bind(this));1551 }.bind(this));1552};1553IOS.prototype.navToViewWithTitle = function (titleRegex, cb) {1554 logger.debug("Navigating to most recently opened webview");1555 var start = Date.now();1556 var spinTime = 500;1557 var spinHandles = function () {1558 this.getLatestWebviewContextForTitle(titleRegex, function (err, res) {1559 if (err) {1560 cb(new Error("Could not navigate to webview! Err: " + err));1561 } else if (!res) {1562 if ((Date.now() - start) < 90000) {1563 logger.warn("Could not find any webviews yet, refreshing/retrying");1564 if (this.args.udid || !this.capabilities.safari) {1565 return setTimeout(spinHandles, spinTime);1566 }1567 this.findUIElementOrElements('accessibility id', 'ReloadButton',1568 '', false, function (err, res) {1569 if (err || !res || !res.value || !res.value.ELEMENT) {1570 logger.warn("Could not find reload button, continuing");1571 setTimeout(spinHandles, spinTime);1572 } else {1573 this.nativeTap(res.value.ELEMENT, function (err, res) {1574 if (err || !res) {1575 logger.warn("Could not click reload button, continuing");1576 }1577 setTimeout(spinHandles, spinTime);1578 }.bind(this));1579 }1580 }.bind(this));1581 } else {1582 cb(new Error("Could not navigate to webview; there aren't any!"));1583 }1584 } else {1585 var latestWindow = res;1586 logger.debug("Picking webview " + latestWindow);1587 this.setContext(latestWindow, function (err) {1588 if (err) return cb(err);1589 this.remote.cancelPageLoad();1590 cb();1591 }.bind(this), true);1592 }1593 }.bind(this));1594 }.bind(this);1595 spinHandles();1596};1597_.extend(IOS.prototype, iOSHybrid);1598_.extend(IOS.prototype, iOSController);...
context.js
Source:context.js
1import _ from 'lodash';2import B from 'bluebird';3import { retryInterval } from 'asyncbox';4import { RemoteDebugger, WebKitRemoteDebugger } from 'appium-remote-debugger';5import IOSPerformanceLog from '../device-log/ios-performance-log';6import { errors } from 'appium-base-driver';7import logger from '../logger';8import { util } from 'appium-support';9const NATIVE_WIN = 'NATIVE_APP';10const WEBVIEW_WIN = 'WEBVIEW';11const WEBVIEW_BASE = `${WEBVIEW_WIN}_`;12let commands = {}, helpers = {}, extensions = {};13commands.getCurrentContext = async function getCurrentContext () { // eslint-disable-line require-await14 if (this.curContext && this.curContext !== NATIVE_WIN) {15 return `${WEBVIEW_BASE}${this.curContext}`;16 } else {17 return NATIVE_WIN;18 }19};20commands.getContexts = async function getContexts () {21 logger.debug('Getting list of available contexts');22 let contexts = await this.getContextsAndViews(false);23 let mapFn = (context) => context.id.toString();24 if (this.opts.fullContextList) {25 mapFn = function (context) {26 return {27 id: context.id.toString(),28 title: context.view.title,29 url: context.view.url,30 };31 };32 }33 return contexts.map(mapFn);34};35commands.setContext = async function setContext (name, callback, skipReadyCheck) {36 function alreadyInContext (desired, current) {37 return (desired === current ||38 (desired === null && current === NATIVE_WIN) ||39 (desired === NATIVE_WIN && current === null));40 }41 logger.debug(`Attempting to set context to '${name}'`);42 if (alreadyInContext(name, this.curContext)) {43 // already in the named context, no need to do anything44 } else if (name === NATIVE_WIN || name === null) {45 // switching into the native context46 this.curContext = null;47 if (this.isRealDevice()) {48 this.remote.disconnect();49 }50 } else {51 // switching into a webview context52 // if contexts have not already been retrieved, get them53 if (_.isUndefined(this.contexts)) {54 await this.getContexts();55 }56 let contextId = name.replace(WEBVIEW_BASE, '');57 if (contextId === '') {58 // allow user to pass in "WEBVIEW" without an index59 // the second context will be the first webview as60 // the first is always NATIVE_APP61 contextId = this.contexts[1];62 }63 if (!_.includes(this.contexts, contextId)) {64 throw new errors.NoSuchContextError();65 }66 if (this.isRealDevice()) {67 if (this.remote) {68 await this.remote.disconnect();69 }70 this.curContext = contextId;71 await this.remote.connect(contextId);72 } else {73 // `contextId` will be in the form of `appId.pageId` in this case74 let [appIdKey, pageIdKey] = _.map(contextId.split('.'), (id) => parseInt(id, 10));75 await this.remote.selectPage(appIdKey, pageIdKey, skipReadyCheck);76 this.curContext = contextId;77 }78 }79 // attempt to start performance logging, if requested80 if (this.opts.enablePerformanceLogging && this.remote) {81 logger.debug(`Starting performance log on '${this.curContext}'`);82 this.logs.performance = new IOSPerformanceLog(this.remote);83 await this.logs.performance.startCapture();84 }85};86commands.getWindowHandle = async function getWindowHandle () { // eslint-disable-line require-await87 if (!this.isWebContext()) {88 throw new errors.NotImplementedError();89 }90 return this.curContext.toString();91};92commands.getWindowHandles = async function getWindowHandles () {93 if (!this.isWebContext()) {94 throw new errors.NotImplementedError();95 }96 this.windowHandleCache = await this.listWebFrames(false);97 const idArray = _.map(this.windowHandleCache, 'id');98 // since we use this.contexts to manage selecting debugger pages, make99 // sure it gets populated even if someone did not use the100 // getContexts method101 if (!this.contexts) {102 this.contexts = idArray;103 }104 return _.map(idArray, (id) => id.toString());105};106commands.setWindow = async function setWindow (name, skipReadyCheck) {107 if (!this.isWebContext()) {108 throw new errors.NotImplementedError();109 }110 if (!_.includes(_.map(this.windowHandleCache, 'id'), name)) {111 throw new errors.NoSuchWindowError();112 }113 let pageIdKey = parseInt(name, 10);114 if (!this.isRealDevice()) {115 await this.remote.selectPage(pageIdKey, skipReadyCheck);116 this.curContext = this.curWindowHandle = name;117 } else {118 if (name === this.curWindowHandle) {119 logger.debug(`Remote debugger is already connected to window '${name}'`);120 } else if (!_.includes(_.map(this.windowHandleCache, 'id'), name)) {121 throw new errors.NoSuchWindowError();122 } else {123 await this.remote.disconnect();124 this.curContext = this.curWindowHandle = name;125 await this.remote.connect(name);126 }127 }128};129helpers.webContextIndex = function webContextIndex () {130 return this.curContext.replace(WEBVIEW_BASE, '') - 1;131};132extensions.initAutoWebview = async function initAutoWebview () {133 if (this.opts.autoWebview) {134 logger.debug('Setting auto webview');135 await this.navToInitialWebview(this);136 }137};138extensions.getContextsAndViews = async function getContextsAndViews (useUrl = true) {139 logger.debug('Retrieving contexts and views');140 let webviews = await this.listWebFrames(useUrl);141 let ctxs = [{id: NATIVE_WIN, view: {}}];142 this.contexts = [NATIVE_WIN];143 for (let view of webviews) {144 ctxs.push({id: `${WEBVIEW_BASE}${view.id}`, view});145 this.contexts.push(view.id.toString());146 }147 return ctxs;148};149extensions.getNewRemoteDebugger = async function getNewRemoteDebugger () { // eslint-disable-line require-await150 return new RemoteDebugger({151 bundleId: this.opts.bundleId,152 useNewSafari: this.useNewSafari(),153 pageLoadMs: this.pageLoadMs,154 platformVersion: this.opts.platformVersion,155 isSafari: this.isSafari(),156 remoteDebugProxy: this.opts.remoteDebugProxy,157 garbageCollectOnExecute: false,158 logFullResponse: !!this.opts.safariShowFullResponse,159 });160};161extensions.listWebFrames = async function listWebFrames (useUrl = true) {162 if (!this.opts.bundleId) {163 logger.errorAndThrow('Cannot enter web frame without a bundle ID');164 }165 useUrl = useUrl && !!this.getCurrentUrl();166 logger.debug(`Selecting by url: ${useUrl} ${useUrl ? `(expected url: '${this.getCurrentUrl()}')` : ''}`);167 let currentUrl = useUrl ? this.getCurrentUrl() : undefined;168 let pageArray;169 if (this.isRealDevice() && this.remote && this.opts.bundleId) {170 // real device, and already connected171 pageArray = await this.remote.pageArrayFromJson(this.opts.ignoreAboutBlankUrl);172 } else if (this.remote && this.remote.appIdKey) {173 // simulator, and already connected174 pageArray = await this.remote.selectApp(currentUrl, this.opts.webviewConnectRetries, this.opts.ignoreAboutBlankUrl);175 } else if (this.isRealDevice()) {176 // real device, and not connected177 try {178 this.remote = new WebKitRemoteDebugger({179 port: this.opts.webkitDebugProxyPort,180 webkitResponseTimeout: this.opts.webkitResponseTimeout,181 platformVersion: this.opts.platformVersion,182 isSafari: this.isSafari(),183 garbageCollectOnExecute: false,184 logFullResponse: !!this.opts.safariShowFullResponse,185 });186 pageArray = await this.remote.pageArrayFromJson(this.opts.ignoreAboutBlankUrl);187 } catch (err) {188 // it is reasonable to expect that this might be called when there is no189 // webkit remote debugger to connect to190 if (!_.includes(err.message, 'connect ECONNREFUSED')) throw err; // eslint-disable-line curly191 logger.warn('Attempted to get a list of webview contexts but could not connect to ' +192 'ios-webkit-debug-proxy. If you expect to find webviews, please ensure ' +193 'that the proxy is running and accessible');194 this.remote = null;195 pageArray = [];196 }197 } else {198 // simulator, and not connected199 this.remote = await this.getNewRemoteDebugger();200 let appInfo = await this.remote.connect();201 if (!appInfo) {202 logger.debug('Unable to connect to the remote debugger.');203 return [];204 }205 pageArray = await this.remote.selectApp(currentUrl, this.opts.webviewConnectRetries, this.opts.ignoreAboutBlankUrl);206 this.remote.on(RemoteDebugger.EVENT_PAGE_CHANGE, this.onPageChange.bind(this));207 this.remote.on(RemoteDebugger.EVENT_FRAMES_DETACHED, () => {208 if (!_.isEmpty(this.curWebFrames)) {209 logger.debug(`Clearing ${this.curWebFrames.length} frames: ${this.curWebFrames.join(', ')}`);210 }211 this.curWebFrames = [];212 });213 let tryClosingAlert = async () => {214 let didDismiss = await this.closeAlertBeforeTest();215 if (!didDismiss) {216 throw new Error('Close alert failed. Retry.');217 }218 };219 try {220 await retryInterval(3, 4000, tryClosingAlert);221 } catch (err) {222 // if the loop to close alerts failed to dismiss, ignore,223 // otherwise log and throw the error224 if (err.message !== 'Close alert failed. Retry.') {225 logger.errorAndThrow(err);226 }227 }228 }229 if (pageArray.length === 0) {230 // we have no web frames, but continue anyway231 logger.debug('No web frames found.');232 }233 return pageArray;234};235extensions.onPageChange = async function onPageChange (pageChangeNotification) {236 logger.debug(`Remote debugger notified us of a new page listing: ${JSON.stringify(pageChangeNotification)}`);237 if (this.selectingNewPage) {238 logger.debug('We are in the middle of selecting a page, ignoring');239 return;240 }241 if (!this.remote || !this.remote.isConnected()) {242 logger.debug('We have not yet connected, ignoring');243 return;244 }245 const {appIdKey, pageArray} = pageChangeNotification;246 let newIds = [];247 let newPages = [];248 let keyId = null;249 for (const page of pageArray) {250 const id = page.id.toString();251 newIds.push(id);252 if (page.isKey) {253 keyId = id;254 }255 const contextId = `${appIdKey}.${id}`;256 // add if this is a new page257 if (!_.includes(this.contexts, contextId)) {258 newPages.push(id);259 this.contexts.push(contextId);260 }261 }262 if (!keyId) {263 // if there is no key id, pull the first id from the page array and use that264 // as a stand in265 logger.debug('No key id found. Choosing first id from page array');266 keyId = newIds[0] || null;267 }268 if (!util.hasValue(this.curContext)) {269 logger.debug('We do not appear to have window set yet, ignoring');270 return;271 }272 const [curAppIdKey, curPageIdKey] = this.curContext.split('.');273 if (curAppIdKey !== appIdKey) {274 logger.debug('Page change not referring to currently selected app, ignoring.');275 return;276 }277 let newPage = null;278 if (newPages.length) {279 newPage = _.last(newPages);280 logger.debug(`We have new pages, selecting page '${newPage}'`);281 } else if (!_.includes(newIds, curPageIdKey)) {282 logger.debug('New page listing from remote debugger does not contain ' +283 'current window; assuming it is closed');284 if (!util.hasValue(keyId)) {285 logger.error('Do not have our current window anymore, and there ' +286 'are not any more to load! Doing nothing...');287 this.setCurrentUrl(undefined);288 return;289 }290 logger.debug(`Debugger already selected page '${keyId}', ` +291 `confirming that choice.`);292 this.curContext = `${appIdKey}.${keyId}`;293 newPage = keyId;294 } else {295 // at this point, there are no new pages, and the current page still exists296 logger.debug('Checking if page needs to load');297 // If a window navigates to an anchor it doesn't always fire a page298 // callback event. Let's check if we wound up in such a situation.299 const needsPageLoad = (() => {300 // need to map the page ids to context ids301 const contextArray = _.map(pageArray, (page) => `${appIdKey}.${page.id}`);302 // check if the current context exists in both our recorded contexts,303 // and the page array304 return !_.isEqual(_.find(this.contexts, this.curContext), _.find(contextArray, this.curContext));305 })();306 if (needsPageLoad) {307 logger.debug('Page load needed. Loading...');308 await this.remote.pageLoad();309 }310 logger.debug('New page listing is same as old, doing nothing');311 }312 // make sure that the page listing isn't indicating a redirect313 if (util.hasValue(this.curContext)) {314 let currentPageId = parseInt(_.last(this.curContext.split('.')), 10);315 let page = _.find(pageArray, (p) => parseInt(p.id, 10) === currentPageId);316 if (page && page.url !== this.getCurrentUrl()) {317 logger.debug(`Redirected from '${this.getCurrentUrl()}' to '${page.url}'`);318 this.setCurrentUrl(page.url);319 }320 }321 if (util.hasValue(newPage)) {322 this.selectingNewPage = true;323 await this.remote.selectPage(appIdKey, parseInt(newPage, 10));324 this.selectingNewPage = false;325 this.curContext = `${appIdKey}.${newPage}`;326 }327 this.windowHandleCache = pageArray;328};329extensions.getLatestWebviewContextForTitle = async function getLatestWebviewContextForTitle (regExp) {330 let contexts = await this.getContextsAndViews();331 let matchingCtx;332 for (let ctx of contexts) {333 if (ctx.view && ((ctx.view.title && ctx.view.title.match(regExp)) || (ctx.view.url && ctx.view.url.match(regExp)))) {334 if (ctx.view.url !== 'about:blank') {335 matchingCtx = ctx;336 } else {337 // in the cases of Xcode < 5 (i.e., iOS SDK Version less than 7)338 // iOS 7.1, iOS 9.0 & iOS 9.1 in a webview (not in Safari)339 // we can have the url be `about:blank`340 if (parseFloat(this.iosSdkVersion) < 7 || parseFloat(this.iosSdkVersion) >= 9 ||341 (this.opts.platformVersion === '7.1' && this.opts.app && this.opts.app.toLowerCase() !== 'safari')) {342 matchingCtx = ctx;343 }344 }345 break;346 }347 }348 return matchingCtx ? matchingCtx.id : undefined;349};350// Right now we don't necessarily wait for webview351// and frame to load, which leads to race conditions and flakiness,352// let's see if we can transition to something better353extensions.useNewSafari = function useNewSafari () {354 return parseFloat(this.iosSdkVersion) >= 8.1 &&355 parseFloat(this.opts.platformVersion) >= 8.1 &&356 !this.isRealDevice() &&357 this.opts.safari;358};359extensions.navToInitialWebview = async function navToInitialWebview () {360 let timeout = 0;361 if (this.isRealDevice()) {362 timeout = 3000;363 logger.debug(`Waiting for ${timeout} ms before navigating to view.`);364 }365 await B.delay(timeout);366 if (this.useNewSafari()) {367 await this.typeAndNavToUrl();368 } else if (parseInt(this.iosSdkVersion, 10) >= 7 && !this.isRealDevice() && this.opts.safari) {369 await this.navToViewThroughFavorites();370 } else {371 await this.navToViewWithTitle(/.*/);372 }373};374async function openNewPage () {375 let newPageButton = await this.findElement('xpath', "//UIAButton[contains(@name,'New page')]");376 await this.nativeTap(newPageButton.ELEMENT);377}378extensions.typeAndNavToUrl = async function typeAndNavToUrl () {379 let address = this.opts.address ? this.opts.address : '127.0.0.1';380 this.setCurrentUrl(this.caps.safariInitialUrl || `http://${address}:${this.opts.port}/welcome`);381 let tries = 0;382 const MAX_TRIES = 2;383 let navigate = async () => {384 let oldImpWait = this.implicitWaitMs;385 this.implicitWaitMs = 7000;386 // find the url bar, and tap on it. retry to make sure we don't try387 // too soon while the view is still loading388 let el = await retryInterval(3, 1000, async () => await this.findElement('accessibility id', 'URL'));389 this.implicitWaitMs = oldImpWait;390 try {391 await this.nativeTap(el.ELEMENT);392 } catch (err) {393 if (_.includes(err.message, 'could not be tapped')) {394 if (tries++ >= MAX_TRIES) throw err; // eslint-disable-line curly395 // generally this means that Safari is in page viewing mode396 // so try to open a new page and then redo the navigation397 await openNewPage();398 return await navigate();399 } else {400 throw err;401 }402 }403 // get the last address element and set the url404 try {405 let el = await this.findElement('class name', 'UIATextField');406 await this.setValueImmediate(this.getCurrentUrl(), el);407 } catch (err) {408 // this is flakey on certain systems so we retry until we get something409 // ios sims: safari opens but the text field can't be found410 if (tries++ >= MAX_TRIES) throw err; // eslint-disable-line curly411 return await navigate();412 }413 // make it happen414 try {415 el = await this.findElement('accessibility id', 'Go');416 await this.nativeTap(el.ELEMENT);417 } catch (err) {418 if (_.includes(err.message, 'could not be tapped')) {419 logger.error('Unable to submit URL because \'Go\' button could not be tapped. ' +420 'Please make sure your keyboard is toggled on.');421 }422 throw err;423 }424 await this.navToViewWithTitle(undefined, new RegExp(this.getCurrentUrl(), 'i'));425 // wait for page to finish loading.426 await this.remote.pageUnload();427 };428 await navigate();429};430extensions.navToViewThroughFavorites = async function navToViewThroughFavorites () {431 logger.debug('We are on iOS7+ simulator: clicking apple button to get into a webview');432 let oldImpWait = this.implicitWaitMs;433 this.implicitWaitMs = 7000; // wait 7s for apple button to exist434 let el;435 try {436 el = await this.findElement('xpath', '//UIAScrollView[1]/UIAButton[1]');437 } catch (err) {438 let msg = 'Could not find button to click to get into webview. ' +439 'Proceeding on the assumption we have a working one.';440 logger.error(msg);441 this.implicitWaitMs = oldImpWait;442 return await this.navToViewWithTitle(/.*/i);443 }444 this.implicitWaitMs = oldImpWait;445 try {446 await this.nativeTap(el.ELEMENT);447 } catch (err) {448 let msg = 'Could not click button to get into webview. ' +449 'Proceeding on the assumption we have a working one.';450 logger.error(msg);451 }452 await this.navToViewWithTitle(/apple/i);453};454extensions.navToViewWithTitle = async function navToViewWithTitle (titleRegex, urlRegExp) {455 logger.debug('Navigating to most recently opened webview');456 let start = Date.now();457 let spinTime = 500;458 let spinHandles = async () => {459 let res;460 try {461 res = await this.getLatestWebviewContextForTitle(titleRegex || urlRegExp);462 } catch (err) {463 if (!err.message.includes('Could not connect to a valid app after')) {464 const error = new Error(`Could not navigate to webview! Err: ${err.message}`);465 error.stack += `\nCaused by: ${err.stack}`;466 throw error;467 }468 logger.debug('Could not navigate to webview. Retrying if possible.');469 }470 if (res) {471 let latestWindow = res;472 logger.debug(`Picking webview '${latestWindow}'`);473 await this.setContext(latestWindow);474 await this.remote.cancelPageLoad();475 return;476 }477 // no webview was found478 if ((Date.now() - start) >= 90000) {479 // too slow, get out480 throw new Error('Could not navigate to webview; there are none!');481 }482 logger.warn('Could not find any webviews yet, refreshing/retrying');483 if (this.isRealDevice() || !this.opts.safari) {484 // on a real device, when not using Safari, we just want to try again485 await B.delay(spinTime);486 return await spinHandles();487 }488 // find the reload button and tap it, if possible489 let element;490 try {491 logger.debug('Finding and tapping reload button');492 element = await this.findUIElementOrElements('accessibility id', 'ReloadButton', '', false);493 await this.nativeTap(element.ELEMENT);494 } catch (err) {495 logger.warn(`Error finding and tapping reload button: ${err.message}`);496 logger.warn('Retrying.');497 await B.delay(spinTime);498 }499 // try it all again500 return await spinHandles();501 };502 await spinHandles();503};504helpers.closeAlertBeforeTest = async function closeAlertBeforeTest () {505 let present = await this.uiAutoClient.sendCommand('au.alertIsPresent()');506 if (!present) {507 return false;508 }509 logger.debug('Alert present before starting test, let us banish it');510 await this.uiAutoClient.sendCommand('au.dismissAlert()');511 logger.debug('Alert banished!');512 return true;513};514helpers.stopRemote = async function stopRemote (closeWindowBeforeDisconnecting = false) {515 if (!this.remote) {516 logger.errorAndThrow('Tried to leave a web frame but were not in one');517 }518 if (closeWindowBeforeDisconnecting) {519 await this.closeWindow();520 }521 await this.remote.disconnect();522 this.curContext = null;523 this.curWebFrames = [];524 this.curWebCoords = null;525 this.remote = null;526};527helpers.isWebContext = function isWebContext () {528 return !!this.curContext && this.curContext !== NATIVE_WIN;529};530helpers.setCurrentUrl = function setCurrentUrl (url) {531 this._currentUrl = url;532};533helpers.getCurrentUrl = function getCurrentUrl () {534 return this._currentUrl;535};536Object.assign(extensions, commands, helpers);537export { commands, helpers, NATIVE_WIN, WEBVIEW_WIN, WEBVIEW_BASE };...
selendroid.js
Source:selendroid.js
1"use strict";2var Device = require('../device.js')3 , mkdirp = require('mkdirp')4 , _ = require('underscore')5 , deviceCommon = require('../common.js')6 , androidController = require('./android-controller.js')7 , androidContextController = require('./android-context-controller.js')8 , proxyTo = deviceCommon.proxyTo9 , doRequest = deviceCommon.doRequest10 , logger = require('../../server/logger.js').get('appium')11 , status = require("../../server/status.js")12 , fs = require('fs')13 , async = require('async')14 , androidCommon = require('./android-common.js')15 , androidHybrid = require('./android-hybrid.js')16 , path = require('path')17 , md5 = require('md5calculator')18 , utf7 = require('utf7').imap;19var Selendroid = function () {20 this.init();21};22_.extend(Selendroid.prototype, Device.prototype);23Selendroid.prototype._deviceInit = Device.prototype.init;24Selendroid.prototype.init = function () {25 this._deviceInit();26 this.selendroidHost = 'localhost';27 this.selendroidPort = 8080;28 this.selendroidSessionId = null;29 this.appExt = ".apk";30 this.args.devicePort = this.selendroidPort;31 this.serverApk = null;32 this.onStop = function () {};33 this.adb = null;34 this.isProxy = true;35 this.mobileMethodsSupported = [36 'setLocation'37 , 'setCommandTimeout'38 , 'reset'39 , 'lock'40 , 'background'41 , 'keyevent'42 , 'currentActivity'43 , 'installApp'44 , 'uninstallApp'45 , 'removeApp'46 , 'closeApp'47 , 'isAppInstalled'48 , 'launchApp'49 , 'toggleData'50 , 'toggleFlightMode'51 , 'toggleWiFi'52 , 'toggleLocationServices'53 , 'getStrings'54 ];55 this.proxyHost = this.selendroidHost;56 this.avoidProxy = [57 ['GET', new RegExp('^/wd/hub/session/[^/]+/log/types$')]58 , ['POST', new RegExp('^/wd/hub/session/[^/]+/log')]59 , ['POST', new RegExp('^/wd/hub/session/[^/]+/location')]60 , ['POST', new RegExp('^/wd/hub/session/[^/]+/appium')]61 , ['GET', new RegExp('^/wd/hub/session/[^/]+/appium')]62 , ['POST', new RegExp('^/wd/hub/session/[^/]+/context')]63 , ['GET', new RegExp('^/wd/hub/session/[^/]+/context')]64 , ['GET', new RegExp('^/wd/hub/session/[^/]+/contexts')]65 , ['POST', new RegExp('^/wd/hub/session/[^/]+/element/[^/]+/value')]66 , ['GET', new RegExp('^/wd/hub/session/[^/]+/network_connection')]67 , ['POST', new RegExp('^/wd/hub/session/[^/]+/network_connection')]68 , ['POST', new RegExp('^/wd/hub/session/[^/]+/ime')]69 , ['GET', new RegExp('^/wd/hub/session/[^/]+/ime')]70 ];71 this.curContext = this.defaultContext();72};73Selendroid.prototype.getSettings = deviceCommon.getSettings;74Selendroid.prototype.updateSettings = deviceCommon.updateSettings;75Selendroid.prototype.pushUnlock = androidController.pushUnlock;76Selendroid.prototype.unlock = androidController.unlock;77Selendroid.prototype.installApp = androidController.installApp;78Selendroid.prototype.isAppInstalled = androidController.isAppInstalled;79Selendroid.prototype.removeApp = androidController.removeApp;80_.extend(Selendroid.prototype, androidCommon);81Selendroid.prototype._deviceConfigure = Device.prototype.configure;82Selendroid.prototype._setAndroidArgs = androidCommon.setAndroidArgs;83Selendroid.prototype.setAndroidArgs = function () {84 this._setAndroidArgs();85 this.args.systemPort = this.args.selendroidPort;86 this.proxyPort = this.args.systemPort;87};88Selendroid.prototype.start = function (cb) {89 logger.debug("Starting selendroid server");90 var modServerExists = false91 , modAppPkg = null92 , resignedServerMd5Hash = null;93 var checkModServerExists = function (cb) {94 this.selendroidServerPath = path.resolve(this.args.tmpDir,95 'selendroid.' + this.args.appPackage + '.apk');96 modAppPkg = this.args.appPackage + '.selendroid';97 fs.exists(this.selendroidServerPath, function (exists) {98 modServerExists = exists;99 cb();100 });101 }.bind(this);102 var checkServerResigned = function (cb) {103 if (modServerExists) {104 md5(this.selendroidServerPath, function (err, md5Hash) {105 if (err) return cb(err);106 logger.debug("MD5 for selendroid server is " + md5Hash);107 if (resignedServerMd5Hash !== md5Hash) {108 resignedServerMd5Hash = md5Hash;109 modServerExists = false;110 }111 }.bind(this));112 }113 cb();114 }.bind(this);115 var conditionalUninstallSelendroid = function (cb) {116 if (!modServerExists) {117 logger.debug("Rebuilt selendroid apk does not exist, uninstalling " +118 "any instances of it on device to make way for new one");119 this.adb.uninstallApk(modAppPkg, cb);120 } else {121 logger.debug("Rebuilt selendroid apk exists, doing nothing");122 cb();123 }124 }.bind(this);125 var conditionalInsertManifest = function (cb) {126 if (!modServerExists) {127 logger.debug("Rebuilt selendroid server does not exist, inserting " +128 "modified manifest");129 this.insertSelendroidManifest(this.serverApk, cb);130 } else {131 logger.debug("Rebuilt selendroid server already exists, no need to " +132 "rebuild it with a new manifest");133 cb();134 }135 }.bind(this);136 var conditionalInstallSelendroid = function (cb) {137 this.adb.isAppInstalled(modAppPkg, function (e, installed) {138 if (!installed) {139 logger.debug("Rebuilt selendroid is not installed, installing it");140 this.adb.install(this.selendroidServerPath, cb);141 } else {142 logger.debug("Rebuilt selendroid is already installed");143 cb();144 }145 }.bind(this));146 }.bind(this);147 async.series([148 this.initJavaVersion.bind(this),149 this.initAdb.bind(this),150 this.ensureServerExists.bind(this),151 this.prepareDevice.bind(this),152 this.checkInternetPermissionForApp.bind(this),153 this.packageAndLaunchActivityFromManifest.bind(this),154 checkModServerExists,155 conditionalInsertManifest,156 this.checkSelendroidCerts.bind(this),157 checkServerResigned,158 conditionalUninstallSelendroid,159 conditionalInstallSelendroid,160 this.extractStringsSelendroid.bind(this),161 this.uninstallApp.bind(this),162 this.installAppForTest.bind(this),163 this.forwardPort.bind(this),164 this.initUnicode.bind(this),165 this.pushSettingsApp.bind(this),166 this.pushUnlock.bind(this),167 this.unlock.bind(this),168 this.pushSelendroid.bind(this),169 this.waitForServer.bind(this)170 ], function (err) {171 if (err) return cb(err);172 async.series([173 this.createSession.bind(this),174 this.initAutoWebview.bind(this)175 ], function (err, res) {176 if (err) return cb(err);177 // `createSession` returns session id, so send that along178 cb(null, res[0]);179 });180 }.bind(this));181};182Selendroid.prototype.pushSelendroid = function (cb) {183 var instrumentWith = this.args.appPackage + ".selendroid/" +184 "io.selendroid.server.ServerInstrumentation";185 this.adb.instrument(this.args.appPackage, this.args.appActivity, instrumentWith, cb);186};187Selendroid.prototype.checkInternetPermissionForApp = function (cb) {188 var apk = this.args.app;189 this.adb.hasInternetPermissionFromManifest(apk, function (err, hasInternetPermission) {190 if (err) return cb(err);191 if (hasInternetPermission) {192 return cb();193 }194 else {195 var errorMessg = "apk does not have INTERNET permissions. Selendroid needs internet " +196 "permission to proceed, please check if you have <uses-permission " +197 "android:name=\"android.**permission.INTERNET\"/> in your " +198 "AndroidManifest.xml";199 cb(new Error(errorMessg));200 }201 });202};203Selendroid.prototype.checkSelendroidCerts = function (cb) {204 var alreadyReturned = false205 , checks = 0;206 var onDoneSigning = function () {207 checks++;208 if (checks === 2 && !alreadyReturned) {209 cb();210 }211 };212 // these run in parallel213 var apks = [this.selendroidServerPath, this.args.app];214 _.each(apks, function (apk) {215 logger.debug("Checking signed status of " + apk);216 this.adb.checkApkCert(apk, this.args.appPackage, function (err, isSigned) {217 if (err) return cb(err);218 if (isSigned) return onDoneSigning();219 this.adb.sign(apk, function (err) {220 if (err && !alreadyReturned) {221 alreadyReturned = true;222 return cb(err);223 }224 onDoneSigning();225 });226 }.bind(this));227 }.bind(this));228};229Selendroid.prototype.stop = function (ocb) {230 var completeShutdown = function (cb) {231 if (this.args.unicodeKeyboard && this.args.resetKeyboard && this.defaultIME) {232 logger.debug('Resetting IME to \'' + this.defaultIME + '\'');233 this.adb.setIME(this.defaultIME, function (err) {234 if (err) {235 // simply warn on error here, because we don't want to stop the shutdown236 // process237 logger.warn(err);238 }239 logger.debug("Stopping selendroid server");240 this.deleteSession(cb);241 }.bind(this));242 } else {243 logger.debug("Stopping selendroid server");244 this.deleteSession(cb);245 }246 }.bind(this);247 completeShutdown(function (err) {248 if (err) return ocb(err);249 // Remove the app _after_ stopping Selendroid, or Selendroid will fail250 if (this.args.fullReset) {251 logger.debug("Removing app from device");252 this.uninstallApp(function (err) {253 if (err) {254 // simply warn on error here, because we don't want to stop the shutdown255 // process256 logger.warn(err);257 }258 ocb();259 });260 } else {261 ocb();262 }263 }.bind(this));264};265Selendroid.prototype.keyevent = function (keycode, metastate, cb) {266 this.adb.keyevent(keycode, function () {267 cb(null, {268 status: status.codes.Success.code269 , value: null270 });271 });272};273/*274 * Execute an arbitrary function and handle potential ADB disconnection before275 * proceeding276 */277Selendroid.prototype.wrapActionAndHandleADBDisconnect = function (action, ocb) {278 async.series([279 function (cb) {280 action(cb);281 }.bind(this)282 , this.adb.restart.bind(this.adb)283 , this.forwardPort.bind(this)284 ], function (err) {285 ocb(err);286 }.bind(this));287};288Selendroid.prototype.ensureServerExists = function (cb) {289 logger.debug("Checking whether selendroid is built yet");290 var selBin = path.resolve(__dirname, "..", "..", "..", "build", "selendroid",291 "selendroid.apk");292 fs.stat(selBin, function (err) {293 if (err) {294 logger.debug("Selendroid needs to be built; please run ./reset.sh " +295 "--selendroid");296 return cb(err);297 }298 logger.debug("Selendroid server exists!");299 this.serverApk = selBin;300 cb(null);301 }.bind(this));302};303Selendroid.prototype.waitForServer = function (cb) {304 var waitMs = 20000305 , intMs = 800306 , start = Date.now();307 var pingServer = function () {308 this.proxyTo('/wd/hub/status', 'GET', null, function (err, res, body) {309 if (body === null || typeof body === "undefined" || !body.trim()) {310 if (Date.now() - start < waitMs) {311 setTimeout(pingServer, intMs);312 } else {313 cb(new Error("Waited " + (waitMs / 1000) + " secs for " +314 "selendroid server and it never showed up"));315 }316 } else {317 logger.debug("Selendroid server is alive!");318 cb(null);319 }320 });321 }.bind(this);322 pingServer();323};324Selendroid.prototype.createSession = function (cb) {325 logger.debug("Listening for Selendroid logs");326 this.adb.logcat.on('log', function (logObj) {327 if (/System/.test(logObj.message)) {328 var type = "";329 if (/System\.err/.test(logObj.message)) {330 type = " ERR";331 }332 var msg = logObj.message.replace(/^.+: /, '');333 logger.debug("[SELENDROID" + type + "] " + msg);334 }335 }.bind(this));336 logger.debug("Creating Selendroid session");337 var data = {desiredCapabilities: this.capabilities};338 this.proxyTo('/wd/hub/session', 'POST', data, function (err, res, body) {339 if (err) return cb(err);340 if (res.statusCode === 200 && body.sessionId) {341 logger.debug("Successfully started selendroid session");342 this.selendroidSessionId = body.sessionId;343 this.proxySessionId = this.selendroidSessionId;344 this.adb.waitForActivity(this.args.appWaitPackage, this.args.appWaitActivity, 1800,345 function (err) {346 if (err) {347 logger.debug("Selendroid hasn't started app yet, let's do it " +348 "manually with adb.startApp");349 var onStart = function (err) {350 if (err) return cb(err);351 return cb(null, body.sessionId);352 }.bind(this);353 return this.adb.startApp({354 pkg: this.args.appPackage,355 activity: this.args.appActivity,356 action: this.args.intentAction,357 category: this.args.intentCategory,358 flags: this.args.intentFlags,359 waitPkg: this.args.appWaitPackage,360 waitActivity: this.args.appWaitActivity,361 optionalIntentArguments: this.args.optionalIntentArguments,362 stopApp: this.args.stopAppOnReset,363 retry: false364 }, onStart);365 }366 return cb(null, body.sessionId);367 }.bind(this), 1800);368 } else {369 logger.error("Selendroid create session did not work. Status was " +370 res.statusCode + " and body was " + body);371 cb(new Error("Selendroid session creation did not work."));372 }373 }.bind(this));374};375Selendroid.prototype.deleteSession = function (cb) {376 var url = '/wd/hub/session/' + this.selendroidSessionId;377 this.proxyTo(url, 'DELETE', null, function (err, res) {378 if (err) return cb(err);379 if (res.statusCode !== 200) return cb(new Error("Status was not 200"));380 this.adb.forceStop(this.args.appPackage, function (err) {381 if (err) return cb(err);382 this.adb.stopLogcat(cb);383 }.bind(this));384 }.bind(this));385};386Selendroid.prototype.proxyTo = proxyTo;387Selendroid.prototype.insertSelendroidManifest = function (serverPath, cb) {388 logger.debug("Inserting selendroid manifest");389 var newServerPath = this.selendroidServerPath390 , newPackage = this.args.appPackage + '.selendroid'391 , srcManifest = path.resolve(__dirname, '..', '..', '..', 'build',392 'selendroid', 'AndroidManifest.xml')393 , dstDir = path.resolve(this.args.tmpDir, this.args.appPackage)394 , dstManifest = path.resolve(dstDir, 'AndroidManifest.xml');395 try {396 fs.mkdirSync(dstDir);397 } catch (e) {398 if (e.message.indexOf("EEXIST") === -1) {399 throw e;400 }401 }402 fs.writeFileSync(dstManifest, fs.readFileSync(srcManifest, "utf8"), "utf8");403 async.series([404 function (cb) { mkdirp(dstDir, cb); }.bind(this),405 function (cb) { this.adb.checkSdkBinaryPresent("aapt", cb); }.bind(this),406 function (cb) {407 this.adb.compileManifest(dstManifest, newPackage,408 this.args.appPackage, cb);409 }.bind(this),410 function (cb) {411 this.adb.insertManifest(dstManifest, serverPath,412 newServerPath, cb);413 }.bind(this)414 ], cb);415};416Selendroid.prototype.setLocation = androidController.setLocation;417Selendroid.prototype.removeApp = androidController.removeApp;418Selendroid.prototype.unpackApp = androidController.unpackApp;419Selendroid.prototype.translatePath = function (req) {420 var path = req.originalUrl;421 if (path.indexOf("contexts") !== -1) {422 logger.debug("Temporarily translating 'contexts' to 'window_handles");423 path = path.replace("contexts", "window_handles");424 } else if (path.indexOf("context") !== -1) {425 logger.debug("Temporarily translating 'context' to 'window'");426 path = path.replace("context", "window");427 }428 req.originalUrl = path;429};430Selendroid.prototype.extractStringsSelendroid = function (cb) {431 this.extractStrings(function () {432 cb();433 });434};435Selendroid.prototype.getStrings = function (language, stringFile, cb) {436 if (this.language && this.language === language) {437 // Return last strings438 return cb(null, {439 status: status.codes.Success.code,440 value: this.apkStrings441 });442 }443 // Extract and return strings444 return this.extractStrings(function () {445 cb(null, {446 status: status.codes.Success.code,447 value: this.apkStrings448 });449 }.bind(this), language);450};451_.extend(Selendroid.prototype, androidHybrid);452_.extend(Selendroid.prototype, androidContextController);453Selendroid.prototype.isChromedriverContext = function (windowName) {454 return windowName === this.CHROMIUM_WIN;455};456Selendroid.prototype.getContexts = function (cb) {457 var chromiumViews = [];458 this.listWebviews(function (err, webviews) {459 if (err) return cb(err);460 if (_.contains(webviews, this.CHROMIUM_WIN)) {461 chromiumViews = [this.CHROMIUM_WIN];462 } else {463 chromiumViews = [];464 }465 var selendroidViews = [];466 var reqUrl = this.selendroidHost + ':' + this.args.selendroidPort + '/wd/hub/session/' + this.selendroidSessionId;467 doRequest(reqUrl + '/window_handles', 'GET', {}, null, function (err, res) {468 if (err) return cb(err);469 selendroidViews = JSON.parse(res.body).value;470 this.contexts = _.union(selendroidViews, chromiumViews);471 logger.debug("Available contexts: " + JSON.stringify(this.contexts));472 cb(null, {sessionId: this.selendroidSessionId, status: status.codes.Success.code, value: this.contexts});473 }.bind(this));474 }.bind(this));475};476Selendroid.prototype.defaultWebviewName = function () {477 return this.WEBVIEW_WIN + "_0";478};479Selendroid.prototype.setValue = function (elementId, value, cb) {480 logger.debug('Setting text on element \'' + elementId + '\': \'' + value + '\'');481 for (var i = 0; i < value.length; i++) {482 var c = value.charCodeAt(i);483 // if we're using the unicode keyboard, and this is unicode, maybe encode484 if (this.args.unicodeKeyboard && (c > 127 || c === 38)) {485 // this is not simple ascii, or it is an ampersand (`&`)486 if (c >= parseInt("E000", 16) && c <= parseInt("E040", 16)) {487 // Selenium uses a Unicode PUA to cover certain special characters488 // see https://code.google.com/p/selenium/source/browse/java/client/src/org/openqa/selenium/Keys.java489 } else {490 // encode the text491 value = utf7.encode(value);492 break;493 }494 }495 }496 var reqUrl = this.proxyHost + ':' + this.proxyPort +497 '/wd/hub/session/' + this.proxySessionId +498 '/element/' + elementId + '/value';499 doRequest(reqUrl, 'POST', { value: [value] }, null, function (err) {500 if (err) return cb(err);501 cb(null, {502 status: status.codes.Success.code,503 value: ''504 });505 });506};507Selendroid.prototype.back = function (cb) {508 this.keyevent(4, null, cb);509};...
driver.js
Source:driver.js
...261 if (util.hasValue(this.opts.orientation)) {262 log.debug(`Setting initial orientation to '${this.opts.orientation}'`);263 await this.setOrientation(this.opts.orientation);264 }265 await this.initAutoWebview();266 }267 async initAutoWebview () {268 if (this.opts.autoWebview) {269 let viewName = this.defaultWebviewName();270 let timeout = (this.opts.autoWebviewTimeout) || 2000;271 log.info(`Setting auto webview to context '${viewName}' with timeout ${timeout}ms`);272 // try every 500ms until timeout is over273 await retryInterval(timeout / 500, 500, async () => {274 await this.setContext(viewName);275 });276 }277 }278 async initAUT () {279 // populate appPackage, appActivity, appWaitPackage, appWaitActivity,...
android.js
Source:android.js
1"use strict";2var errors = require('../../server/errors.js')3 , path = require('path')4 , fs = require('fs')5 , Device = require('../device.js')6 , _ = require('underscore')7 , logger = require('../../server/logger.js').get('appium')8 , deviceCommon = require('../common.js')9 , status = require("../../server/status.js")10 , async = require('async')11 , androidController = require('./android-controller.js')12 , androidContextController = require('./android-context-controller.js')13 , androidCommon = require('./android-common.js')14 , androidHybrid = require('./android-hybrid.js')15 , UiAutomator = require('./uiautomator.js')16 , UnknownError = errors.UnknownError;17var Android = function () {18 this.init();19};20_.extend(Android.prototype, Device.prototype);21Android.prototype._deviceInit = Device.prototype.init;22Android.prototype.init = function () {23 this._deviceInit();24 this.appExt = ".apk";25 this.capabilities = {26 platform: 'LINUX'27 , browserName: 'Android'28 , platformVersion: '4.1'29 , webStorageEnabled: false30 , takesScreenshot: true31 , javascriptEnabled: true32 , databaseEnabled: false33 , networkConnectionEnabled: true34 , locationContextEnabled: false35 };36 this.args.devicePort = 4724;37 this.appMd5Hash = null;38 this.args.avd = null;39 this.args.language = null;40 this.args.locale = null;41 this.args.javaVersion = null;42 this.initQueue();43 this.implicitWaitMs = 0;44 this.shuttingDown = false;45 this.adb = null;46 this.uiautomator = null;47 this.uiautomatorRestartOnExit = false;48 this.uiautomatorIgnoreExit = false;49 this.swipeStepsPerSec = 28;50 this.dragStepsPerSec = 40;51 this.asyncWaitMs = 0;52 this.remote = null;53 this.contexts = [];54 this.curContext = this.defaultContext();55 this.didLaunch = false;56 this.launchCb = function () {};57 this.uiautomatorExitCb = function () {};58 this.dataDir = null;59 this.isProxy = false;60 this.proxyHost = null;61 this.proxyPort = null;62 this.proxySessionId = null;63 this.avoidProxy = [64 ['POST', new RegExp('^/wd/hub/session/[^/]+/context')]65 , ['GET', new RegExp('^/wd/hub/session/[^/]+/context')]66 , ['GET', new RegExp('^/wd/hub/session/[^/]+/contexts')]67 , ['POST', new RegExp('^/wd/hub/session/[^/]+/appium')]68 , ['GET', new RegExp('^/wd/hub/session/[^/]+/appium')]69 ];70 // listen for changes to ignoreUnimportantViews71 this.settings.on("update", function (update) {72 if (update.key === "ignoreUnimportantViews") {73 this.setCompressedLayoutHierarchy(update.value, update.callback);74 } else {75 update.callback();76 }77 }.bind(this));78};79Android.prototype._deviceConfigure = Device.prototype.configure;80Android.prototype.noLaunchSetup = function (cb) {81 logger.debug("Setting up Android for 'autoLaunch: false'");82 async.series([83 this.initJavaVersion.bind(this),84 this.initAdb.bind(this),85 ], function (err) { cb(err); });86};87Android.prototype.start = function (cb, onDie) {88 this.launchCb = cb;89 this.uiautomatorExitCb = onDie;90 logger.info("Starting android appium");91 async.series([92 this.initJavaVersion.bind(this),93 this.initAdb.bind(this),94 this.packageAndLaunchActivityFromManifest.bind(this),95 this.initUiautomator.bind(this),96 this.prepareDevice.bind(this),97 this.checkApiLevel.bind(this),98 this.pushStrings.bind(this),99 this.processFromManifest.bind(this),100 this.uninstallApp.bind(this),101 this.installAppForTest.bind(this),102 this.forwardPort.bind(this),103 //this.pushAppium.bind(this),104 this.initUnicode.bind(this),105 //this.pushSettingsApp.bind(this),106 //this.pushUnlock.bind(this),107 function (cb) {this.uiautomator.start(cb);}.bind(this),108 this.wakeUp.bind(this),109 this.unlock.bind(this),110 this.getDataDir.bind(this),111 this.setupCompressedLayoutHierarchy.bind(this),112 this.startAppUnderTest.bind(this),113 this.initAutoWebview.bind(this),114 this.setActualCapabilities.bind(this)115 ], function (err) {116 if (err) {117 this.shutdown(function () {118 this.launchCb(err);119 }.bind(this));120 } else {121 this.didLaunch = true;122 this.launchCb(null, this.proxySessionId);123 }124 }.bind(this));125};126Android.prototype.initUiautomator = function (cb) {127 if (this.uiautomator === null) {128 this.uiautomator = new UiAutomator(this.adb, this.args);129 this.uiautomator.setExitHandler(this.onUiautomatorExit.bind(this));130 }131 return cb();132};133Android.prototype.onLaunch = function (err) {134 var readyToGo = function () {135 this.didLaunch = true;136 this.launchCb();137 }.bind(this);138 var giveUp = function (err) {139 this.shutdown(function () {140 this.launchCb(err);141 }.bind(this));142 }.bind(this);143 if (err) {144 if (this.checkShouldRelaunch(err)) {145 logger.error(err);146 logger.error("Above error isn't fatal, maybe relaunching adb will help....");147 this.adb.waitForDevice(function (err) {148 if (err) return giveUp(err);149 readyToGo();150 });151 } else {152 giveUp(err);153 }154 } else {155 readyToGo();156 }157};158Android.prototype.restartUiautomator = function (cb) {159 async.series([160 this.forwardPort.bind(this)161 , this.uiautomator.start.bind(this.uiautomator)162 , this.setupCompressedLayoutHierarchy.bind(this)163 ], cb);164};165/*166 * Execute an arbitrary function and handle potential ADB disconnection before167 * proceeding168 */169Android.prototype.wrapActionAndHandleADBDisconnect = function (action, ocb) {170 async.series([171 function (cb) {172 this.uiautomatorIgnoreExit = true;173 action(cb);174 }.bind(this)175 , this.adb.restart.bind(this.adb)176 , this.restartUiautomator.bind(this)177 ], function (err) {178 this.uiautomatorIgnoreExit = false;179 ocb(err);180 }.bind(this));181};182Android.prototype.onUiautomatorExit = function () {183 logger.debug("UiAutomator exited");184 var respondToClient = function () {185 this.stopChromedriverProxies(function () {186 this.cleanup();187 if (!this.didLaunch) {188 var msg = "UiAutomator quit before it successfully launched";189 logger.error(msg);190 this.launchCb(new Error(msg));191 return;192 } else if (typeof this.cbForCurrentCmd === "function") {193 var error = new UnknownError("UiAutomator died while responding to " +194 "command, please check appium logs!");195 this.cbForCurrentCmd(error, null);196 }197 // make sure appium.js knows we crashed so it can clean up198 this.uiautomatorExitCb();199 }.bind(this));200 }.bind(this);201 if (this.adb) {202 var uninstall = function () {203 logger.debug("Attempting to uninstall app");204 this.uninstallApp(function () {205 this.shuttingDown = false;206 respondToClient();207 }.bind(this));208 }.bind(this);209 if (!this.uiautomatorIgnoreExit) {210 this.adb.ping(function (err, ok) {211 if (ok) {212 uninstall();213 } else {214 logger.debug(err);215 this.adb.restart(function (err) {216 if (err) {217 logger.debug(err);218 }219 if (this.uiautomatorRestartOnExit) {220 this.uiautomatorRestartOnExit = false;221 this.restartUiautomator(function (err) {222 if (err) {223 logger.debug(err);224 uninstall();225 }226 }.bind(this));227 } else {228 uninstall();229 }230 }.bind(this));231 }232 }.bind(this));233 } else {234 this.uiautomatorIgnoreExit = false;235 }236 } else {237 logger.debug("We're in uiautomator's exit callback but adb is gone already");238 respondToClient();239 }240};241Android.prototype.checkShouldRelaunch = function (launchErr) {242 if (launchErr.message === null || typeof launchErr.message === 'undefined') {243 logger.error("We're checking if we should relaunch based on something " +244 "which isn't an error object. Check the codez!");245 return false;246 }247 var msg = launchErr.message.toString();248 var relaunchOn = [249 'Could not find a connected Android device'250 , 'Device did not become ready'251 ];252 var relaunch = false;253 _.each(relaunchOn, function (relaunchMsg) {254 relaunch = relaunch || msg.indexOf(relaunchMsg) !== -1;255 });256 return relaunch;257};258Android.prototype.checkApiLevel = function (cb) {259 this.adb.getApiLevel(function (err, apiLevel) {260 if (err) return cb(err);261 logger.info('Device API level is:', parseInt(apiLevel, 10));262 if (parseInt(apiLevel) < 17) {263 var msg = "Android devices must be of API level 17 or higher. Please change your device to Selendroid or upgrade Android on your device.";264 logger.error(msg); // logs the error when we encounter it265 return cb(new Error(msg)); // send the error up the chain266 }267 cb();268 });269};270Android.prototype.decorateChromeOptions = function (caps) {271 // add options from appium session caps272 if (this.args.chromeOptions) {273 _.each(this.args.chromeOptions, function (val, option) {274 if (typeof caps.chromeOptions[option] === "undefined") {275 caps.chromeOptions[option] = val;276 } else {277 logger.warn("Cannot pass option " + caps.chromeOptions[option] + " because Appium needs it to make chromeDriver work");278 }279 });280 }281 // add device id from adb282 caps.chromeOptions.androidDeviceSerial = this.adb.curDeviceId;283 return caps;284};285Android.prototype.processFromManifest = function (cb) {286 if (!this.args.app) {287 return cb();288 } else { // apk must be local to process the manifest.289 this.adb.processFromManifest(this.args.app, function (err, process) {290 var value = process || this.args.appPackage;291 this.appProcess = value;292 logger.debug("Set app process to: " + this.appProcess);293 cb();294 }.bind(this));295 }296};297Android.prototype.pushStrings = function (cb, language) {298 var outputPath = path.resolve(this.args.tmpDir, this.args.appPackage);299 var remotePath = '/data/local/tmp';300 var stringsJson = 'strings.json';301 this.extractStrings(function (err) {302 if (err) {303 if (!fs.existsSync(this.args.app)) {304 // apk doesn't exist locally so remove old strings.json305 logger.debug("Could not get strings, but it looks like we had an " +306 "old strings file anyway, so ignoring");307 return this.adb.rimraf(remotePath + '/' + stringsJson, function (err) {308 if (err) return cb(new Error("Could not remove old strings"));309 cb();310 });311 } else {312 // if we can't get strings, just dump an empty json and continue313 logger.warn("Could not get strings, continuing anyway");314 var remoteFile = remotePath + '/' + stringsJson;315 return this.adb.shell("echo '{}' > " + remoteFile, cb);316 }317 }318 var jsonFile = path.resolve(outputPath, stringsJson);319 this.adb.push(jsonFile, remotePath, function (err) {320 if (err) return cb(new Error("Could not push strings.json"));321 cb();322 });323 }.bind(this), language);324};325Android.prototype.getStrings = function (language, stringFile, cb) {326 if (this.language && this.language === language) {327 // Return last strings328 return cb(null, {329 status: status.codes.Success.code,330 value: this.apkStrings331 });332 }333 // Extract, push and return strings334 return this.pushStrings(function () {335 this.proxy(["updateStrings", {}], function (err, res) {336 if (err || res.status !== status.codes.Success.code) return cb(err, res);337 cb(null, {338 status: status.codes.Success.code,339 value: this.apkStrings340 });341 }.bind(this));342 }.bind(this), language);343};344Android.prototype.pushAppium = function (cb) {345 logger.debug("Pushing appium bootstrap to device...");346 var binPath = path.resolve(__dirname, "..", "..", "..", "build",347 "android_bootstrap", "AppiumBootstrap.jar");348 fs.stat(binPath, function (err) {349 if (err) {350 cb(new Error("Could not find AppiumBootstrap.jar; please run " +351 "'grunt buildAndroidBootstrap'"));352 } else {353 this.adb.push(binPath, this.remoteTempPath(), cb);354 }355 }.bind(this));356};357Android.prototype.startAppUnderTest = function (cb) {358 this.startApp(this.args, cb);359};360Android.prototype.startApp = function (args, cb) {361 if (args.androidCoverage) {362 this.adb.androidCoverage(args.androidCoverage, args.appWaitPackage,363 args.appWaitActivity, cb);364 } else {365 this.adb.startApp({366 pkg: args.appPackage,367 activity: args.appActivity,368 action: args.intentAction,369 category: args.intentCategory,370 flags: args.intentFlags,371 waitPkg: args.appWaitPackage,372 waitActivity: args.appWaitActivity,373 optionalIntentArguments: args.optionalIntentArguments,374 stopApp: args.stopAppOnReset || !args.dontStopAppOnReset375 }, cb);376 }377};378Android.prototype.stop = function (cb) {379 if (this.shuttingDown) {380 logger.debug("Already in process of shutting down.");381 return cb();382 }383 this.shuttingDown = true;384 var completeShutdown = function (cb) {385 if (this.adb) {386 this.adb.goToHome(function () {387 this.shutdown(cb);388 }.bind(this));389 } else {390 this.shutdown(cb);391 }392 }.bind(this);393 if (this.args.fullReset) {394 logger.debug("Removing app from device");395 this.uninstallApp(function (err) {396 if (err) {397 // simply warn on error here, because we don't want to stop the shutdown398 // process399 logger.warn(err);400 }401 completeShutdown(cb);402 });403 } else {404 completeShutdown(cb);405 }406};407Android.prototype.cleanup = function () {408 logger.debug("Cleaning up android objects");409 this.adb = null;410 this.uiautomator = null;411 this.shuttingDown = false;412};413Android.prototype.shutdown = function (cb) {414 var next = function () {415 this.stopChromedriverProxies(function () {416 if (this.uiautomator) {417 this.uiautomator.shutdown(function () {418 this.cleanup();419 cb();420 }.bind(this));421 } else {422 this.cleanup();423 cb();424 }425 }.bind(this));426 }.bind(this);427 if (this.adb) {428 this.adb.endAndroidCoverage();429 if (this.args.unicodeKeyboard && this.args.resetKeyboard && this.defaultIME) {430 logger.debug('Resetting IME to \'' + this.defaultIME + '\'');431 this.adb.setIME(this.defaultIME, function (err) {432 if (err) {433 // simply warn on error here, because we don't want to stop the shutdown434 // process435 logger.warn(err);436 }437 if (this.adb) {438 this.adb.stopLogcat(function () {439 next();440 }.bind(this));441 }442 }.bind(this));443 } else {444 this.adb.stopLogcat(function () {445 next();446 }.bind(this));447 }448 } else {449 next();450 }451};452Android.prototype.proxy = deviceCommon.proxy;453Android.prototype.respond = deviceCommon.respond;454Android.prototype.initQueue = function () {455 this.queue = async.queue(function (task, cb) {456 var action = task.action,457 params = task.params;458 this.cbForCurrentCmd = cb;459 if (this.adb && !this.shuttingDown) {460 this.uiautomator.sendAction(action, params, function (response) {461 this.cbForCurrentCmd = null;462 if (typeof cb === 'function') {463 this.respond(response, cb);464 }465 }.bind(this));466 } else {467 this.cbForCurrentCmd = null;468 var msg = "Tried to send command to non-existent Android device, " +469 "maybe it shut down?";470 if (this.shuttingDown) {471 msg = "We're in the middle of shutting down the Android device, " +472 "so your request won't be executed. Sorry!";473 }474 this.respond({475 status: status.codes.UnknownError.code476 , value: msg477 }, cb);478 }479 }.bind(this), 1);480};481Android.prototype.push = function (elem) {482 this.queue.push({action: elem[0][0], params: elem[0][1] || {}}, elem[1]);483};484Android.prototype.wakeUp = function (cb) {485 // requires an appium bootstrap connection loaded486 logger.debug("Waking up device if it's not alive");487 this.proxy(["wake", {}], cb);488};489Android.prototype.getDataDir = function (cb) {490 this.proxy(["getDataDir", {}], function (err, res) {491 if (err) return cb(err);492 this.dataDir = res.value;493 logger.debug("dataDir set to: " + this.dataDir);494 cb();495 }.bind(this));496};497// Set CompressedLayoutHierarchy on the device based on current settings object498Android.prototype.setupCompressedLayoutHierarchy = function (cb) {499 // setup using cap500 if (_.has(this.args, 'ignoreUnimportantViews')) {501 // set the setting directly on the internal _settings object, this way we don't trigger an update event502 this.settings._settings.ignoreUnimportantViews = this.args.ignoreUnimportantViews;503 }504 if (_.isUndefined(this.getSetting("ignoreUnimportantViews"))) {505 return cb();506 }507 this.setCompressedLayoutHierarchy(this.getSetting("ignoreUnimportantViews"), cb);508};509// Set CompressedLayoutHierarchy on the device510Android.prototype.setCompressedLayoutHierarchy = function (compress, cb) {511 this.proxy(["compressedLayoutHierarchy", {compressLayout: compress}], cb);512};513Android.prototype.waitForActivityToStop = function (cb) {514 this.adb.waitForNotActivity(this.args.appWaitPackage, this.args.appWaitActivity, cb);515};516Android.prototype.setActualCapabilities = function (cb) {517 this.capabilities.deviceName = this.adb.udid || this.adb.curDeviceId;518 this.adb.shell("getprop ro.build.version.release", function (err, version) {519 if (err) {520 logger.warn(err);521 } else {522 logger.debug("Device is at release version " + version);523 this.capabilities.platformVersion = version;524 }525 return cb();526 }.bind(this));527};528Android.prototype.resetTimeout = deviceCommon.resetTimeout;529Android.prototype.waitForCondition = deviceCommon.waitForCondition;530Android.prototype.implicitWaitForCondition = deviceCommon.implicitWaitForCondition;531Android.prototype.getSettings = deviceCommon.getSettings;532Android.prototype.updateSettings = deviceCommon.updateSettings;533_.extend(Android.prototype, androidController);534_.extend(Android.prototype, androidContextController);535_.extend(Android.prototype, androidCommon);536_.extend(Android.prototype, androidHybrid);...
android-hybrid.js
Source:android-hybrid.js
1"use strict";2var logger = require('../../server/logger.js').get('appium')3 , _ = require('underscore')4 , errors = require('../../server/errors.js')5 , UnknownError = errors.UnknownError6 , async = require('async')7 , Chromedriver = require('appium-chromedriver')8 , status = require("../../server/status.js");9var androidHybrid = {};10androidHybrid.chromedriver = null;11androidHybrid.sessionChromedrivers = {};12androidHybrid.listWebviews = function (cb) {13 logger.debug("Getting a list of available webviews");14 var webviews = [];15 var definedDeviceSocket = this.args.androidDeviceSocket;16 this.adb.shell("cat /proc/net/unix", function (err, out) {17 if (err) return cb(err);18 _.each(out.split("\n"), function (line) {19 line = line.trim();20 var webviewPid = line.match(/@?_devtools_remote/);21 if (definedDeviceSocket) {22 if (line.indexOf("@" + definedDeviceSocket) ===23 line.length - definedDeviceSocket.length - 1) {24 if (webviewPid) {25 webviews.push(this.WEBVIEW_BASE + webviewPid[1]);26 } else {27 webviews.push(this.CHROMIUM_WIN);28 }29 }30 } else if (webviewPid) {31 // for multiple webviews a list of 'WEBVIEW_<index>' will be returned32 // where <index> is zero based (same is in selendroid)33 //webviews.push(this.WEBVIEW_BASE + webviewPid[1]);34 webviews.push(this.WEBVIEW_BASE + this.args.appPackage + "_devtools_remote");35 }36 }.bind(this));37 webviews = _.uniq(webviews);38 if (definedDeviceSocket) {39 return cb(null, webviews);40 }41 var webviewsTmp = webviews;42 webviews = [];43 var getProcessNameFromWebview = function (view, cb) {44 webviews.push(this.WEBVIEW_BASE + this.args.appPackage);45 cb(null, webviews);46 }.bind(this);47 async.each(webviewsTmp, getProcessNameFromWebview, function (err) {48 if (err) return cb(err);49 logger.debug("Available contexts: " + this.contexts);50 cb(null, webviews);51 }.bind(this));52 }.bind(this));53};54var previousState = {};55// remember whether we were previously proxying to a chromedriver or not56androidHybrid.rememberProxyState = function () {57 previousState.isProxy = this.isProxy;58};59androidHybrid.restoreProxyState = function () {60 this.isProxy = previousState.isProxy;61};62androidHybrid.getProcessNameFromWebview = function (webview, cb) {63 // webview_devtools_remote_4296 => 429664 var pid = webview.match(/\d+$/);65 if (!pid) return cb("No pid for webview " + webview);66 pid = pid[0];67 logger.debug(webview + " mapped to pid " + pid);68 logger.debug("Getting process name for webview");69 this.adb.shell("ps", function (err, out) {70 if (err) return cb(err);71 var pkg = "unknown";72 var lines = out.split(/\r?\n/);73 /*74 USER PID PPID VSIZE RSS WCHAN PC NAME75 u0_a136 6248 179 946000 48144 ffffffff 4005903e R com.example.test76 */77 var header = lines[0].trim().split(/\s+/);78 // the column order may not be identical on all androids79 // dynamically locate the pid and name column.80 var pidColumn = header.indexOf("PID");81 var pkgColumn = header.indexOf("NAME") + 1;82 _.find(lines, function (line) {83 line = line.trim().split(/\s+/);84 if (line[pidColumn].indexOf(pid) !== -1) {85 logger.debug("Parsed pid: " + line[pidColumn] + " pkg: " + line[pkgColumn]);86 logger.debug("from: " + line);87 pkg = line[pkgColumn];88 return pkg; // exit from _.find89 }90 });91 logger.debug("returning process name: " + pkg);92 cb(null, pkg);93 });94};95androidHybrid.startChromedriverProxy = function (context, cb) {96 cb = _.once(cb);97 logger.debug("Connecting to chrome-backed webview");98 if (this.chromedriver !== null) {99 return cb(new Error("We already have a chromedriver instance running"));100 }101 if (this.sessionChromedrivers[context]) {102 // in the case where we've already set up a chromedriver for a context,103 // we want to reconnect to it, not create a whole new one104 this.setupExistingChromedriver(context, cb);105 } else {106 this.setupNewChromedriver(context, cb);107 }108};109androidHybrid.setupNewChromedriver = function (context, cb) {110 var chromeArgs = {111 port: this.args.chromeDriverPort,112 executable: this.args.chromedriverExecutable113 };114 this.chromedriver = new Chromedriver(chromeArgs);115 this.proxyReqRes = this.chromedriver.proxyReq.bind(this.chromedriver);116 this.rememberProxyState();117 this.isProxy = true;118 var caps = {119 chromeOptions: {120 androidPackage: this.args.appPackage,121 androidUseRunningApp: true122 }123 };124 if (this.args.enablePerformanceLogging) {125 caps.loggingPrefs = {performance: 'ALL'};126 }127 // For now the only known arg passed this way is androidDeviceSocket used128 // by Operadriver (deriving from Chromedriver) // We don't know how other129 // Chromium embedders will call this argument so for now it's name needs to130 // be configurable. When Google adds the androidDeviceSocket argument to131 // the original Chromedriver then we will be sure about its name for all132 // Chromium embedders (as their Webdrivers will derive from Chromedriver)133 if (this.args.specialChromedriverSessionArgs) {134 _.each(this.args.specialChromedriverSessionArgs, function (val, option) {135 logger.debug("This method is being deprecated. Apply chromeOptions " +136 "normally to pass along options,see sites.google.com/a/" +137 "chromium.org/chromedriver/capabilities for more info");138 caps.chromeOptions[option] = val;139 });140 }141 caps = this.decorateChromeOptions(caps);142 this.chromedriver.on(Chromedriver.EVENT_CHANGED, function (msg) {143 if (msg.state === Chromedriver.STATE_STOPPED) {144 // bind our stop/exit handler, passing in context so we know which145 // one stopped unexpectedly146 this.onChromedriverStop(context);147 }148 }.bind(this));149 this.chromedriver.start(caps).then(function () {150 // save the chromedriver object under the context151 this.sessionChromedrivers[context] = this.chromedriver;152 cb();153 }.bind(this), cb);154};155androidHybrid.setupExistingChromedriver = function (context, cb) {156 logger.debug("Found existing Chromedriver for context '" + context + "'." +157 " Using it.");158 this.rememberProxyState();159 this.chromedriver = this.sessionChromedrivers[context];160 this.proxyReqRes = this.chromedriver.proxyReq.bind(this.chromedriver);161 this.isProxy = true;162 // check the status by sending a simple window-based command to ChromeDriver163 // if there is an error, we want to recreate the ChromeDriver session164 this.chromedriver.hasWorkingWebview().then(function (works) {165 if (works) return cb();166 logger.debug("ChromeDriver is not associated with a window. " +167 "Re-initializing the session.");168 this.chromedriverRestartingContext = context;169 this.chromedriver.restart().then(function () {170 this.chromedriverRestartingContext = null;171 cb();172 }.bind(this), cb);173 }.bind(this), cb);174};175androidHybrid.onChromedriverStop = function (context) {176 logger.warn("Chromedriver for context " + context + " stopped unexpectedly");177 if (context === this.curContext) {178 // if we don't have a stop callback, we exited unexpectedly and so want179 // to shut down the session and respond with an error180 // TODO: this kind of thing should be emitted and handled by a higher-level181 // controlling function182 var error = new UnknownError("Chromedriver quit unexpectedly during session");183 logger.error(error.message);184 if (typeof this.cbForCurrentCmd === "function") {185 this.shutdown(function () {186 this.cbForCurrentCmd(error, null);187 }.bind(this));188 }189 } else if (context !== this.chromedriverRestartingContext) {190 // if a Chromedriver in the non-active context barfs, we don't really191 // care, we'll just make a new one next time we need the context.192 // The only time we ignore this is if we know we're in the middle of a193 // Chromedriver restart194 logger.warn("Chromedriver quit unexpectedly, but it wasn't the active " +195 "context, ignoring");196 delete this.sessionChromedrivers[context];197 }198};199androidHybrid.suspendChromedriverProxy = function (cb) {200 this.chromedriver = null;201 this.restoreProxyState();202 cb();203};204androidHybrid.stopChromedriverProxies = function (ocb) {205 async.eachSeries(Object.keys(this.sessionChromedrivers), function (context, cb) {206 logger.debug("Stopping chromedriver for context " + context);207 // stop listening for the stopped state event208 this.sessionChromedrivers[context].removeAllListeners(Chromedriver.EVENT_CHANGED);209 var onStop = function (err) {210 if (err) logger.warn("Error stopping Chromedriver: " + err.message);211 // chromedriver isn't valid anymore, so remove it from context list212 delete this.sessionChromedrivers[context];213 cb();214 }.bind(this);215 this.sessionChromedrivers[context].stop().then(function () {216 onStop();217 }, onStop);218 }.bind(this), function (err) {219 // if one of these fails, go back to last proxy state and error out220 this.restoreProxyState();221 ocb(err);222 }.bind(this));223};224androidHybrid.defaultWebviewName = function () {225 return this.WEBVIEW_BASE + this.appProcess;226};227androidHybrid.initAutoWebview = function (cb) {228 if (this.args.autoWebview) {229 logger.debug('Setting auto webview');230 var viewName = this.defaultWebviewName();231 var timeout = (this.args.autoWebviewTimeout) || 2000;232 this.setContext(viewName, function (err, res) {233 if (err && res.status !== status.codes.NoSuchContext.code) return cb(err);234 if (res.status === status.codes.Success.code) return cb();235 setTimeout(function () {236 logger.debug("Retrying context switch with timeout '" + timeout + "'");237 this.setContext(viewName, cb);238 }.bind(this), timeout);239 }.bind(this));240 } else {241 cb();242 }243};...
Using AI Code Generation
1var webdriverio = require('webdriverio');2var options = {3 desiredCapabilities: {4 }5};6var driver = webdriverio.remote(options);7 .init()8 .initAutoWebview()9 .getTitle().then(function(title) {10 console.log('Title was: ' + title);11 })12 .end();13import io.appium.java_client.AppiumDriver;14import io.appium.java_client.android.AndroidDriver;15import org.junit.After;16import org.junit.Before;17import org.junit.Test;18import org.openqa.selenium.remote.DesiredCapabilities;19import java.net.URL;20public class InitAutoWebviewTest {21 private AppiumDriver driver;22 public void setUp() throws Exception {23 DesiredCapabilities capabilities = new DesiredCapabilities();24 capabilities.setCapability("browserName", "chrome");25 capabilities.setCapability("platformName", "Android");26 capabilities.setCapability("platformVersion", "4.4");27 capabilities.setCapability("deviceName", "Android Emulator");
Using AI Code Generation
1driver.initAutoWebview();2driver.autoWebview();3driver.autoWebviewTimeout();4driver.autoWebviewTimeout(10000);5driver.autoWebviewTimeout("10000");6driver.autoWebviewTimeout(10000, function(err, res) {7 if(err) {8 console.log(err);9 } else {10 console.log(res);11 }12});13driver.autoWebviewTimeout("10000", function(err, res) {14 if(err) {15 console.log(err);16 } else {17 console.log(res);18 }19});20driver.autoWebviewTimeout(function(err, res) {21 if(err) {22 console.log(err);23 } else {24 console.log(res);25 }26});27driver.autoWebviewTimeout("10000", function(err, res) {28 if(err) {29 console.log(err);30 } else {31 console.log(res);32 }33});34driver.autoWebviewTimeout("10000", function(err, res) {35 if(err) {36 console.log(err);37 } else {38 console.log(res);39 }40});41driver.autoWebviewTimeout(10000, function(err, res) {42 if(err) {43 console.log(err);44 } else {45 console.log(res);46 }47});48driver.autoWebviewTimeout("10000", function(err, res) {49 if(err) {50 console.log(err);51 } else {52 console.log(res);53 }54});55driver.autoWebviewTimeout(function(err, res) {56 if(err) {57 console.log(err);58 } else {59 console.log(res
Using AI Code Generation
1var webdriver = require('selenium-webdriver');2var By = webdriver.By;3var until = webdriver.until;4var driver = new webdriver.Builder()5 .forBrowser('chrome')6 .build();7driver.findElement(By.name('q')).sendKeys('webdriver');8driver.findElement(By.name('btnG')).click();9driver.wait(until.titleIs('webdriver - Google Search'), 1000);10driver.quit();11Then we are using the quit() method
Using AI Code Generation
1var webdriver = require('selenium-webdriver');2var AppiumDriver = require('appium-android-driver');3var driver = new AppiumDriver();4var desiredCaps = {5};6 .init(desiredCaps)7 .then(function () {8 return driver.initAutoWebview();9 })10 .then(function () {11 return driver.title();12 })13 .then(function (title) {14 console.log('Title is: ' + title);15 })16 .then(function () {17 return driver.quit();18 })19 .done();20from appium import webdriver21desired_caps = {22}23driver.init_auto_webview()24print driver.title()25driver.quit()26var webdriver = require('selenium-webdriver');27var AppiumDriver = require('appium-android-driver');28var driver = new AppiumDriver();29var desiredCaps = {30};31 .init(desiredCaps)32 .then(function () {33 return driver.initAutoWebview();34 })35 .then(function () {36 return driver.title();37 })38 .then(function (title) {39 console.log('Title is: ' + title);40 })41 .then(function () {42 return driver.quit();43 })44 .done();
Using AI Code Generation
1await driver.initAutoWebview();2await driver.context('WEBVIEW_com.android.chrome');3await driver.$('#someElement').click();4await driver.context('NATIVE_APP');5await driver.$('#someNativeAppElement').click();6await driver.context('WEBVIEW_com.android.chrome');7await driver.$('#someElement').click();8await driver.context('NATIVE_APP');9await driver.$('#someNativeAppElement').click();10await driver.context('WEBVIEW_com.android.chrome');11await driver.$('#someElement').click();12await driver.context('NATIVE_APP');13await driver.$('#someNativeAppElement').click();14await driver.context('WEBVIEW_com.android.chrome');15await driver.$('#someElement').click();16await driver.context('NATIVE_APP');17await driver.$('#someNativeAppElement').click();18await driver.context('WEBVIEW_com.android.chrome');19await driver.$('#someElement').click();20await driver.context('NATIVE_APP');21await driver.$('#someNativeAppElement').click();22await driver.context('WEBVIEW_com.android.chrome');23await driver.$('#someElement').click();24await driver.context('NATIVE_APP');25await driver.$('#someNativeAppElement').click();26await driver.context('WEBVIEW_com.android.chrome');27await driver.$('#someElement').click();28await driver.context('NATIVE_APP');29await driver.$('#someNativeAppElement').click();30await driver.context('WEBVIEW_com.android.chrome');
Using AI Code Generation
1var AndroidDriver = require('appium-android-driver');2var wd = require('wd');3var chai = require('chai');4var chaiAsPromised = require('chai-as-promised');5var should = chai.should();6var expect = chai.expect;7var assert = chai.assert;8chai.use(chaiAsPromised);9var driver = new AndroidDriver();10var wdDriver = wd.promiseChainRemote();11var driver = new AndroidDriver();12var wdDriver = wd.promiseChainRemote();13var desiredCaps = {
Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.
You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.
Get 100 minutes of automation test minutes FREE!!