How to use initAutoWebview method in Appium Android Driver

Best JavaScript code snippet using appium-android-driver

ios.js

Source:ios.js Github

copy

Full Screen

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);...

Full Screen

Full Screen

context.js

Source:context.js Github

copy

Full Screen

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 };...

Full Screen

Full Screen

selendroid.js

Source:selendroid.js Github

copy

Full Screen

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};...

Full Screen

Full Screen

driver.js

Source:driver.js Github

copy

Full Screen

...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,...

Full Screen

Full Screen

android.js

Source:android.js Github

copy

Full Screen

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);...

Full Screen

Full Screen

android-hybrid.js

Source:android-hybrid.js Github

copy

Full Screen

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};...

Full Screen

Full Screen

Using AI Code Generation

copy

Full Screen

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");

Full Screen

Using AI Code Generation

copy

Full Screen

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

Full Screen

Using AI Code Generation

copy

Full Screen

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

Full Screen

Using AI Code Generation

copy

Full Screen

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();

Full Screen

Using AI Code Generation

copy

Full Screen

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');

Full Screen

Using AI Code Generation

copy

Full Screen

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 = {

Full Screen

Automation Testing Tutorials

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.

LambdaTest Learning Hubs:

YouTube

You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.

Run Appium Android Driver automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful