UNPKG

appium-ios-simulator

Version:
1,448 lines (1,179 loc) 158 kB
'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