appium-ios-simulator
Version:
iOS Simulator interface for Appium.
1,448 lines (1,179 loc) • 158 kB
JavaScript
'use strict';
var _get = require('babel-runtime/helpers/get')['default'];
var _inherits = require('babel-runtime/helpers/inherits')['default'];
var _createClass = require('babel-runtime/helpers/create-class')['default'];
var _classCallCheck = require('babel-runtime/helpers/class-call-check')['default'];
var _slicedToArray = require('babel-runtime/helpers/sliced-to-array')['default'];
var _regeneratorRuntime = require('babel-runtime/regenerator')['default'];
var _getIterator = require('babel-runtime/core-js/get-iterator')['default'];
var _Object$assign = require('babel-runtime/core-js/object/assign')['default'];
var _interopRequireDefault = require('babel-runtime/helpers/interop-require-default')['default'];
var _interopRequireWildcard = require('babel-runtime/helpers/interop-require-wildcard')['default'];
Object.defineProperty(exports, '__esModule', {
value: true
});
var _path = require('path');
var _path2 = _interopRequireDefault(_path);
var _nodeSimctl = require('node-simctl');
var simctl = _interopRequireWildcard(_nodeSimctl);
var _appiumXcode = require('appium-xcode');
var _appiumXcode2 = _interopRequireDefault(_appiumXcode);
var _logger = require('./logger');
var _logger2 = _interopRequireDefault(_logger);
var _appiumSupport = require('appium-support');
var _bluebird = require('bluebird');
var _bluebird2 = _interopRequireDefault(_bluebird);
var _lodash = require('lodash');
var _lodash2 = _interopRequireDefault(_lodash);
var _asyncLock = require('async-lock');
var _asyncLock2 = _interopRequireDefault(_asyncLock);
var _utilsJs = require('./utils.js');
var _asyncbox = require('asyncbox');
var _settings = require('./settings');
var settings = _interopRequireWildcard(_settings);
var _teen_process = require('teen_process');
var _tailUntilJs = require('./tail-until.js');
var _extensionsIndex = require('./extensions/index');
var _extensionsIndex2 = _interopRequireDefault(_extensionsIndex);
var _events = require('events');
var _events2 = _interopRequireDefault(_events);
var _calendar = require('./calendar');
var _calendar2 = _interopRequireDefault(_calendar);
var EventEmitter = _events2['default'].EventEmitter;
var STARTUP_TIMEOUT = 60 * 1000;
var EXTRA_STARTUP_TIME = 2000;
var UI_CLIENT_ACCESS_GUARD = new _asyncLock2['default']();
/*
* This event is emitted as soon as iOS Simulator
* has finished booting and it is ready to accept xcrun commands.
* The event handler is called after 'run' method is completed
* for Xcode 7 and older and is only useful in Xcode 8+,
* since one can start doing stuff (for example install/uninstall an app) in parallel
* with Simulator UI startup, which shortens session startup time.
*/
var BOOT_COMPLETED_EVENT = 'bootCompleted';
var SimulatorXcode6 = (function (_EventEmitter) {
_inherits(SimulatorXcode6, _EventEmitter);
/**
* Constructs the object with the `udid` and version of Xcode. Use the exported `getSimulator(udid)` method instead.
*
* @param {string} udid - The Simulator ID.
* @param {object} xcodeVersion - The target Xcode version in format {major, minor, build}.
*/
function SimulatorXcode6(udid, xcodeVersion) {
_classCallCheck(this, SimulatorXcode6);
_get(Object.getPrototypeOf(SimulatorXcode6.prototype), 'constructor', this).call(this);
this.udid = String(udid);
this.xcodeVersion = xcodeVersion;
// platformVersion cannot be found initially, since getting it has side effects for
// our logic for figuring out if a sim has been run
// it will be set when it is needed
this._platformVersion = null;
this.keychainPath = _path2['default'].resolve(this.getDir(), 'Library', 'Keychains');
this.simulatorApp = 'iOS Simulator.app';
this.appDataBundlePaths = {};
// list of files to check for when seeing if a simulator is "fresh"
// (meaning it has never been booted).
// If these files are present, we assume it's been successfully booted
this.isFreshFiles = ['Library/ConfigurationProfiles', 'Library/Cookies', 'Library/Preferences/.GlobalPreferences.plist', 'Library/Preferences/com.apple.springboard.plist', 'var/run/syslog.pid'];
// extra time to wait for simulator to be deemed booted
this.extraStartupTime = EXTRA_STARTUP_TIME;
this.calendar = new _calendar2['default'](this.getDir());
}
/**
* Check the state of Simulator UI client.
*
* @return {boolean} True of if UI client is running or false otherwise.
*/
_createClass(SimulatorXcode6, [{
key: 'isUIClientRunning',
value: function isUIClientRunning() {
return _regeneratorRuntime.async(function isUIClientRunning$(context$2$0) {
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
context$2$0.prev = 0;
context$2$0.next = 3;
return _regeneratorRuntime.awrap((0, _teen_process.exec)('pgrep', ['-x', this.simulatorApp.split('.')[0]]));
case 3:
return context$2$0.abrupt('return', true);
case 6:
context$2$0.prev = 6;
context$2$0.t0 = context$2$0['catch'](0);
return context$2$0.abrupt('return', false);
case 9:
case 'end':
return context$2$0.stop();
}
}, null, this, [[0, 6]]);
}
/**
* How long to wait before throwing an error about Simulator startup timeout happened.
*
* @return {number} The number of milliseconds.
*/
}, {
key: 'getPlatformVersion',
/**
* Get the platform version of the current Simulator.
*
* @return {string} SDK version, for example '8.3'.
*/
value: function getPlatformVersion() {
var _ref, sdk;
return _regeneratorRuntime.async(function getPlatformVersion$(context$2$0) {
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
if (this._platformVersion) {
context$2$0.next = 6;
break;
}
context$2$0.next = 3;
return _regeneratorRuntime.awrap(this.stat());
case 3:
_ref = context$2$0.sent;
sdk = _ref.sdk;
this._platformVersion = sdk;
case 6:
return context$2$0.abrupt('return', this._platformVersion);
case 7:
case 'end':
return context$2$0.stop();
}
}, null, this);
}
/**
* Retrieve the full path to the directory where Simulator stuff is located.
*
* @return {string} The path string.
*/
}, {
key: 'getRootDir',
value: function getRootDir() {
var home = process.env.HOME;
return _path2['default'].resolve(home, 'Library', 'Developer', 'CoreSimulator', 'Devices');
}
/**
* Retrieve the full path to the directory where Simulator applications data is located.
*
* @return {string} The path string.
*/
}, {
key: 'getDir',
value: function getDir() {
return _path2['default'].resolve(this.getRootDir(), this.udid, 'data');
}
/**
* Retrieve the full path to the directory where Simulator logs are stored.
*
* @return {string} The path string.
*/
}, {
key: 'getLogDir',
value: function getLogDir() {
var home = process.env.HOME;
return _path2['default'].resolve(home, 'Library', 'Logs', 'CoreSimulator', this.udid);
}
/**
* Install valid .app package on Simulator.
*
* @param {string} app - The path to the .app package.
*/
}, {
key: 'installApp',
value: function installApp(app) {
return _regeneratorRuntime.async(function installApp$(context$2$0) {
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
context$2$0.next = 2;
return _regeneratorRuntime.awrap(simctl.installApp(this.udid, app));
case 2:
return context$2$0.abrupt('return', context$2$0.sent);
case 3:
case 'end':
return context$2$0.stop();
}
}, null, this);
}
/**
* Verify whether the particular application is installed on Simulator.
*
* @param {string} bundleId - The bundle id of the application to be checked.
* @param {string} appFule - Application name minus ".app" (for iOS 7.1)
* @return {boolean} True if the given application is installed
*/
}, {
key: 'isAppInstalled',
value: function isAppInstalled(bundleId) {
var appFile = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
var appDirs;
return _regeneratorRuntime.async(function isAppInstalled$(context$2$0) {
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
context$2$0.next = 2;
return _regeneratorRuntime.awrap(this.getAppDirs(appFile, bundleId));
case 2:
appDirs = context$2$0.sent;
return context$2$0.abrupt('return', appDirs.length !== 0);
case 4:
case 'end':
return context$2$0.stop();
}
}, null, this);
}
/**
* Retrieve the directory for a particular application's data.
*
* @param {string} id - Either a bundleId (e.g., com.apple.mobilesafari) or, for iOS 7.1, the app name without `.app` (e.g., MobileSafari)
* @param {string} subdir - The sub-directory we expect to be within the application directory. Defaults to "Data".
* @return {string} The root application folder.
*/
}, {
key: 'getAppDir',
value: function getAppDir(id) {
var subDir = arguments.length <= 1 || arguments[1] === undefined ? 'Data' : arguments[1];
return _regeneratorRuntime.async(function getAppDir$(context$2$0) {
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
this.appDataBundlePaths[subDir] = this.appDataBundlePaths[subDir] || {};
context$2$0.t0 = _lodash2['default'].isEmpty(this.appDataBundlePaths[subDir]);
if (!context$2$0.t0) {
context$2$0.next = 6;
break;
}
context$2$0.next = 5;
return _regeneratorRuntime.awrap(this.isFresh());
case 5:
context$2$0.t0 = !context$2$0.sent;
case 6:
if (!context$2$0.t0) {
context$2$0.next = 10;
break;
}
context$2$0.next = 9;
return _regeneratorRuntime.awrap(this.buildBundlePathMap(subDir));
case 9:
this.appDataBundlePaths[subDir] = context$2$0.sent;
case 10:
return context$2$0.abrupt('return', this.appDataBundlePaths[subDir][id]);
case 11:
case 'end':
return context$2$0.stop();
}
}, null, this);
}
/**
* The xcode 6 simulators are really annoying, and bury the main app
* directories inside directories just named with Hashes.
* This function finds the proper directory by traversing all of them
* and reading a metadata plist (Mobile Container Manager) to get the
* bundle id.
*
* @param {string} subdir - The sub-directory we expect to be within the application directory. Defaults to "Data".
* @return {object} The list of path-bundle pairs to an object where bundleIds are mapped to paths.
*/
}, {
key: 'buildBundlePathMap',
value: function buildBundlePathMap() {
var subDir = arguments.length <= 0 || arguments[0] === undefined ? 'Data' : arguments[0];
var applicationList, pathBundlePair, bundlePathDirs, bundlePathPairs;
return _regeneratorRuntime.async(function buildBundlePathMap$(context$2$0) {
var _this = this;
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
_logger2['default'].debug('Building bundle path map');
applicationList = undefined;
pathBundlePair = undefined;
context$2$0.next = 5;
return _regeneratorRuntime.awrap(this.getPlatformVersion());
case 5:
context$2$0.t0 = context$2$0.sent;
if (!(context$2$0.t0 === '7.1')) {
context$2$0.next = 11;
break;
}
// apps available
// Web.app,
// WebViewService.app,
// MobileSafari.app,
// WebContentAnalysisUI.app,
// DDActionsService.app,
// StoreKitUIService.app
applicationList = _path2['default'].resolve(this.getDir(), 'Applications');
pathBundlePair = function callee$2$0(dir) {
var appFiles, bundleId;
return _regeneratorRuntime.async(function callee$2$0$(context$3$0) {
while (1) switch (context$3$0.prev = context$3$0.next) {
case 0:
dir = _path2['default'].resolve(applicationList, dir);
context$3$0.next = 3;
return _regeneratorRuntime.awrap(_appiumSupport.fs.glob(dir + '/*.app'));
case 3:
appFiles = context$3$0.sent;
bundleId = appFiles[0].match(/.*\/(.*)\.app/)[1];
return context$3$0.abrupt('return', { path: dir, bundleId: bundleId });
case 6:
case 'end':
return context$3$0.stop();
}
}, null, _this);
};
context$2$0.next = 12;
break;
case 11:
(function () {
applicationList = _path2['default'].resolve(_this.getDir(), 'Containers', subDir, 'Application');
// given a directory, find the plist file and pull the bundle id from it
var readBundleId = function readBundleId(dir) {
var plist, metadata;
return _regeneratorRuntime.async(function readBundleId$(context$4$0) {
while (1) switch (context$4$0.prev = context$4$0.next) {
case 0:
plist = _path2['default'].resolve(dir, '.com.apple.mobile_container_manager.metadata.plist');
context$4$0.next = 3;
return _regeneratorRuntime.awrap(settings.read(plist));
case 3:
metadata = context$4$0.sent;
return context$4$0.abrupt('return', metadata.MCMMetadataIdentifier);
case 5:
case 'end':
return context$4$0.stop();
}
}, null, _this);
};
// given a directory, return the path and bundle id associated with it
pathBundlePair = function callee$3$0(dir) {
var bundleId;
return _regeneratorRuntime.async(function callee$3$0$(context$4$0) {
while (1) switch (context$4$0.prev = context$4$0.next) {
case 0:
dir = _path2['default'].resolve(applicationList, dir);
context$4$0.next = 3;
return _regeneratorRuntime.awrap(readBundleId(dir));
case 3:
bundleId = context$4$0.sent;
return context$4$0.abrupt('return', { path: dir, bundleId: bundleId });
case 5:
case 'end':
return context$4$0.stop();
}
}, null, _this);
};
})();
case 12:
context$2$0.next = 14;
return _regeneratorRuntime.awrap(_appiumSupport.fs.readdir(applicationList));
case 14:
bundlePathDirs = context$2$0.sent;
context$2$0.next = 17;
return _regeneratorRuntime.awrap((0, _asyncbox.asyncmap)(bundlePathDirs, function callee$2$0(dir) {
return _regeneratorRuntime.async(function callee$2$0$(context$3$0) {
while (1) switch (context$3$0.prev = context$3$0.next) {
case 0:
context$3$0.next = 2;
return _regeneratorRuntime.awrap(pathBundlePair(dir));
case 2:
return context$3$0.abrupt('return', context$3$0.sent);
case 3:
case 'end':
return context$3$0.stop();
}
}, null, _this);
}, false));
case 17:
bundlePathPairs = context$2$0.sent;
return context$2$0.abrupt('return', bundlePathPairs.reduce(function (bundleMap, bundlePath) {
bundleMap[bundlePath.bundleId] = bundlePath.path;
return bundleMap;
}, {}));
case 19:
case 'end':
return context$2$0.stop();
}
}, null, this);
}
/**
* Get the state and specifics of this sim.
*
* @return {object} Simulator stats mapping, for example:
* { name: 'iPhone 4s',
* udid: 'C09B34E5-7DCB-442E-B79C-AB6BC0357417',
* state: 'Shutdown',
* sdk: '8.3'
* }
*/
}, {
key: 'stat',
value: function stat() {
var devices, normalizedDevices, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _loop, _iterator, _step;
return _regeneratorRuntime.async(function stat$(context$2$0) {
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
context$2$0.next = 2;
return _regeneratorRuntime.awrap(simctl.getDevices());
case 2:
devices = context$2$0.sent;
normalizedDevices = [];
_iteratorNormalCompletion = true;
_didIteratorError = false;
_iteratorError = undefined;
context$2$0.prev = 7;
_loop = function () {
var _step$value = _slicedToArray(_step.value, 2);
var sdk = _step$value[0];
var deviceArr = _step$value[1];
deviceArr = deviceArr.map(function (device) {
device.sdk = sdk;
return device;
});
normalizedDevices = normalizedDevices.concat(deviceArr);
};
// add sdk attribute to all entries, add to normalizedDevices
for (_iterator = _getIterator(_lodash2['default'].toPairs(devices)); !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
_loop();
}
context$2$0.next = 16;
break;
case 12:
context$2$0.prev = 12;
context$2$0.t0 = context$2$0['catch'](7);
_didIteratorError = true;
_iteratorError = context$2$0.t0;
case 16:
context$2$0.prev = 16;
context$2$0.prev = 17;
if (!_iteratorNormalCompletion && _iterator['return']) {
_iterator['return']();
}
case 19:
context$2$0.prev = 19;
if (!_didIteratorError) {
context$2$0.next = 22;
break;
}
throw _iteratorError;
case 22:
return context$2$0.finish(19);
case 23:
return context$2$0.finish(16);
case 24:
return context$2$0.abrupt('return', _lodash2['default'].find(normalizedDevices, { udid: this.udid }));
case 25:
case 'end':
return context$2$0.stop();
}
}, null, this, [[7, 12, 16, 24], [17,, 19, 23]]);
}
/**
* This is a best-bet heuristic for whether or not a sim has been booted
* before. We usually want to start a simulator to "warm" it up, have
* Xcode populate it with plists for us to manipulate before a real
* test run.
*
* @return {boolean} True if the current Simulator has never been started before
*/
}, {
key: 'isFresh',
value: function isFresh() {
var files, pv, existences, fresh;
return _regeneratorRuntime.async(function isFresh$(context$2$0) {
var _this2 = this;
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
// if the following files don't exist, it hasn't been booted.
// THIS IS NOT AN EXHAUSTIVE LIST
_logger2['default'].debug('Checking whether simulator has been run before');
files = this.isFreshFiles;
context$2$0.next = 4;
return _regeneratorRuntime.awrap(this.getPlatformVersion());
case 4:
pv = context$2$0.sent;
if (pv !== '7.1') {
files.push('Library/Preferences/com.apple.Preferences.plist');
} else {
files.push('Applications');
}
files = files.map(function (s) {
return _path2['default'].resolve(_this2.getDir(), s);
});
context$2$0.next = 9;
return _regeneratorRuntime.awrap((0, _asyncbox.asyncmap)(files, function callee$2$0(f) {
return _regeneratorRuntime.async(function callee$2$0$(context$3$0) {
while (1) switch (context$3$0.prev = context$3$0.next) {
case 0:
context$3$0.next = 2;
return _regeneratorRuntime.awrap(_appiumSupport.fs.hasAccess(f));
case 2:
return context$3$0.abrupt('return', context$3$0.sent);
case 3:
case 'end':
return context$3$0.stop();
}
}, null, _this2);
}));
case 9:
existences = context$2$0.sent;
fresh = _lodash2['default'].compact(existences).length !== files.length;
_logger2['default'].debug('Simulator ' + (fresh ? 'has not' : 'has') + ' been run before');
return context$2$0.abrupt('return', fresh);
case 13:
case 'end':
return context$2$0.stop();
}
}, null, this);
}
/**
* Retrieves the state of the current Simulator. One should distinguish the
* states of Simulator UI and the Simulator itself.
*
* @return {boolean} True if the current Simulator is running.
*/
}, {
key: 'isRunning',
value: function isRunning() {
var stat;
return _regeneratorRuntime.async(function isRunning$(context$2$0) {
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
context$2$0.next = 2;
return _regeneratorRuntime.awrap(this.stat());
case 2:
stat = context$2$0.sent;
return context$2$0.abrupt('return', stat.state === 'Booted');
case 4:
case 'end':
return context$2$0.stop();
}
}, null, this);
}
/**
* Verify whether the Simulator booting is completed and/or wait for it
* until the timeout expires.
*
* @param {number} startupTimeout - the number of milliseconds to wait until booting is completed.
* @emits BOOT_COMPLETED_EVENT if the current Simulator is ready to accept simctl commands, like 'install'.
*/
}, {
key: 'waitForBoot',
value: function waitForBoot(startupTimeout) {
var bootedIndicator;
return _regeneratorRuntime.async(function waitForBoot$(context$2$0) {
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
context$2$0.next = 2;
return _regeneratorRuntime.awrap(this.getBootedIndicatorString());
case 2:
bootedIndicator = context$2$0.sent;
context$2$0.next = 5;
return _regeneratorRuntime.awrap(this.tailLogsUntil(bootedIndicator, startupTimeout));
case 5:
// so sorry, but we should wait another two seconds, just to make sure we've really started
// we can't look for another magic log line, because they seem to be app-dependent (not system dependent)
_logger2['default'].debug('Waiting an extra ' + this.extraStartupTime + 'ms for the simulator to really finish booting');
context$2$0.next = 8;
return _regeneratorRuntime.awrap(_bluebird2['default'].delay(this.extraStartupTime));
case 8:
_logger2['default'].debug('Done waiting extra time for simulator');
this.emit(BOOT_COMPLETED_EVENT);
case 10:
case 'end':
return context$2$0.stop();
}
}, null, this);
}
/**
* Returns a magic string, which, if present in logs, reflects the fact that simulator booting has been completed.
*
* @return {string} The magic log string.
*/
}, {
key: 'getBootedIndicatorString',
value: function getBootedIndicatorString() {
var indicator, platformVersion;
return _regeneratorRuntime.async(function getBootedIndicatorString$(context$2$0) {
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
indicator = undefined;
context$2$0.next = 3;
return _regeneratorRuntime.awrap(this.getPlatformVersion());
case 3:
platformVersion = context$2$0.sent;
context$2$0.t0 = platformVersion;
context$2$0.next = context$2$0.t0 === '7.1' ? 7 : context$2$0.t0 === '8.1' ? 7 : context$2$0.t0 === '8.2' ? 7 : context$2$0.t0 === '8.3' ? 7 : context$2$0.t0 === '8.4' ? 7 : context$2$0.t0 === '9.0' ? 9 : context$2$0.t0 === '9.1' ? 9 : context$2$0.t0 === '9.2' ? 9 : context$2$0.t0 === '9.3' ? 9 : context$2$0.t0 === '10.0' ? 11 : 13;
break;
case 7:
indicator = 'profiled: Service starting...';
return context$2$0.abrupt('break', 15);
case 9:
indicator = 'System app "com.apple.springboard" finished startup';
return context$2$0.abrupt('break', 15);
case 11:
indicator = 'Switching to keyboard';
return context$2$0.abrupt('break', 15);
case 13:
_logger2['default'].warn('No boot indicator case for platform version \'' + platformVersion + '\'');
indicator = 'no boot indicator string available';
case 15:
return context$2$0.abrupt('return', indicator);
case 16:
case 'end':
return context$2$0.stop();
}
}, null, this);
}
/**
* Start the Simulator UI client with the given arguments
*
* @param {object} opts - One or more of available Simulator UI client options:
* - {string} scaleFactor: can be one of ['1.0', '0.75', '0.5', '0.33', '0.25'].
* Defines the window scale value for the UI client window for the current Simulator.
* Equals to null by default, which keeps the current scale unchanged.
* - {boolean} connectHardwareKeyboard: whether to connect the hardware keyboard to the
* Simulator UI client. Equals to false by default.
* - {number} startupTimeout: number of milliseconds to wait until Simulator booting
* process is completed. The default timeout will be used if not set explicitly.
*/
}, {
key: 'startUIClient',
value: function startUIClient() {
var opts = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
var simulatorApp, args, supportedScales, stat, formattedDeviceName, argumentName;
return _regeneratorRuntime.async(function startUIClient$(context$2$0) {
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
opts = _Object$assign({
scaleFactor: null,
connectHardwareKeyboard: false,
startupTimeout: this.startupTimeout
}, opts);
context$2$0.t0 = _path2['default'];
context$2$0.next = 4;
return _regeneratorRuntime.awrap((0, _appiumXcode.getPath)());
case 4:
context$2$0.t1 = context$2$0.sent;
context$2$0.t2 = this.simulatorApp;
simulatorApp = context$2$0.t0.resolve.call(context$2$0.t0, context$2$0.t1, 'Applications', context$2$0.t2);
args = ['-Fn', simulatorApp, '--args', '-CurrentDeviceUDID', this.udid];
if (!opts.scaleFactor) {
context$2$0.next = 17;
break;
}
supportedScales = ['1.0', '0.75', '0.5', '0.33', '0.25'];
if (supportedScales.indexOf(opts.scaleFactor) < 0) {
_logger2['default'].errorAndThrow('Only "' + supportedScales + '" values are supported as scale factors. "' + opts.scaleFactor + '" is passed instead.');
}
context$2$0.next = 13;
return _regeneratorRuntime.awrap(this.stat());
case 13:
stat = context$2$0.sent;
formattedDeviceName = stat.name.replace(/\s+/g, '-');
argumentName = '-SimulatorWindowLastScale-com.apple.CoreSimulator.SimDeviceType.' + formattedDeviceName;
args.push(argumentName, opts.scaleFactor);
case 17:
if (!opts.connectHardwareKeyboard) {
args.push('-ConnectHardwareKeyboard', '0');
}
_logger2['default'].info('Starting Simulator UI with command: open ' + args.join(' '));
context$2$0.next = 21;
return _regeneratorRuntime.awrap((0, _teen_process.exec)('open', args, { timeout: opts.startupTimeout }));
case 21:
case 'end':
return context$2$0.stop();
}
}, null, this);
}
/**
* Executes given Simulator with options. The Simulator will not be restarted if
* it is already running.
*
* @param {object} opts - One or more of available Simulator options.
* See {#startUIClient(opts)} documentation for more details on other supported keys.
*/
}, {
key: 'run',
value: function run() {
var opts = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
var _ref2, state, isServerRunning, isUIClientRunning, startTime;
return _regeneratorRuntime.async(function run$(context$2$0) {
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
opts = _Object$assign({
startupTimeout: this.startupTimeout
}, opts);
context$2$0.next = 3;
return _regeneratorRuntime.awrap(this.stat());
case 3:
_ref2 = context$2$0.sent;
state = _ref2.state;
isServerRunning = state === 'Booted';
context$2$0.next = 8;
return _regeneratorRuntime.awrap(this.isUIClientRunning());
case 8:
isUIClientRunning = context$2$0.sent;
if (!(isServerRunning && isUIClientRunning)) {
context$2$0.next = 12;
break;
}
_logger2['default'].info('Both Simulator with UDID ' + this.udid + ' and the UI client are currently running');
return context$2$0.abrupt('return');
case 12:
startTime = process.hrtime();
context$2$0.prev = 13;
context$2$0.next = 16;
return _regeneratorRuntime.awrap(this.shutdown());
case 16:
context$2$0.next = 21;
break;
case 18:
context$2$0.prev = 18;
context$2$0.t0 = context$2$0['catch'](13);
_logger2['default'].warn('Error on Simulator shutdown: ' + context$2$0.t0.message);
case 21:
context$2$0.next = 23;
return _regeneratorRuntime.awrap(this.startUIClient(opts));
case 23:
context$2$0.next = 25;
return _regeneratorRuntime.awrap(this.waitForBoot(opts.startupTimeout));
case 25:
_logger2['default'].info('Simulator with UDID ' + this.udid + ' booted in ' + process.hrtime(startTime)[0] + ' seconds');
case 26:
case 'end':
return context$2$0.stop();
}
}, null, this, [[13, 18]]);
}
// TODO keep keychains
/**
* Reset the current Simulator to the clean state.
*/
}, {
key: 'clean',
value: function clean() {
return _regeneratorRuntime.async(function clean$(context$2$0) {
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
context$2$0.next = 2;
return _regeneratorRuntime.awrap(this.endSimulatorDaemon());
case 2:
_logger2['default'].info('Cleaning simulator ' + this.udid);
context$2$0.next = 5;
return _regeneratorRuntime.awrap(simctl.eraseDevice(this.udid, 10000));
case 5:
case 'end':
return context$2$0.stop();
}
}, null, this);
}
/**
* Scrub (delete the preferences and changed files) the particular application on Simulator.
*
* @param {string} appFile - Application name minus ".app".
* @param {string} appBundleId - Bundle identifier of the application.
* @return {array} Array of deletion promises.
*/
}, {
key: 'scrubCustomApp',
value: function scrubCustomApp(appFile, appBundleId) {
return _regeneratorRuntime.async(function scrubCustomApp$(context$2$0) {
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
context$2$0.next = 2;
return _regeneratorRuntime.awrap(this.cleanCustomApp(appFile, appBundleId, true));
case 2:
return context$2$0.abrupt('return', context$2$0.sent);
case 3:
case 'end':
return context$2$0.stop();
}
}, null, this);
}
/**
* Clean/scrub the particular application on Simulator.
*
* @param {string} appFile - Application name minus ".app".
* @param {string} appBundleId - Bundle identifier of the application.
* @param {boolean} scrub - If `scrub` is false, we want to clean by deleting the app and all
* files associated with it. If `scrub` is true, we just want to delete the preferences and
* changed files.
* @return {array} Array of deletion promises.
*/
}, {
key: 'cleanCustomApp',
value: function cleanCustomApp(appFile, appBundleId) {
var scrub = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2];
var appDirs, deletePromises, _iteratorNormalCompletion2, _didIteratorError2, _iteratorError2, _iterator2, _step2, dir, relRmPath, rmPath;
return _regeneratorRuntime.async(function cleanCustomApp$(context$2$0) {
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
_logger2['default'].debug('Cleaning app data files for \'' + appFile + '\', \'' + appBundleId + '\'');
if (!scrub) {
_logger2['default'].debug('Deleting app altogether');
}
// get the directories to be deleted
context$2$0.next = 4;
return _regeneratorRuntime.awrap(this.getAppDirs(appFile, appBundleId, scrub));
case 4:
appDirs = context$2$0.sent;
if (!(appDirs.length === 0)) {
context$2$0.next = 8;
break;
}
_logger2['default'].debug("Could not find app directories to delete. It is probably not installed");
return context$2$0.abrupt('return');
case 8:
deletePromises = [];
_iteratorNormalCompletion2 = true;
_didIteratorError2 = false;
_iteratorError2 = undefined;
context$2$0.prev = 12;
for (_iterator2 = _getIterator(appDirs); !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
dir = _step2.value;
_logger2['default'].debug('Deleting directory: \'' + dir + '\'');
deletePromises.push(_appiumSupport.fs.rimraf(dir));
}
context$2$0.next = 20;
break;
case 16:
context$2$0.prev = 16;
context$2$0.t0 = context$2$0['catch'](12);
_didIteratorError2 = true;
_iteratorError2 = context$2$0.t0;
case 20:
context$2$0.prev = 20;
context$2$0.prev = 21;
if (!_iteratorNormalCompletion2 && _iterator2['return']) {
_iterator2['return']();
}
case 23:
context$2$0.prev = 23;
if (!_didIteratorError2) {
context$2$0.next = 26;
break;
}
throw _iteratorError2;
case 26:
return context$2$0.finish(23);
case 27:
return context$2$0.finish(20);
case 28:
context$2$0.next = 30;
return _regeneratorRuntime.awrap(this.getPlatformVersion());
case 30:
context$2$0.t1 = context$2$0.sent;
if (!(context$2$0.t1 >= 8)) {
context$2$0.next = 36;
break;
}
relRmPath = 'Library/Preferences/' + appBundleId + '.plist';
rmPath = _path2['default'].resolve(this.getRootDir(), relRmPath);
_logger2['default'].debug('Deleting file: \'' + rmPath + '\'');
deletePromises.push(_appiumSupport.fs.rimraf(rmPath));
case 36:
context$2$0.next = 38;
return _regeneratorRuntime.awrap(_bluebird2['default'].all(deletePromises));
case 38:
case 'end':
return context$2$0.stop();
}
}, null, this, [[12, 16, 20, 28], [21,, 23, 27]]);
}
/**
* Retrieve paths to dirs where application data is stored. iOS 8+ stores app data in two places,
* and iOS 7.1 has only one directory
*
* @param {string} appFile - Application name minus ".app".
* @param {string} appBundleId - Bundle identifier of the application.
* @param {boolean} scrub - The `Bundle` directory has the actual app in it. If we are just scrubbing,
* we want this to stay. If we are cleaning we delete.
* @return {array} Array of application data paths.
*/
}, {
key: 'getAppDirs',
value: function getAppDirs(appFile, appBundleId) {
var scrub = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2];
var dirs, data, bundle, _arr, _i, src;
return _regeneratorRuntime.async(function getAppDirs$(context$2$0) {
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
dirs = [];
context$2$0.next = 3;
return _regeneratorRuntime.awrap(this.getPlatformVersion());
case 3:
context$2$0.t0 = context$2$0.sent;
if (!(context$2$0.t0 >= 8)) {
context$2$0.next = 22;
break;
}
context$2$0.next = 7;
return _regeneratorRuntime.awrap(this.getAppDir(appBundleId));
case 7:
data = context$2$0.sent;
if (data) {
context$2$0.next = 10;
break;
}
return context$2$0.abrupt('return', dirs);
case 10:
if (scrub) {
context$2$0.next = 16;
break;
}
context$2$0.next = 13;
return _regeneratorRuntime.awrap(this.getAppDir(appBundleId, 'Bundle'));
case 13:
context$2$0.t1 = context$2$0.sent;
context$2$0.next = 17;
break;
case 16:
context$2$0.t1 = undefined;
case 17:
bundle = context$2$0.t1;
_arr = [data, bundle];
for (_i = 0; _i < _arr.length; _i++) {
src = _arr[_i];
if (src) {
dirs.push(src);
}
}
context$2$0.next = 26;
break;
case 22:
context$2$0.next = 24;
return _regeneratorRuntime.awrap(this.getAppDir(appFile));
case 24:
data = context$2$0.sent;
if (data) {
dirs.push(data);
}
case 26:
return context$2$0.abrupt('return', dirs);
case 27:
case 'end':
return context$2$0.stop();
}
}, null, this);
}
/**
* Execute the Simulator in order to have the initial file structure created and shutdown it afterwards.
*
* @param {boolean} safari - Whether to execute mobile Safari after startup.
* @param {number} startupTimeout - How long to wait until Simulator booting is completed (in milliseconds).
*/
}, {
key: 'launchAndQuit',
value: function launchAndQuit() {
var safari = arguments.length <= 0 || arguments[0] === undefined ? false : arguments[0];
var startupTimeout = arguments.length <= 1 || arguments[1] === undefined ? this.startupTimeout : arguments[1];
return _regeneratorRuntime.async(function launchAndQuit$(context$2$0) {
var _this3 = this;
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
_logger2['default'].debug('Attempting to launch and quit the simulator, to create directory structure');
_logger2['default'].debug('Will launch with Safari? ' + safari);
context$2$0.next = 4;
return _regeneratorRuntime.awrap(this.run(startupTimeout));
case 4:
if (!safari) {
context$2$0.next = 7;
break;
}
context$2$0.next = 7;
return _regeneratorRuntime.awrap(this.openUrl('http://www.appium.io'));
case 7:
context$2$0.next = 9;
return _regeneratorRuntime.awrap((0, _asyncbox.retryInterval)(20, 250, function callee$2$0() {
var msg;
return _regeneratorRuntime.async(function callee$2$0$(context$3$0) {
while (1) switch (context$3$0.prev = context$3$0.next) {
case 0:
context$3$0.next = 2;
return _regeneratorRuntime.awrap(this.isFresh());
case 2:
if (!context$3$0.sent) {
context$3$0.next = 6;
break;
}
msg = 'Simulator files not fully created. Waiting a bit';
_logger2['default'].debug(msg);
throw new Error(msg);
case 6:
case 'end':
return context$3$0.stop();
}
}, null, _this3);
}));
case 9:
context$2$0.next = 11;
return _regeneratorRuntime.awrap(this.shutdown());
case 11:
case 'end':
return context$2$0.stop();
}
}, null, this);
}
/**
* Looks for launchd daemons corresponding to the sim udid and tries to stop them cleanly
* This prevents xcrun simctl erase from hanging.
*/
}, {
key: 'endSimulatorDaemon',
value: function endSimulatorDaemon() {
var launchctlCmd, stopCmd, removeCmd;
return _regeneratorRuntime.async(function endSimulatorDaemon$(context$2$0) {
var _this4 = this;
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
_logger2['default'].debug('Killing any simulator daemons for ' + this.udid);
launchctlCmd = 'launchctl list | grep ' + this.udid + ' | cut -f 3 | xargs -n 1 launchctl';
context$2$0.prev = 2;
stopCmd = launchctlCmd + ' stop';
context$2$0.next = 6;
return _regeneratorRuntime.awrap((0, _teen_process.exec)('bash', ['-c', stopCmd]));
case 6:
context$2$0.next = 12;
break;
case 8:
context$2$0.prev = 8;
context$2$0.t0 = context$2$0['catch'](2);
_logger2['default'].warn('Could not stop simulator daemons: ' + context$2$0.t0.message);
_logger2['default'].debug('Carrying on anyway!');
case 12:
context$2$0.prev = 12;
removeCmd = launchctlCmd + ' remove';
context$2$0.next = 16;
return _regeneratorRuntime.awrap((0, _teen_process.exec)('bash', ['-c', removeCmd]));
case 16:
context$2$0.next = 22;
break;
case 18:
context$2$0.prev = 18;
context$2$0.t1 = context$2$0['catch'](12);
_logger2['default'].warn('Could not remove simulator daemons: ' + context$2$0.t1.message);
_logger2['default'].debug('Carrying on anyway!');
case 22:
context$2$0.prev = 22;
context$2$0.next = 25;
return _regeneratorRuntime.awrap((0, _asyncbox.waitForCondition)(function callee$2$0() {
var _ref3, stdout;
return _regeneratorRuntime.async(function callee$2$0$(context$3$0) {
while (1) switch (context$3$0.prev = context$3$0.next) {
case 0:
context$3$0.next = 2;
return _regeneratorRuntime.awrap((0, _teen_process.exec)('bash', ['-c', 'ps -e | grep ' + this.udid + ' | grep launchd_sim | grep -v bash | grep -v grep | awk {\'print$1\'}']));
case 2:
_ref3 = context$3$0.sent;
stdout = _ref3.stdout;
return context$3$0.abrupt('return', stdout.trim().length === 0);
case 5:
case 'end':
return context$3$0.stop();
}
}, null, _this4);
}, { waitMs: 10000, intervalMs: 500 }));
case 25:
context$2$0.next = 31;
break;
case 27:
context$2$0.prev = 27;
context$2$0.t2 = context$2$0['catch'](22);
_logger2['default'].warn('Could not end simulator daemon for ' + this.udid + ': ' + context$2$0.t2.message);
_logger2['default'].debug('Carrying on anyway!');
case 31:
case 'end':
return context$2$0.stop();
}
}, null, this, [[2, 8], [12, 18], [22, 27]]);
}
/**
* Shutdown all the running Simulators and the UI client.
*/
}, {
key: 'shutdown',
value: function shutdown() {
return _regeneratorRuntime.async(function shutdown$(context$2$0) {
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
context$2$0.next = 2;
return _regeneratorRuntime.awrap((0, _utilsJs.killAllSimulators)());
case 2:
case 'end':
return context$2$0.stop();
}
}, null, this);
}
/**
* Delete the particular Simulator from devices list
*/
}, {
key: 'delete',
value: function _delete() {
return _regeneratorRuntime.async(function _delete$(context$2$0) {
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
context$2$0.next = 2;
return _regeneratorRuntime.awrap(simctl.deleteDevice(this.udid));
case 2:
case 'end':
return context$2$0.stop();
}
}, null, this);
}
/**
* Update the particular preference file with the given key/value pairs.
*
* @param {string} plist - The preferences file to update.
* @param {object} updates - The key/value pairs to update.
*/
}, {
key: 'updateSettings',
value: function updateSettings(plist, updates) {
return _regeneratorRuntime.async(function updateSettings$(context$2$0) {
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
context$2$0.next = 2;
return _regeneratorRuntime.awrap(settings.updateSettings(this, plist, updates));
case 2:
return context$2$0.abrupt('return', context$2$0.sent);
case 3:
case 'end':
return context$2$0.stop();
}
}, null, this);
}
/**
* Authorize/de-authorize location settings for a particular application.
*
* @param {string} bundleId - The application ID to update.
* @param {boolean} authorized - Whether or not to authorize.
*/
}, {
key: 'updateLocationSettings',
value: function updateLocationSettings(bundleId, authorized) {
return _regeneratorRuntime.async(function updateLocationSettings$(context$2$0) {
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
context$2$0.next = 2;
return _regeneratorRuntime.awrap(settings.updateLocationSettings(this, bundleId, authorized));
case 2:
return context$2$0.abrupt('return', context$2$0.sent);
case 3:
case 'end':
return context$2$0.stop();
}
}, null, this);
}
/**
* Update settings for Safari.
*
* @param {object} updates - The hash of key/value pairs to update for Safari.
*/
}, {
key: 'updateSafariSettings',
value: function updateSafariSettings(updates) {
return _regeneratorRuntime.async(function updateSafariSettings$(context$2$0) {
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
context$2$0.next = 2;
return _regeneratorRuntime.awrap(settings.updateSafariUserSettings(this, updates));
case 2:
context$2$0.next = 4;
return _regeneratorRuntime.awrap(settings.updateSettings(this, 'mobil