Best JavaScript code snippet using appium-xcuitest-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);...
driver.js
Source:driver.js
...374 }375 // check for a particular real device376 if (this.opts.udid) {377 if (this.opts.udid.toLowerCase() === 'auto') {378 this.opts.udid = await detectUdid();379 } else {380 // make sure it is a connected device. If not, the udid passed in is invalid381 let devices = await getConnectedDevices();382 log.debug(`Available devices: ${devices.join(', ')}`);383 if (devices.indexOf(this.opts.udid) === -1) {384 throw new Error(`Unknown device or simulator UDID: '${this.opts.udid}'`);385 }386 }387 let device = await this.getIDeviceObj();388 return {device, realDevice: true, udid: this.opts.udid};389 }390 // figure out the correct simulator to use, given the desired capabilities391 let device = await getExistingSim(this.opts.deviceName, this.opts.platformVersion);392 // check for an existing simulator...
capability-specs.js
Source:capability-specs.js
1import { IosDriver } from '../..';2import * as utils from '../../lib/utils';3import chai from 'chai';4import sinon from 'sinon';5import { withMocks } from 'appium-test-support';6import xcode from 'appium-xcode';7let sandbox = sinon.createSandbox();8const should = chai.Should();9describe('Desired Capabilities', function () {10 let driver;11 before(function () {12 driver = new IosDriver();13 });14 function checkCaps (caps, throws = false) {15 if (throws) {16 should.Throw(function () {17 driver.validateDesiredCaps(caps);18 });19 } else {20 should.not.Throw(function () {21 driver.validateDesiredCaps(caps);22 });23 }24 }25 describe('platform name, app and bundleId', function () {26 it('should throw error if neither app nor bundleId are present', function () {27 let caps = {28 platformName: 'iOS',29 deviceName: 'iPhone 5'30 };31 checkCaps(caps, true);32 });33 it('should accept an app', function () {34 let caps = {35 platformName: 'iOS',36 deviceName: 'iPhone 5',37 app: 'some-app'38 };39 checkCaps(caps);40 });41 it('should accept a bundleId', function () {42 let caps = {43 platformName: 'iOS',44 deviceName: 'iPhone 5',45 bundleId: 'com.some-company.some-app'46 };47 checkCaps(caps);48 });49 it('should not be sensitive to platform name casing', function () {50 let caps = {51 platformName: 'IoS',52 deviceName: 'iPhone 5',53 bundleId: 'com.some-company.some-app'54 };55 checkCaps(caps);56 });57 });58 describe('launchTimeout', function () {59 it('should accept a number', function () {60 let caps = {61 platformName: 'iOS',62 deviceName: 'iPhone 5',63 app: 'some-app',64 launchTimeout: 165 };66 checkCaps(caps);67 });68 it('should accept an object', function () {69 let caps = {70 platformName: 'iOS',71 deviceName: 'iPhone 5',72 app: 'some-app',73 launchTimeout: {74 global: 175 }76 };77 checkCaps(caps);78 });79 it('should accept a stringified object', function () {80 let caps = {81 platformName: 'iOS',82 deviceName: 'iPhone 5',83 app: 'some-app',84 launchTimeout: JSON.stringify({85 global: 186 })87 };88 checkCaps(caps);89 });90 it('should fail for non-JSON string', function () {91 let caps = {92 platformName: 'iOS',93 deviceName: 'iPhone 5',94 app: 'some-app',95 launchTimeout: 'launch timeout!'96 };97 checkCaps(caps, true);98 });99 });100 describe('processArguments', function () {101 it('should accept plain string', function () {102 let caps = {103 platformName: 'iOS',104 deviceName: 'iPhone 5',105 app: 'some-app',106 processArguments: 'this is a process argument'107 };108 checkCaps(caps);109 });110 it('should accept an object', function () {111 let caps = {112 platformName: 'iOS',113 deviceName: 'iPhone 5',114 app: 'some-app',115 processArguments: {116 global: 1117 }118 };119 checkCaps(caps);120 });121 it('should accept a stringified object', function () {122 let caps = {123 platformName: 'iOS',124 deviceName: 'iPhone 5',125 app: 'some-app',126 processArguments: JSON.stringify({127 global: 1128 })129 };130 checkCaps(caps);131 });132 it('should fail for non-string, non-object', function () {133 let caps = {134 platformName: 'iOS',135 deviceName: 'iPhone 5',136 app: 'some-app',137 processArguments: 4138 };139 checkCaps(caps, true);140 });141 });142 describe('server capabilities', withMocks({xcode}, (mocks) => {143 it('should collect server capabilities', async function () {144 mocks.xcode.expects('getVersion')145 .once().returns({146 versionString: '7.0.0',147 versionFloat: 7.0,148 major: 7,149 minor: 0,150 patch: 0151 });152 sandbox.stub(driver, 'configureApp');153 sandbox.stub(driver, 'validateDesiredCaps');154 sandbox.stub(driver, 'start');155 sandbox.stub(driver, 'startNewCommandTimeout');156 sandbox.stub(utils, 'detectUdid');157 sandbox.stub(utils, 'prepareIosOpts');158 let caps = {159 platformName: 'iOS',160 deviceName: 'iPhone 5',161 app: 'some-app'162 };163 await driver.createSession(caps);164 driver.caps.takesScreenshot.should.exist;165 sandbox.restore();166 });167 }));...
utils.js
Source:utils.js
1import { fs, plist } from 'appium-support';2import logger from './logger';3import path from 'path';4import _ from 'lodash';5import { exec } from 'teen_process';6const rootDir = path.resolve(__dirname, '..', '..');7function appIsPackageOrBundle (app) {8 return (/^([a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]+)+$/).test(app);9}10async function removeInstrumentsSocket (sock) {11 logger.debug("Removing any remaining instruments sockets");12 await fs.rimraf(sock);13 logger.debug("Cleaned up instruments socket " + sock);14}15function getSimForDeviceString (dString, availDevices) {16 let matchedDevice = null;17 let matchedUdid = null;18 _.each(availDevices, function (device) {19 if (device.indexOf(dString) !== -1) {20 matchedDevice = device;21 try {22 matchedUdid = /.+\[([^\]]+)\]/.exec(device)[1];23 } catch (e) {24 matchedUdid = null;25 }26 }27 });28 return [matchedDevice, matchedUdid];29}30async function detectUdid (caps) {31 if (caps.udid !== null && caps.udid === "auto") {32 logger.debug("Auto-detecting iOS udid...");33 let cmd, args = [];34 try {35 cmd = await fs.which('idevice_id');36 args.push('-l');37 } catch (err) {38 cmd = require.resolve('udidetect');39 }40 let udid;41 try {42 let {stdout} = await exec(cmd, args, {timeout: 3000});43 udid = stdout.split("\n")[0];44 } catch (err) {45 logger.error("Error detecting udid");46 throw err;47 }48 if (udid && udid.length > 2) {49 caps.udid = udid;50 logger.debug("Detected udid as " + caps.udid);51 } else {52 throw new Error("Could not detect udid.");53 }54 } else {55 logger.debug("Not auto-detecting udid.");56 }57}58async function parseLocalizableStrings (opts) {59 if (_.isNull(opts.app) || _.isUndefined(opts.app)) {60 logger.debug("Localizable.strings is not currently supported when using real devices.");61 return;62 }63 let language = opts.language;64 let stringFile = "Localizable.strings";65 let strings = null;66 if (language) {67 strings = path.resolve(opts.app, language + ".lproj", stringFile);68 }69 if (!await fs.exists(strings)) {70 if (language) {71 logger.debug("No strings file '" + stringFile + "' for language '" + language + "', getting default strings");72 }73 strings = path.resolve(opts.app, stringFile);74 }75 if (!await fs.exists(strings)) {76 strings = path.resolve(opts.app, opts.localizableStringsDir, stringFile);77 }78 if (!await fs.exists(strings)) {79 logger.warn('Could not file localizable strings file: Localizable.strings!');80 return;81 }82 let obj;83 try {84 obj = await plist.parsePlistFile(strings);85 logger.debug("Parsed app " + stringFile);86 opts.localizableStrings = obj;87 } catch (err) {88 logger.warn("Could not parse app " + stringFile +" assuming it " +89 "doesn't exist");90 }91}92function shouldPrelaunchSimulator (caps, iosSdkVersion) {93 let shouldPrelaunch = false;94 if (caps.defaultDevice || iosSdkVersion >= 7.1) {95 if (this.iosSdkVersion >= 7.1) {96 logger.debug("We're on iOS7.1+ so forcing defaultDevice on");97 } else {98 logger.debug("User specified default device, letting instruments launch it");99 }100 } else {101 shouldPrelaunch = true;102 }103 return shouldPrelaunch;104}105async function setDeviceTypeInInfoPlist (app, deviceString) {106 if (_.isNull(app) || _.isUndefined(app)) { return; }107 let plistFile = path.resolve(app, "Info.plist");108 let isiPhone = deviceString.toLowerCase().indexOf("ipad") === -1;109 let deviceTypeCode = isiPhone ? 1 : 2;110 await plist.updatePlistFile(plistFile, {UIDeviceFamily: [deviceTypeCode]});111}112function unwrapEl (el) {113 if(typeof el === 'object' && el.ELEMENT){114 return el.ELEMENT;115 }116 return el;117}118export default { rootDir, removeInstrumentsSocket,119 appIsPackageOrBundle, detectUdid, parseLocalizableStrings,120 shouldPrelaunchSimulator, setDeviceTypeInInfoPlist, getSimForDeviceString,...
Using AI Code Generation
1const { detectUdid } = require('appium-xcuitest-driver');2const udid = await detectUdid();3console.log(udid);4const { detectUdid } = require('appium-xcuitest-driver');5const udid = await detectUdid();6console.log(udid);
Using AI Code Generation
1const { detectUdid } = require('appium-xcuitest-driver');2const udid = await detectUdid();3console.log(udid);4const { detectUdid } = require('appium-xcuitest-driver');5const udid = await detectUdid();6console.log(udid);7const { detectUdid } = require('appium-xcuitest-driver');8const udid = await detectUdid();9console.log(udid);10const { detectUdid } = require('appium-xcuitest-driver');11const udid = await detectUdid();12console.log(udid);13const { detectUdid } = require('appium-xcuitest-driver');14const udid = await detectUdid();15console.log(udid);16const { detectUdid } = require('appium-xcuitest-driver');17const udid = await detectUdid();18console.log(udid);19const { detectUdid } = require('appium-xcuitest-driver');20const udid = await detectUdid();21console.log(udid);22const { detectUdid } = require('appium-xcuitest-driver');23const udid = await detectUdid();24console.log(udid);25const { detectUdid } = require('appium-xcuitest-driver');26const udid = await detectUdid();27console.log(udid);28const { detect
Using AI Code Generation
1const { detectUdid } = require('appium-xcuitest-driver');2const udid = await detectUdid();3console.log(udid);4const { detectUdid } = require('appium-xcuitest-driver');5const udid = await detectUdid();6console.log(udid);
Using AI Code Generation
1const { detectUdid } = require('appium-xcuitest-driver');2const { fs } = require('appium-support');3async function main () {4 const udid = await detectUdid();5 console.log(`Detected udid: ${udid}`);6 const bundleId = 'com.apple.Preferences';7 const path = await fs.which('ideviceinstaller');8 const args = ['-i', bundleId];9 const {stdout} = await exec(path, args);10 console.log(`ideviceinstaller returned: ${stdout}`);11}12main();13{14 "scripts": {15 },16 "dependencies": {17 }18}19{20 "dependencies": {21 "appium-support": {22 "requires": {23 }24 },25 "appium-xcuitest-driver": {
Using AI Code Generation
1const { detectUdid } = require('appium-xcuitest-driver');2const udid = await detectUdid();3console.log(udid)4async function detectUdid () {5 const { stdout } = await exec('idevice_id -l');6 const udid = stdout.trim();7 if (!udid) {8 throw new Error('No device found. Please connect your device and try again.');9 }10 return udid;11}12appium --default-capabilities '{"platformName":"iOS"}'13appium --default-capabilities '{"platformName":"iOS"}'14appium --default-capabilities '{"
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!!