Best JavaScript code snippet using appium-xcuitest-driver
simulator-xcode-6.js
Source:simulator-xcode-6.js  
1import path from 'path';2import { default as xcode, getPath as getXcodePath } from 'appium-xcode';3import log from './logger';4import { fs, tempDir, mkdirp, plist, timing, util } from '@appium/support';5import B from 'bluebird';6import _ from 'lodash';7import AsyncLock from 'async-lock';8import {9  safeRimRaf, getDeveloperRoot, installSSLCert, hasSSLCert, activateApp,10  MOBILE_SAFARI_BUNDLE_ID, launchApp11} from './utils.js';12import { asyncmap, retryInterval, waitForCondition, retry } from 'asyncbox';13import * as settings from './settings';14import { exec } from 'teen_process';15import { tailUntil } from './tail-until.js';16import extensions from './extensions/index';17import { EventEmitter } from 'events';18import Calendar from './calendar';19import Permissions from './permissions';20import Simctl from 'node-simctl';21const STARTUP_TIMEOUT = 60 * 1000;22const EXTRA_STARTUP_TIME = 2000;23const UI_CLIENT_ACCESS_GUARD = new AsyncLock();24const UI_CLIENT_BUNDLE_ID = 'com.apple.iphonesimulator';25const SPRINGBOARD_BUNDLE_ID = 'com.apple.SpringBoard';26/*27 * This event is emitted as soon as iOS Simulator28 * has finished booting and it is ready to accept xcrun commands.29 * The event handler is called after 'run' method is completed30 * for Xcode 7 and older and is only useful in Xcode 8+,31 * since one can start doing stuff (for example install/uninstall an app) in parallel32 * with Simulator UI startup, which shortens session startup time.33 */34const BOOT_COMPLETED_EVENT = 'bootCompleted';35class SimulatorXcode6 extends EventEmitter {36  /**37   * Constructs the object with the `udid` and version of Xcode. Use the exported `getSimulator(udid)` method instead.38   *39   * @param {string} udid - The Simulator ID.40   * @param {object} xcodeVersion - The target Xcode version in format {major, minor, build}.41   */42  constructor (udid, xcodeVersion) {43    super();44    this.udid = String(udid);45    this.simctl = new Simctl({46      udid: this.udid,47    });48    this.xcodeVersion = xcodeVersion;49    // platformVersion cannot be found initially, since getting it has side effects for50    // our logic for figuring out if a sim has been run51    // it will be set when it is needed52    this._platformVersion = null;53    this.keychainPath = path.resolve(this.getDir(), 'Library', 'Keychains');54    this.simulatorApp = 'iOS Simulator.app';55    this.appDataBundlePaths = {};56    // list of files to check for when seeing if a simulator is "fresh"57    // (meaning it has never been booted).58    // If these files are present, we assume it's been successfully booted59    this.isFreshFiles = [60      'Library/ConfigurationProfiles',61      'Library/Cookies',62      'Library/Preferences/.GlobalPreferences.plist',63      'Library/Preferences/com.apple.springboard.plist',64      'var/run/syslog.pid'65    ];66    // extra time to wait for simulator to be deemed booted67    this.extraStartupTime = EXTRA_STARTUP_TIME;68    this.calendar = new Calendar(xcodeVersion, this.getDir());69    this.permissions = new Permissions(xcodeVersion, this.getDir(), this.udid);70  }71  /**72   * @return {string} Bundle identifier of Simulator UI client.73   */74  get uiClientBundleId () {75    return UI_CLIENT_BUNDLE_ID;76  }77  /**78   * @return {?string} The full path to the devices set where the current simulator is located.79   * `null` value means that the default path is used, which is usually `~/Library/Developer/CoreSimulator/Devices`80   */81  get devicesSetPath () {82    return this.simctl.devicesSetPath;83  }84  /**85   * Set the full path to the devices set. It is recommended to set this value86   * once right after Simulator instance is created and to not change it during87   * the instance lifecycle88   *89   * @param {?string} value The full path to the devices set root on the90   * local file system91   */92  set devicesSetPath (value) {93    this.simctl.devicesSetPath = value;94  }95  /**96   * Retrieves the current process id of the UI client97   *98   * @return {?string} The process ID or null if the UI client is not running99   */100  async getUIClientPid () {101    let stdout;102    try {103      ({stdout} = await exec('pgrep', ['-fn', `${this.simulatorApp}/Contents/MacOS/`]));104    } catch (e) {105      return null;106    }107    if (isNaN(parseInt(stdout, 10))) {108      return null;109    }110    stdout = stdout.trim();111    log.debug(`Got Simulator UI client PID: ${stdout}`);112    return stdout;113  }114  /**115   * Check the state of Simulator UI client.116   *117   * @return {boolean} True of if UI client is running or false otherwise.118   */119  async isUIClientRunning () {120    return !_.isNull(await this.getUIClientPid());121  }122  /**123   * How long to wait before throwing an error about Simulator startup timeout happened.124   *125   * @return {number} The number of milliseconds.126   */127  get startupTimeout () {128    return STARTUP_TIMEOUT;129  }130  /**131   * Get the platform version of the current Simulator.132   *133   * @return {string} SDK version, for example '8.3'.134   */135  async getPlatformVersion () {136    if (!this._platformVersion) {137      let {sdk} = await this.stat();138      this._platformVersion = sdk;139    }140    return this._platformVersion;141  }142  /**143   * Retrieve the full path to the directory where Simulator stuff is located.144   *145   * @return {string} The path string.146   */147  getRootDir () {148    let home = process.env.HOME;149    return path.resolve(home, 'Library', 'Developer', 'CoreSimulator', 'Devices');150  }151  /**152   * Retrieve the full path to the directory where Simulator applications data is located.153   *154   * @return {string} The path string.155   */156  getDir () {157    return path.resolve(this.getRootDir(), this.udid, 'data');158  }159  /**160   * Retrieve the full path to the directory where Simulator logs are stored.161   *162   * @return {string} The path string.163   */164  getLogDir () {165    let home = process.env.HOME;166    return path.resolve(home, 'Library', 'Logs', 'CoreSimulator', this.udid);167  }168  /**169   * Install valid .app package on Simulator.170   *171   * @param {string} app - The path to the .app package.172   */173  async installApp (app) {174    return await this.simctl.installApp(app);175  }176  /**177   * Verify whether the particular application is installed on Simulator.178   *179   * @param {string} bundleId - The bundle id of the application to be checked.180   * @param {string} appFule - Application name minus ".app" (for iOS 7.1)181   * @return {boolean} True if the given application is installed182   */183  async isAppInstalled (bundleId, appFile = null) {184    // `appFile` argument only necessary for iOS below version 8185    let appDirs = await this.getAppDirs(appFile, bundleId);186    return appDirs.length !== 0;187  }188  /**189   * Returns user installed bundle ids which has 'bundleName' in their Info.Plist as 'CFBundleName'190   * @param {string} bundleId - The bundle id of the application to be checked.191   * @return {array<string>} - The list of bundle ids which have 'bundleName'192   */193  async getUserInstalledBundleIdsByBundleName (bundleName) {194    const rootUserAppDir = await this.buildBundlePathMap('Bundle');195    const bundleIds = [];196    if (_.isEmpty(rootUserAppDir)) {197      return bundleIds;198    }199    for (const [bundleId, userAppDirPath] of Object.entries(rootUserAppDir)) {200      const appFile = (await fs.readdir(userAppDirPath)).find(201        (file) => path.extname(file).toLowerCase() === '.app');202      const infoPlistPath = path.resolve(userAppDirPath, appFile, 'Info.plist');203      if (!await fs.exists(infoPlistPath)) {204        continue;205      }206      try {207        const infoPlist = await plist.parsePlistFile(infoPlistPath, false);208        if (infoPlist.CFBundleName === bundleName) {209          bundleIds.push(bundleId);210        }211      } catch (err) {212        log.warn(`Failed to read plist ${infoPlistPath}. Original error '${err.message}'`);213        continue;214      }215    }216    log.debug(`The simulator has '${bundleIds.length}' bundles which have '${bundleName}' as their 'CFBundleName':`);217    for (const bundleId of bundleIds) {218      log.debug(`    '${bundleId}'`);219    }220    return bundleIds;221  }222  /**223   * Retrieve the directory for a particular application's data.224   *225   * @param {string} id - Either a bundleId (e.g., com.apple.mobilesafari) or, for iOS 7.1, the app name without `.app` (e.g., MobileSafari)226   * @param {string} subdir - The sub-directory we expect to be within the application directory. Defaults to "Data".227   * @return {string} The root application folder.228   */229  async getAppDir (id, subDir = 'Data') {230    this.appDataBundlePaths[subDir] = this.appDataBundlePaths[subDir] || {};231    if (_.isEmpty(this.appDataBundlePaths[subDir]) && !await this.isFresh()) {232      this.appDataBundlePaths[subDir] = await this.buildBundlePathMap(subDir);233    }234    return this.appDataBundlePaths[subDir][id];235  }236  /**237   * The xcode 6 simulators are really annoying, and bury the main app238   * directories inside directories just named with Hashes.239   * This function finds the proper directory by traversing all of them240   * and reading a metadata plist (Mobile Container Manager) to get the241   * bundle id.242   *243   * @param {string} subdir - The sub-directory we expect to be within the application directory. Defaults to "Data".244   * @return {object} The list of path-bundle pairs to an object where bundleIds are mapped to paths.245   */246  async buildBundlePathMap (subDir = 'Data') {247    log.debug('Building bundle path map');248    let applicationList;249    let pathBundlePair;250    if (await this.getPlatformVersion() === '7.1') {251      // apps available252      //   Web.app,253      //   WebViewService.app,254      //   MobileSafari.app,255      //   WebContentAnalysisUI.app,256      //   DDActionsService.app,257      //   StoreKitUIService.app258      applicationList = path.resolve(this.getDir(), 'Applications');259      pathBundlePair = async (dir) => {260        dir = path.resolve(applicationList, dir);261        let appFiles = await fs.glob(`${dir}/*.app`);262        let bundleId = appFiles[0].match(/.*\/(.*)\.app/)[1];263        return {path: dir, bundleId};264      };265    } else {266      applicationList = path.resolve(this.getDir(), 'Containers', subDir, 'Application');267      // given a directory, find the plist file and pull the bundle id from it268      let readBundleId = async (dir) => {269        let plist = path.resolve(dir, '.com.apple.mobile_container_manager.metadata.plist');270        let metadata = await settings.read(plist);271        return metadata.MCMMetadataIdentifier;272      };273      // given a directory, return the path and bundle id associated with it274      pathBundlePair = async (dir) => {275        dir = path.resolve(applicationList, dir);276        let bundleId = await readBundleId(dir);277        return {path: dir, bundleId};278      };279    }280    if (!await fs.exists(applicationList)) {281      log.warn(`No directory path '${applicationList}'`);282      return {};283    }284    let bundlePathDirs = await fs.readdir(applicationList);285    let bundlePathPairs = await asyncmap(bundlePathDirs, async function (dir) {286      return await pathBundlePair(dir);287    }, false);288    // reduce the list of path-bundle pairs to an object where bundleIds are mapped to paths289    return bundlePathPairs.reduce((bundleMap, bundlePath) => {290      bundleMap[bundlePath.bundleId] = bundlePath.path;291      return bundleMap;292    }, {});293  }294  /**295   * Get the state and specifics of this sim.296   *297   * @return {object} Simulator stats mapping, for example:298   * { name: 'iPhone 4s',299   *   udid: 'C09B34E5-7DCB-442E-B79C-AB6BC0357417',300   *   state: 'Shutdown',301   *   sdk: '8.3'302   * }303   */304  async stat () {305    for (let [sdk, deviceArr] of _.toPairs(await this.simctl.getDevices())) {306      for (let device of deviceArr) {307        if (device.udid === this.udid) {308          device.sdk = sdk;309          return device;310        }311      }312    }313    return {};314  }315  /**316   * This is a best-bet heuristic for whether or not a sim has been booted317   * before. We usually want to start a simulator to "warm" it up, have318   * Xcode populate it with plists for us to manipulate before a real319   * test run.320   *321   * @return {boolean} True if the current Simulator has never been started before322   */323  async isFresh () {324    // if the following files don't exist, it hasn't been booted.325    // THIS IS NOT AN EXHAUSTIVE LIST326    let files = this.isFreshFiles;327    let pv = await this.getPlatformVersion();328    if (pv !== '7.1') {329      files.push('Library/Preferences/com.apple.Preferences.plist');330    } else {331      files.push('Applications');332    }333    const dir = this.getDir();334    files = files.map((s) => path.resolve(dir, s));335    const existences = await asyncmap(files, async (f) => await fs.hasAccess(f));336    const fresh = _.compact(existences).length !== files.length;337    log.debug(`Checking whether simulator has been run before: ${fresh ? 'no' : 'yes'}`);338    return fresh;339  }340  /**341   * Retrieves the state of the current Simulator. One should distinguish the342   * states of Simulator UI and the Simulator itself.343   *344   * @return {boolean} True if the current Simulator is running.345   */346  async isRunning () {347    try {348      await this.simctl.getEnv('dummy');349      return true;350    } catch (e) {351      return false;352    }353  }354  /**355   * Checks if the simulator is in shutdown state.356   * This method is necessary, because Simulator might also be357   * in the transitional Shutting Down state right after the `shutdown`358   * command has been issued.359   *360   * @return {boolean} True if the current Simulator is shut down.361   */362  async isShutdown () {363    try {364      await this.simctl.getEnv('dummy');365      return false;366    } catch (e) {367      return _.includes(e.stderr, 'Current state: Shutdown');368    }369  }370  /**371   * Verify whether the Simulator booting is completed and/or wait for it372   * until the timeout expires.373   *374   * @param {number} startupTimeout - the number of milliseconds to wait until booting is completed.375   * @emits BOOT_COMPLETED_EVENT if the current Simulator is ready to accept simctl commands, like 'install'.376   */377  async waitForBoot (startupTimeout) {378    // wait for the simulator to boot379    // waiting for the simulator status to be 'booted' isn't good enough380    // it claims to be booted way before finishing loading381    // let's tail the simulator system log until we see a magic line (this.bootedIndicator)382    let bootedIndicator = await this.getBootedIndicatorString();383    await this.tailLogsUntil(bootedIndicator, startupTimeout);384    // so sorry, but we should wait another two seconds, just to make sure we've really started385    // we can't look for another magic log line, because they seem to be app-dependent (not system dependent)386    log.debug(`Waiting an extra ${this.extraStartupTime}ms for the simulator to really finish booting`);387    await B.delay(this.extraStartupTime);388    log.debug('Done waiting extra time for simulator');389    this.emit(BOOT_COMPLETED_EVENT);390  }391  /**392   * Returns a magic string, which, if present in logs, reflects the fact that simulator booting has been completed.393   *394   * @return {string} The magic log string.395   */396  async getBootedIndicatorString () {397    let indicator;398    let platformVersion = await this.getPlatformVersion();399    switch (platformVersion) {400      case '7.1':401      case '8.1':402      case '8.2':403      case '8.3':404      case '8.4':405        indicator = 'profiled: Service starting...';406        break;407      case '9.0':408      case '9.1':409      case '9.2':410      case '9.3':411        indicator = 'System app "com.apple.springboard" finished startup';412        break;413      case '10.0':414        indicator = 'Switching to keyboard';415        break;416      default:417        log.warn(`No boot indicator case for platform version '${platformVersion}'`);418        indicator = 'no boot indicator string available';419    }420    return indicator;421  }422  /**423   * @typedef {Object} SimulatorOptions424   * @property {?string} scaleFactor [null] - Defines the window scale value for the UI client window for the current Simulator.425   *   Equals to null by default, which keeps the current scale unchanged.426   *   It should be one of ['1.0', '0.75', '0.5', '0.33', '0.25'].427   * @property {number} startupTimeout [60000] - Number of milliseconds to wait until Simulator booting428   *   process is completed. The default timeout will be used if not set explicitly.429   */430  /**431   * Start the Simulator UI client with the given arguments432   * @param {SimulatorOptions} opts - Simulator startup options433   */434  async startUIClient (opts = {}) {435    opts = _.cloneDeep(opts);436    _.defaultsDeep(opts, {437      scaleFactor: null,438      startupTimeout: this.startupTimeout,439    });440    const simulatorApp = path.resolve(await getXcodePath(), 'Applications', this.simulatorApp);441    const args = [442      '-Fn', simulatorApp,443      '--args', '-CurrentDeviceUDID', this.udid,444    ];445    if (opts.scaleFactor) {446      const {name} = await this.stat();447      const formattedDeviceName = name.replace(/\s+/g, '-');448      const argumentName = `-SimulatorWindowLastScale-com.apple.CoreSimulator.SimDeviceType.${formattedDeviceName}`;449      args.push(argumentName, opts.scaleFactor);450    }451    log.info(`Starting Simulator UI with command: open ${args.join(' ')}`);452    try {453      await exec('open', args, {timeout: opts.startupTimeout});454    } catch (err) {455      if (!(err.stdout || '').includes('-10825') && !(err.stderr || '').includes('-10825')) {456        throw err;457      }458      log.warn(`Error while opening UI: ${err.stdout || err.stderr}. Continuing`);459    }460  }461  /**462   * Executes given Simulator with options. The Simulator will not be restarted if463   * it is already running.464   *465   * @param {object} opts - One or more of available Simulator options.466   *   See {#startUIClient(opts)} documentation for more details on other supported keys.467   */468  async run (opts = {}) {469    opts = Object.assign({470      startupTimeout: this.startupTimeout,471    }, opts);472    const isServerRunning = await this.isRunning();473    const isUIClientRunning = await this.isUIClientRunning();474    if (isServerRunning && isUIClientRunning) {475      log.info(`Both Simulator with UDID ${this.udid} and the UI client are currently running`);476      return;477    }478    const timer = new timing.Timer().start();479    try {480      await this.shutdown();481    } catch (err) {482      log.warn(`Error on Simulator shutdown: ${err.message}`);483    }484    await this.startUIClient(opts);485    await this.waitForBoot(opts.startupTimeout);486    log.info(`Simulator with UDID ${this.udid} booted in ${timer.getDuration().asSeconds.toFixed(3)}s`);487  }488  // TODO keep keychains489  /**490   * Reset the current Simulator to the clean state.491   */492  async clean () {493    await this.endSimulatorDaemon();494    log.info(`Cleaning simulator ${this.udid}`);495    await this.simctl.eraseDevice(10000);496  }497  /**498   * Scrub (delete the preferences and changed files) the particular application on Simulator.499   *500   * @param {string} appFile - Application name minus ".app".501   * @param {string} appBundleId - Bundle identifier of the application.502   */503  async scrubCustomApp (appFile, appBundleId) {504    return await this.cleanCustomApp(appFile, appBundleId, true);505  }506  /**507   * Clean/scrub the particular application on Simulator.508   *509   * @param {string} appFile - Application name minus ".app".510   * @param {string} appBundleId - Bundle identifier of the application.511   * @param {boolean} scrub - If `scrub` is false, we want to clean by deleting the app and all512   *   files associated with it. If `scrub` is true, we just want to delete the preferences and513   *   changed files.514   */515  async cleanCustomApp (appFile, appBundleId, scrub = false) {516    log.debug(`Cleaning app data files for '${appFile}', '${appBundleId}'`);517    if (!scrub) {518      log.debug(`Deleting app altogether`);519    }520    // get the directories to be deleted521    let appDirs = await this.getAppDirs(appFile, appBundleId, scrub);522    if (appDirs.length === 0) {523      log.debug('Could not find app directories to delete. It is probably not installed');524      return;525    }526    let deletePromises = [];527    for (let dir of appDirs) {528      log.debug(`Deleting directory: '${dir}'`);529      deletePromises.push(fs.rimraf(dir));530    }531    if (await this.getPlatformVersion() >= 8) {532      let relRmPath = `Library/Preferences/${appBundleId}.plist`;533      let rmPath = path.resolve(this.getRootDir(), relRmPath);534      log.debug(`Deleting file: '${rmPath}'`);535      deletePromises.push(fs.rimraf(rmPath));536    }537    await B.all(deletePromises);538  }539  /**540   * Retrieve paths to dirs where application data is stored. iOS 8+ stores app data in two places,541   * and iOS 7.1 has only one directory542   *543   * @param {string} appFile - Application name minus ".app".544   * @param {string} appBundleId - Bundle identifier of the application.545   * @param {boolean} scrub - The `Bundle` directory has the actual app in it. If we are just scrubbing,546   *   we want this to stay. If we are cleaning we delete.547   * @return {array<string>} Array of application data paths.548   */549  async getAppDirs (appFile, appBundleId, scrub = false) {550    let dirs = [];551    if (await this.getPlatformVersion() >= 8) {552      let data = await this.getAppDir(appBundleId);553      if (!data) return dirs; // eslint-disable-line curly554      let bundle = !scrub ? await this.getAppDir(appBundleId, 'Bundle') : undefined;555      for (let src of [data, bundle]) {556        if (src) {557          dirs.push(src);558        }559      }560    } else {561      let data = await this.getAppDir(appFile);562      if (data) {563        dirs.push(data);564      }565    }566    return dirs;567  }568  /**569   * Execute the Simulator in order to have the initial file structure created and shutdown it afterwards.570   *571   * @param {boolean} safari - Whether to execute mobile Safari after startup.572   * @param {number} startupTimeout - How long to wait until Simulator booting is completed (in milliseconds).573   */574  async launchAndQuit (safari = false, startupTimeout = this.startupTimeout) {575    log.debug('Attempting to launch and quit the simulator to create the directory structure');576    await this.run({startupTimeout});577    if (safari) {578      log.debug('Spawning Safari browser in order to create the necessary file system items');579      await launchApp(this.simctl, MOBILE_SAFARI_BUNDLE_ID);580    }581    // wait for the system to create the files we will manipulate582    // need quite a high retry number, in order to accommodate iOS 7.1583    // locally, 7.1 averages 8.5 retries (from 6 - 12)584    //          8 averages 0.6 retries (from 0 - 2)585    //          9 averages 14 retries586    try {587      await retryInterval(60, 250, async () => {588        if (await this.isFresh()) {589          throw new Error('Simulator files not fully created. Waiting a bit');590        }591      });592    } catch (err) {593      log.warn(`Timeout waiting for simulator files to be created. Continuing`);594    }595    // and quit596    await this.shutdown();597  }598  /**599   * Looks for launchd daemons corresponding to the sim udid and tries to stop them cleanly600   * This prevents xcrun simctl erase from hanging.601   */602  async endSimulatorDaemon () {603    log.debug(`Killing any simulator daemons for ${this.udid}`);604    let launchctlCmd = `launchctl list | grep ${this.udid} | cut -f 3 | xargs -n 1 launchctl`;605    try {606      let stopCmd = `${launchctlCmd} stop`;607      await exec('bash', ['-c', stopCmd]);608    } catch (err) {609      log.warn(`Could not stop simulator daemons: ${err.message}`);610      log.debug('Carrying on anyway!');611    }612    try {613      let removeCmd = `${launchctlCmd} remove`;614      await exec('bash', ['-c', removeCmd]);615    } catch (err) {616      log.warn(`Could not remove simulator daemons: ${err.message}`);617      log.debug('Carrying on anyway!');618    }619    try {620      // Waits 10 sec for the simulator launchd services to stop.621      await waitForCondition(async () => {622        let {stdout} = await exec('bash', ['-c',623          `ps -e  | grep ${this.udid} | grep launchd_sim | grep -v bash | grep -v grep | awk {'print$1'}`]);624        return stdout.trim().length === 0;625      }, {waitMs: 10000, intervalMs: 500});626    } catch (err) {627      log.warn(`Could not end simulator daemon for ${this.udid}: ${err.message}`);628      log.debug('Carrying on anyway!');629    }630  }631  /**632   * @typedef {Object} ShutdownOptions633   * @property {?number|string} timeout The number of milliseconds to wait until634   * Simulator is shut down completely. No wait happens if the timeout value is not set635   */636  /**637   * Shut down the current Simulator.638   *639   * @param {?ShutdownOptions} opts640   * @throws {Error} If Simulator fails to transition into Shutdown state after641   * the given timeout642   */643  async shutdown (opts = {}) {644    if (await this.isShutdown()) {645      return;646    }647    await retryInterval(5, 500, this.simctl.shutdownDevice.bind(this.simctl));648    const waitMs = parseInt(opts.timeout, 10);649    if (waitMs > 0) {650      try {651        await waitForCondition(async () => await this.isShutdown(), {652          waitMs,653          intervalMs: 100,654        });655      } catch (err) {656        throw new Error(`Simulator is not in 'Shutdown' state after ${waitMs}ms`);657      }658    }659  }660  /**661   * Delete the particular Simulator from devices list662   */663  async delete () {664    await this.simctl.deleteDevice();665  }666  /**667   * Update the particular preference file with the given key/value pairs.668   *669   * @param {string} plist - The preferences file to update.670   * @param {object} updates - The key/value pairs to update.671   */672  async updateSettings (plist, updates) {673    return await settings.updateSettings(this, plist, updates);674  }675  /**676   * Authorize/de-authorize location settings for a particular application.677   *678   * @param {string} bundleId - The application ID to update.679   * @param {boolean} authorized - Whether or not to authorize.680   */681  async updateLocationSettings (bundleId, authorized) {682    return await settings.updateLocationSettings(this, bundleId, authorized);683  }684  /**685   * Enable/Disable reduce motion.686   *687   * @param {boolean} reduceMotion - Whether or not to enable it.688   */689  async setReduceMotion (reduceMotion = true) {690    if (await this.isFresh()) {691      await this.launchAndQuit(false, STARTUP_TIMEOUT);692    }693    await settings.setReduceMotion(this, reduceMotion);694  }695  /**696   * Sets UI appearance style.697   * This function can only be called on a booted simulator.698   *699   * @since Xcode SDK 11.4700   */701  async setAppearance (/* value */) { // eslint-disable-line require-await702    throw new Error(`Xcode SDK '${this.xcodeVersion}' is too old to set UI appearance`);703  }704  /**705   * Gets the current UI appearance style706   * This function can only be called on a booted simulator.707   *708   * @since Xcode SDK 11.4709   */710  async getAppearance () { // eslint-disable-line require-await711    throw new Error(`Xcode SDK '${this.xcodeVersion}' is too old to get UI appearance`);712  }713  /**714   * Update settings for Safari.715   *716   * @param {object} updates - The hash of key/value pairs to update for Safari.717   */718  async updateSafariSettings (updates) {719    let updated = await settings.updateSafariUserSettings(this, updates);720    return await settings.updateSettings(this, 'mobileSafari', updates) || updated;721  }722  /**723   * Update global settings for Safari.724   *725   * @param {object} updates - The hash of key/value pairs to update for Safari.726   */727  async updateSafariGlobalSettings (updates) {728    return await settings.updateSafariGlobalSettings(this, updates);729  }730  /**731   * Update the locale for the Simulator.732   *733   * @param {string} language - The language for the simulator. E.g., `"fr_US"`.734   * @param {string} locale - The locale to set for the simulator. E.g., `"en"`.735   * @param {string} calendarFormat - The format of the calendar.736   */737  async updateLocale (language, locale, calendarFormat) {738    return await settings.updateLocale(this, language, locale, calendarFormat);739  }740  /**741   * Completely delete mobile Safari application from the current Simulator.742   */743  async deleteSafari () {744    log.debug('Deleting Safari apps from simulator');745    let dirs = [];746    // get the data directory747    dirs.push(await this.getAppDir(MOBILE_SAFARI_BUNDLE_ID));748    let pv = await this.getPlatformVersion();749    if (pv >= 8) {750      // get the bundle directory751      dirs.push(await this.getAppDir(MOBILE_SAFARI_BUNDLE_ID, 'Bundle'));752    }753    let deletePromises = [];754    for (let dir of _.compact(dirs)) {755      log.debug(`Deleting directory: '${dir}'`);756      deletePromises.push(fs.rimraf(dir));757    }758    await B.all(deletePromises);759  }760  /**761   * Clean up the directories for mobile Safari.762   *763   * @param {boolean} keepPrefs - Whether to keep Safari preferences from being deleted.764   */765  async cleanSafari (keepPrefs = true) {766    log.debug('Cleaning mobile safari data files');767    if (await this.isFresh()) {768      log.info('Could not find Safari support directories to clean out old ' +769               'data. Probably there is nothing to clean out');770      return;771    }772    let libraryDir = path.resolve(this.getDir(), 'Library');773    let safariRoot = await this.getAppDir(MOBILE_SAFARI_BUNDLE_ID);774    if (!safariRoot) {775      log.info('Could not find Safari support directories to clean out old ' +776               'data. Probably there is nothing to clean out');777      return;778    }779    let safariLibraryDir = path.resolve(safariRoot, 'Library');780    let filesToDelete = [781      `Caches/Snapshots/${MOBILE_SAFARI_BUNDLE_ID}`,782      `Caches/${MOBILE_SAFARI_BUNDLE_ID}/*`,783      'Caches/com.apple.WebAppCache/*',784      'Caches/com.apple.WebKit.Networking/*',785      'Caches/com.apple.WebKit.WebContent/*',786      'Image Cache/*',787      `WebKit/${MOBILE_SAFARI_BUNDLE_ID}/*`,788      'WebKit/GeolocationSites.plist',789      'WebKit/LocalStorage/*.*',790      'Safari/*',791      'Cookies/*.binarycookies',792      'Caches/com.apple.UIStatusBar/*',793      'Caches/com.apple.keyboards/images/*',794      'Caches/com.apple.Safari.SafeBrowsing/*',795      `../tmp/${MOBILE_SAFARI_BUNDLE_ID}/*`796    ];797    let deletePromises = [];798    for (let file of filesToDelete) {799      deletePromises.push(fs.rimraf(path.resolve(libraryDir, file)));800      deletePromises.push(fs.rimraf(path.resolve(safariLibraryDir, file)));801    }802    if (!keepPrefs) {803      deletePromises.push(fs.rimraf(path.resolve(safariLibraryDir, 'Preferences/*.plist')));804    }805    await B.all(deletePromises);806  }807  /**808   * Uninstall the given application from the current Simulator.809   *810   * @param {string} bundleId - The buindle ID of the application to be removed.811   */812  async removeApp (bundleId) {813    await this.simctl.removeApp(bundleId);814  }815  /**816   * Move a built-in application to a new place (actually, rename it).817   *818   * @param {string} appName - The name of the app to be moved.819   * @param {string} appPath - The current path to the application.820   * @param {string} newAppPath - The new path to the application.821   *   If some application already exists by this path then it's going to be removed.822   */823  async moveBuiltInApp (appName, appPath, newAppPath) {824    await safeRimRaf(newAppPath);825    await fs.copyFile(appPath, newAppPath);826    log.debug(`Copied '${appName}' to '${newAppPath}'`);827    await fs.rimraf(appPath);828    log.debug(`Temporarily deleted original app at '${appPath}'`);829    return [newAppPath, appPath];830  }831  /**832   * Open the given URL in mobile Safari browser.833   * The browser will be started automatically if it is not running.834   *835   * @param {string} url - The URL to be opened.836   */837  async openUrl (url) {838    const SAFARI_BOOTED_INDICATOR = 'MobileSafari[';839    const SAFARI_STARTUP_TIMEOUT = 15 * 1000;840    const EXTRA_STARTUP_TIME = 3 * 1000;841    if (await this.isRunning()) {842      await retry(5000, this.simctl.openUrl.bind(this.simctl), url);843      await this.tailLogsUntil(SAFARI_BOOTED_INDICATOR, SAFARI_STARTUP_TIMEOUT);844      // So sorry, but the logs have nothing else for Safari starting.. just delay a little bit845      log.debug(`Safari started, waiting ${EXTRA_STARTUP_TIME}ms for it to fully start`);846      await B.delay(EXTRA_STARTUP_TIME);847      log.debug('Done waiting for Safari');848      return;849    } else {850      throw new Error('Tried to open a url, but the Simulator is not Booted');851    }852  }853  /**854   * Perform Simulator caches cleanup.855   *856   * @param {...string} folderNames - The names of Caches subfolders to be cleaned.857   *   Non-accessible/non-existing subfolders will be skipped.858   *   All existing subfolders under Caches will be deleted if this parameter is omitted.859   * @returns {number} The count of cleaned cache items.860   *   Zero is returned if no items were matched for cleanup (either not accessible or not directories).861   */862  async clearCaches (...folderNames) {863    const cachesRoot = path.resolve(this.getDir(), 'Library', 'Caches');864    if (!(await fs.hasAccess(cachesRoot))) {865      log.debug(`Caches root at '${cachesRoot}' does not exist or is not accessible. Nothing to do there`);866      return 0;867    }868    let itemsToRemove = folderNames.length ? folderNames : (await fs.readdir(cachesRoot));869    itemsToRemove = itemsToRemove.map((x) => path.resolve(cachesRoot, x));870    if (folderNames.length) {871      itemsToRemove = await B.filter(itemsToRemove, (x) => fs.hasAccess(x));872    }873    itemsToRemove = await B.filter(itemsToRemove, async (x) => (await fs.stat(x)).isDirectory());874    if (!itemsToRemove.length) {875      log.debug(`No Simulator cache items for cleanup were matched in '${cachesRoot}'`);876      return 0;877    }878    log.debug(`Matched ${util.pluralize('simulator cache item', itemsToRemove.length, true)} ` +879      `for cleanup: ${itemsToRemove}`);880    try {881      await B.all(itemsToRemove, (x) => fs.rimraf(x));882    } catch (e) {883      log.warn(`Got an exception while cleaning Simulator caches: ${e.message}`);884    }885    return itemsToRemove.length;886  }887  /**888   * Blocks until the given indicater string appears in Simulator logs.889   *890   * @param {string} bootedIndicator - The magic string, which appears in logs after Simulator booting is completed.891   * @param {number} timeoutMs - The maximumm number of milliseconds to wait for the string indicator presence.892   * @returns {Promise} A promise that resolves when the ios simulator logs output a line matching `bootedIndicator`893   * times out after timeoutMs894   */895  async tailLogsUntil (bootedIndicator, timeoutMs) {896    let simLog = path.resolve(this.getLogDir(), 'system.log');897    // we need to make sure log file exists before we can tail it898    await retryInterval(200, 200, async () => {899      let exists = await fs.exists(simLog);900      if (!exists) {901        throw new Error(`Could not find Simulator log: '${simLog}'`);902      }903    });904    log.info(`Simulator log at '${simLog}'`);905    log.info(`Tailing simulator logs until we encounter the string "${bootedIndicator}"`);906    log.info(`We will time out after ${timeoutMs}ms`);907    try {908      await tailUntil(simLog, bootedIndicator, timeoutMs);909    } catch (err) {910      log.debug('Simulator startup timed out. Continuing anyway.');911    }912  }913  /**914   * Enable Calendar access for the given application.915   *916   * @param {string} bundleID - Bundle ID of the application, for which the access should be granted.917   */918  async enableCalendarAccess (bundleID) {919    await this.calendar.enableCalendarAccess(bundleID);920  }921  /**922   * Disable Calendar access for the given application.923   *924   * @param {string} bundleID - Bundle ID of the application, for which the access should be denied.925   */926  async disableCalendarAccess (bundleID) {927    await this.calendar.disableCalendarAccess(bundleID);928  }929  /**930   * Check whether the given application has access to Calendar.931   *932   * @return {boolean} True if the given application has the access.933   */934  async hasCalendarAccess (bundleID) {935    return await this.calendar.hasCalendarAccess(bundleID);936  }937  /**938   * Activates Simulator window.939   *940   * @private941   * @returns {?string} If the method returns a string then it should be a valid Apple Script which942   * is appended before each UI client command is executed. Otherwise the method should activate the window943   * itself and return nothing.944   */945  async _activateWindow () { // eslint-disable-line require-await946    const pid = await this.getUIClientPid();947    if (pid) {948      try {949        return await activateApp(pid);950      } catch (e) {951        log.debug(e.stderr || e.message);952      }953    }954    return `955      tell application "System Events"956        tell process "Simulator"957          set frontmost to false958          set frontmost to true959        end tell960      end tell961    `;962  }963  /**964   * Execute given Apple Script inside a critical section, so other965   * sessions cannot influence the UI client at the same time.966   *967   * @param {string} appleScript - The valid Apple Script snippet to be executed.968   * @return {string} The stdout output produced by the script.969   * @throws {Error} If osascript tool returns non-zero exit code.970   */971  async executeUIClientScript (appleScript) {972    const windowActivationScript = await this._activateWindow();973    const resultScript = `${windowActivationScript ? windowActivationScript + '\n' : ''}${appleScript}`;974    log.debug(`Executing UI Apple Script on Simulator with UDID ${this.udid}: ${resultScript}`);975    return await UI_CLIENT_ACCESS_GUARD.acquire(this.simulatorApp, async () => {976      try {977        const {stdout} = await exec('osascript', ['-e', resultScript]);978        return stdout;979      } catch (err) {980        log.errorAndThrow(`Could not complete operation. Make sure Simulator UI is running and the parent Appium application (e. g. Appium.app or Terminal.app) ` +981                          `is present in System Preferences > Security & Privacy > Privacy > Accessibility list. If the operation is still unsuccessful then ` +982                          `it is not supported by this Simulator. ` +983                          `Original error: ${err.message}`);984      }985    });986  }987  /**988   * Get the current state of Biometric Enrollment feature.989   *990   * @returns {boolean} Either true or false991   * @throws {Error} If Enrollment state cannot be determined992   */993  async isBiometricEnrolled () {994    const output = await this.executeUIClientScript(`995      tell application "System Events"996        tell process "Simulator"997          set dstMenuItem to menu item "Touch ID Enrolled" of menu 1 of menu bar item "Hardware" of menu bar 1998          set isChecked to (value of attribute "AXMenuItemMarkChar" of dstMenuItem) is "â"999        end tell1000      end tell1001    `);1002    log.debug(`Touch ID enrolled state: ${output}`);1003    return _.isString(output) && output.trim() === 'true';1004  }1005  /**1006   * Enrolls biometric (TouchId, FaceId) feature testing in Simulator UI client.1007   *1008   * @param {boolean} isEnabled - Defines whether biometric state is enabled/disabled1009   * @throws {Error} If the enrolled state cannot be changed1010   */1011  async enrollBiometric (isEnabled = true) {1012    await this.executeUIClientScript(`1013      tell application "System Events"1014        tell process "Simulator"1015          set dstMenuItem to menu item "Touch ID Enrolled" of menu 1 of menu bar item "Hardware" of menu bar 11016          set isChecked to (value of attribute "AXMenuItemMarkChar" of dstMenuItem) is "â"1017          if ${isEnabled ? 'not ' : ''}isChecked then1018            click dstMenuItem1019          end if1020        end tell1021      end tell1022    `);1023  }1024  /**1025   * Sends a notification to match/not match the touch id.1026   *1027   * @param {?boolean} shouldMatch [true] - Set it to true or false in order to emulate1028   * matching/not matching the corresponding biometric1029   */1030  async sendBiometricMatch (shouldMatch = true) {1031    await this.executeUIClientScript(`1032      tell application "System Events"1033        tell process "Simulator"1034          set dstMenuItem to menu item "${shouldMatch ? 'Matching' : 'Non-matching'}" of menu 1 of menu item "Simulate Finger Touch" of menu 1 of menu bar item "Hardware" of menu bar 11035          click dstMenuItem1036        end tell1037      end tell1038    `);1039  }1040  /**1041   * Execute a special Apple script, which clicks the particular button on Database alert.1042   *1043   * @param {boolean} increase - Click the button with 'Increase' title on the alert if this1044   *   parameter is true. The 'Cancel' button will be clicked otherwise.1045   */1046  async dismissDatabaseAlert (increase = true) {1047    let button = increase ? 'Increase' : 'Cancel';1048    log.debug(`Attempting to dismiss database alert with '${button}' button`);1049    await this.executeUIClientScript(`1050      tell application "System Events"1051        tell process "Simulator"1052          click button "${button}" of window 11053        end tell1054      end tell1055    `);1056  }1057  //region Keychains Interaction1058  /**1059   * Create the backup of keychains folder.1060   * The previously created backup will be automatically1061   * deleted if this method was called twice in a row without1062   * `restoreKeychains` being invoked.1063   *1064   * @returns {boolean} True if the backup operation was successfull.1065   */1066  async backupKeychains () {1067    if (!await fs.exists(this.keychainPath)) {1068      return false;1069    }1070    const backupPath = await tempDir.path({1071      prefix: `keychains_backup_${Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1)}`,1072      suffix: '.zip',1073    });1074    const zipArgs = [1075      '-r', backupPath,1076      `${this.keychainPath}${path.sep}`1077    ];1078    log.debug(`Creating keychains backup with 'zip ${zipArgs.join(' ')}' command`);1079    await exec('zip', zipArgs);1080    if (_.isString(this._keychainsBackupPath) && await fs.exists(this._keychainsBackupPath)) {1081      await fs.unlink(this._keychainsBackupPath);1082    }1083    this._keychainsBackupPath = backupPath;1084    return true;1085  }1086  /**1087   * Restore the previsouly created keychains backup.1088   *1089   * @param {?string|Array<string>} excludePatterns - The list1090   * of file name patterns to be excluded from restore. The format1091   * of each item should be the same as '-x' option format for1092   * 'unzip' utility. This can also be a comma-separated string,1093   * which is going be transformed into a list automatically,1094   * for example: '*.db*,blabla.sqlite'1095   * @returns {boolean} If the restore opration was successful.1096   * @throws {Error} If there is no keychains backup available for restore.1097   */1098  async restoreKeychains (excludePatterns = []) {1099    if (!_.isString(this._keychainsBackupPath) || !await fs.exists(this._keychainsBackupPath)) {1100      throw new Error(`The keychains backup archive does not exist. ` +1101                      `Are you sure it was created before?`);1102    }1103    if (_.isString(excludePatterns)) {1104      excludePatterns = excludePatterns.split(',').map((x) => x.trim());1105    }1106    const isServerRunning = await this.isRunning();1107    let plistPath;1108    if (isServerRunning) {1109      plistPath = path.resolve(await this.getLaunchDaemonsRoot(), 'com.apple.securityd.plist');1110      if (!await fs.exists(plistPath)) {1111        throw new Error(`Cannot clear keychains because '${plistPath}' does not exist`);1112      }1113      await this.simctl.spawnProcess(['launchctl', 'unload', plistPath]);1114    }1115    try {1116      await fs.rimraf(this.keychainPath);1117      await mkdirp(this.keychainPath);1118      const unzipArgs = [1119        '-o', this._keychainsBackupPath,1120        ...(_.flatMap(excludePatterns.map((x) => ['-x', x]))),1121        '-d', '/'1122      ];1123      log.debug(`Restoring keychains with 'unzip ${unzipArgs.join(' ')}' command`);1124      await exec('unzip', unzipArgs);1125      await fs.unlink(this._keychainsBackupPath);1126      this._keychainsBackupPath = null;1127    } finally {1128      if (isServerRunning && plistPath) {1129        await this.simctl.spawnProcess(['launchctl', 'load', plistPath]);1130      }1131    }1132    return true;1133  }1134  /**1135   * Clears Keychains for the particular simulator in runtime (there is no need to stop it).1136   *1137   * @throws {Error} If keychain cleanup has failed.1138   */1139  async clearKeychains () {1140    const plistPath = path.resolve(await this.getLaunchDaemonsRoot(), 'com.apple.securityd.plist');1141    if (!await fs.exists(plistPath)) {1142      throw new Error(`Cannot clear keychains because '${plistPath}' does not exist`);1143    }1144    await this.simctl.spawnProcess(['launchctl', 'unload', plistPath]);1145    try {1146      if (await fs.exists(this.keychainPath)) {1147        await fs.rimraf(this.keychainPath);1148        await mkdirp(this.keychainPath);1149      }1150    } finally {1151      await this.simctl.spawnProcess(['launchctl', 'load', plistPath]);1152    }1153  }1154  //endregion1155  /**1156   * @typedef {Object} ProcessInfo1157   * @property {number} pid The actual process identifier.1158   * Could be zero if the process is the system one.1159   * @property {?string} group The process group identifier.1160   * This could be `null` if the process is not a part of the1161   * particular group. For `normal` application processes the group1162   * name usually equals to `UIKitApplication`.1163   * @property {string} name The process name, for example1164   * `com.apple.Preferences`1165   */1166  /**1167   * Lists processes that are currently running on the given Simulator.1168   * The simulator must be in running state in order for this1169   * method to work properly.1170   *1171   * @return {Array<ProcessInfo>} The list of retrieved process1172   * information1173   * @throws {Error} if no process information could be retrieved.1174   */1175  async ps () {1176    const {stdout} = await this.simctl.spawnProcess([1177      'launchctl',1178      'print',1179      'system',1180    ]);1181    const servicesMatch = /^\s*services\s*=\s*{([^}]+)/m.exec(stdout);1182    if (!servicesMatch) {1183      log.debug(stdout);1184      throw new Error(`The list of active processes cannot be retrieved`);1185    }1186    /*1187    Example match:1188        0     78 	com.apple.resourcegrabberd1189    82158      - 	com.apple.assistant_service1190    82120      - 	com.apple.nanoregistryd1191    82087      - 	com.apple.notifyd1192    82264      - 	UIKitApplication:com.apple.Preferences[704b][rb-legacy]1193    */1194    const result = [];1195    const pattern = /^\s*(\d+)\s+[\d-]+\s+([\w\-.]+:)?([\w\-.]+)/gm;1196    let match;1197    while ((match = pattern.exec(servicesMatch[1]))) {1198      result.push({1199        pid: parseInt(match[1], 10),1200        group: _.trimEnd(match[2], ':') || null,1201        name: match[3],1202      });1203    }1204    return result;1205  }1206  /**1207   * Sets the particular permission to the application bundle. See1208   * https://github.com/wix/AppleSimulatorUtils for more details on1209   * the available service names and statuses.1210   *1211   * @param {string} bundleId - Application bundle identifier.1212   * @param {string} permission - Service name to be set.1213   * @param {string} value - The desired status for the service.1214   * @throws {Error} If there was an error while changing permission.1215   */1216  async setPermission (bundleId, permission, value) {1217    await this.setPermissions(bundleId, {[permission]: value});1218  }1219  /**1220   * Sets the permissions for the particular application bundle.1221   *1222   * @param {string} bundleId - Application bundle identifier.1223   * @param {Object} permissionsMapping - A mapping where kays1224   * are service names and values are their corresponding status values.1225   * See https://github.com/wix/AppleSimulatorUtils1226   * for more details on available service names and statuses.1227   * @throws {Error} If there was an error while changing permissions.1228   */1229  async setPermissions (bundleId, permissionsMapping) {1230    log.debug(`Setting access for '${bundleId}': ` +1231      JSON.stringify(permissionsMapping, null, 2));1232    await this.permissions.setAccess(bundleId, permissionsMapping);1233  }1234  /**1235   * Retrieves current permission status for the given application bundle.1236   *1237   * @param {string} bundleId - Application bundle identifier.1238   * @param {string} serviceName - One of available service names.1239   * @throws {Error} If there was an error while retrieving permissions.1240   */1241  async getPermission (bundleId, serviceName) {1242    const result = await this.permissions.getAccess(bundleId, serviceName);1243    log.debug(`Got ${serviceName} access status for '${bundleId}': ${result}`);1244    return result;1245  }1246  /**1247   * Adds the given certificate into the Trusted Root Store on the simulator.1248   * The simulator must be shut down in order for this method to work properly.1249   *1250   * @param {string} payload the content of the PEM certificate1251   * @returns {boolean} `true` if the certificate has been successfully installed1252   * or `false` if it has already been there1253   */1254  async addCertificate (payload, /* opts = {} */) {1255    if (await hasSSLCert(payload, this.udid)) {1256      log.info(`SSL certificate '${_.truncate(payload, {length: 20})}' already installed`);1257      return false;1258    }1259    log.info(`Installing SSL root certificate '${_.truncate(payload, {length: 20})}'`);1260    await installSSLCert(payload, this.udid);1261    return true;1262  }1263  /**1264   * Simulates push notification delivery1265   *1266   * @since Xcode SDK 11.41267   */1268  async pushNotification (/* payload */) { // eslint-disable-line require-await1269    throw new Error(`Xcode SDK '${this.xcodeVersion}' is too old to push notifications`);1270  }1271  async getLaunchDaemonsRoot () {1272    const devRoot = await getDeveloperRoot();1273    return path.resolve(devRoot,1274      'Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/LaunchDaemons');1275  }1276  static async _getDeviceStringPlatformVersion (platformVersion) {1277    let reqVersion = platformVersion;1278    if (!reqVersion) {1279      reqVersion = await xcode.getMaxIOSSDK();1280      log.warn(`No platform version set. Using max SDK version: ${reqVersion}`);1281      // this will be a number, and possibly an integer (e.g., if max iOS SDK is 9)1282      // so turn it into a string and add a .0 if necessary1283      if (!_.isString(reqVersion)) {1284        reqVersion = (reqVersion % 1) ? String(reqVersion) : `${reqVersion}.0`;1285      }1286    }1287    return reqVersion;1288  }1289  // change the format in subclasses, as necessary1290  static async _getDeviceStringVersionString (platformVersion) {1291    let reqVersion = await this._getDeviceStringPlatformVersion(platformVersion);1292    return `(${reqVersion} Simulator)`;1293  }1294  // change the format in subclasses, as necessary1295  static _getDeviceStringConfigFix () {1296    // some devices need to be updated1297    return {1298      'iPad Simulator (7.1 Simulator)': 'iPad 2 (7.1 Simulator)',1299      'iPad Simulator (8.0 Simulator)': 'iPad 2 (8.0 Simulator)',1300      'iPad Simulator (8.1 Simulator)': 'iPad 2 (8.1 Simulator)',1301      'iPad Simulator (8.2 Simulator)': 'iPad 2 (8.2 Simulator)',1302      'iPad Simulator (8.3 Simulator)': 'iPad 2 (8.3 Simulator)',1303      'iPad Simulator (8.4 Simulator)': 'iPad 2 (8.4 Simulator)',1304      'iPhone Simulator (7.1 Simulator)': 'iPhone 5s (7.1 Simulator)',1305      'iPhone Simulator (8.4 Simulator)': 'iPhone 6 (8.4 Simulator)',1306      'iPhone Simulator (8.3 Simulator)': 'iPhone 6 (8.3 Simulator)',1307      'iPhone Simulator (8.2 Simulator)': 'iPhone 6 (8.2 Simulator)',1308      'iPhone Simulator (8.1 Simulator)': 'iPhone 6 (8.1 Simulator)',1309      'iPhone Simulator (8.0 Simulator)': 'iPhone 6 (8.0 Simulator)'1310    };1311  }1312  /**1313   * Takes a set of options and finds the correct device string in order for Instruments to1314   * identify the correct simulator.1315   *1316   * @param {object} opts - The options available are:1317   *   - `deviceName` - a name for the device. If the given device name starts with `=`, the name, less the equals sign, is returned.1318   *   - `platformVersion` - the version of iOS to use. Defaults to the current Xcode's maximum SDK version.1319   *   - `forceIphone` - force the configuration of the device string to iPhone. Defaults to `false`.1320   *   - `forceIpad` - force the configuration of the device string to iPad. Defaults to `false`.1321   *   If both `forceIphone` and `forceIpad` are true, the device will be forced to iPhone.1322   *1323   * @return {string} The found device string.1324   */1325  static async getDeviceString (opts) {1326    opts = Object.assign({}, {1327      deviceName: null,1328      platformVersion: null,1329      forceIphone: false,1330      forceIpad: false1331    }, opts);1332    let logOpts = {1333      deviceName: opts.deviceName,1334      platformVersion: opts.platformVersion,1335      forceIphone: opts.forceIphone,1336      forceIpad: opts.forceIpad1337    };1338    log.debug(`Getting device string from options: ${JSON.stringify(logOpts)}`);1339    // short circuit if we already have a device name1340    if ((opts.deviceName || '')[0] === '=') {1341      return opts.deviceName.substring(1);1342    }1343    let isiPhone = !!opts.forceIphone || !opts.forceIpad;1344    if (opts.deviceName) {1345      let device = opts.deviceName.toLowerCase();1346      if (device.indexOf('iphone') !== -1) {1347        isiPhone = true;1348      } else if (device.indexOf('ipad') !== -1) {1349        isiPhone = false;1350      }1351    }1352    let iosDeviceString = opts.deviceName || (isiPhone ? 'iPhone Simulator' : 'iPad Simulator');1353    // if someone passes in just "iPhone", make that "iPhone Simulator" to1354    // conform to all the logic below1355    if (/^(iPhone|iPad)$/.test(iosDeviceString)) {1356      iosDeviceString += ' Simulator';1357    }1358    // we support deviceName: "iPhone Simulator", and also want to support1359    // "iPhone XYZ Simulator", but these strings aren't in the device list.1360    // So, if someone sent in "iPhone XYZ Simulator", strip off " Simulator"1361    // in order to allow the default "iPhone XYZ" match1362    if (/[^(iPhone|iPad)] Simulator/.test(iosDeviceString)) {1363      iosDeviceString = iosDeviceString.replace(' Simulator', '');1364    }1365    iosDeviceString += ` ${await this._getDeviceStringVersionString(opts.platformVersion)}`;1366    let CONFIG_FIX = this._getDeviceStringConfigFix();1367    let configFix = CONFIG_FIX;1368    if (configFix[iosDeviceString]) {1369      iosDeviceString = configFix[iosDeviceString];1370      log.debug(`Fixing device. Changed from '${opts.deviceName}' ` +1371                `to '${iosDeviceString}'`);1372    }1373    log.debug(`Final device string is '${iosDeviceString}'`);1374    return iosDeviceString;1375  }1376  /**1377   * @return {?string} The full path to the simulator's WebInspector Unix Domain Socket1378   *   or `null` if there is no socket.1379   */1380  async getWebInspectorSocket () { // eslint-disable-line require-await1381    // there is no WebInspector socket for this version of Xcode1382    return null;1383  }1384}1385for (let [cmd, fn] of _.toPairs(extensions)) {1386  SimulatorXcode6.prototype[cmd] = fn;1387}1388export default SimulatorXcode6;...simctl-e2e-specs.js
Source:simctl-e2e-specs.js  
...103      // so we do not delete again104      simctl.udid = null;105    });106    it('should not fail to shutdown a shutdown simulator', async function () {107      await simctl.shutdownDevice().should.eventually.not.be.rejected;108    });109  });110  it('should return a nice error for invalid usage', async function () {111    let err = null;112    try {113      await simctl.createDevice('foo', 'bar', 'baz');114    } catch (e) {115      err = e;116    }117    should.exist(err);118    err.message.should.include(`Unable to parse version 'baz'`);119  });120  describe('on running Simulator', function () {121    if (process.env.TRAVIS) {122      this.retries(3);123    }124    let major, minor;125    before(async function () {126      ({major, minor} = await xcode.getVersion(true));127      if (major < 8 || (major === 8 && minor < 1)) {128        return this.skip();129      }130      const sdk = process.env.IOS_SDK || _.last(validSdks);131      simctl.udid = await simctl.createDevice('runningSimTest', DEVICE_NAME, sdk);132      await simctl.bootDevice();133      await simctl.startBootMonitor({timeout: MOCHA_TIMEOUT});134    });135    after(async function () {136      if (simctl.udid) {137        try {138          await simctl.shutdownDevice();139        } catch (ign) {}140        await simctl.deleteDevice();141        simctl.udid = null;142      }143    });144    describe('startBootMonitor', function () {145      it('should be fulfilled if the simulator is already booted', async function () {146        if (major < 8 || (major === 8 && minor < 1)) {147          return this.skip();148        }149        await simctl.startBootMonitor().should.eventually.be.fulfilled;150      });151      it('should fail to monitor booting of non-existing simulator', async function () {152        if (major < 8 || (major === 8 && minor < 1)) {...simulator-management.js
Source:simulator-management.js  
...183    // It is necessary to stop the corresponding xcodebuild process before killing184    // the simulator, otherwise it will be automatically restarted185    await resetTestProcesses(udid, true);186    simctl.udid = udid;187    await simctl.shutdownDevice();188  }189}190export { createSim, getExistingSim, runSimulatorReset, installToSimulator,...index.js
Source:index.js  
...21    await this.simctl.startBootMonitor({ timeout: 120000 });22    await this.simctl.openUrl(pageUrl);23  },24  async closeBrowser(id) {25    await this.simctl.shutdownDevice();26    this.currentDevices[id] = null;27  },28  // Optional29  // ---30  async init() {31    if (!this.simctl) {32      this.simctl = new Simctl();33    }34    // We don't run tests on tvOS or watchOS, so only include iOS devices35    const { devices, runtimes } = await this.simctl.list();36    const iOSRuntimes = runtimes.filter(runtime => /iOS/i.test(runtime.name));37    iOSRuntimes.forEach(runtime => {38      const identifier = runtime.identifier;39      if (devices[identifier] && !this.devices.find(d => d.identifier === identifier)) {...idb-e2e-specs.js
Source:idb-e2e-specs.js  
...33    });34    after(async function () {35      await idb.disconnect();36      try {37        await simctl.shutdownDevice();38      } catch (ign) {}39    });40    // TODO: getting the description returns data in a format that is a pain41    // to parse.42    it.skip('should be able to call connect/disconnect multiple times', async function () {43      await idb.connect();44      await assertDeviceDescription(idb, simctl.udid);45      await idb.disconnect();46    });47  });48  describe('connect/disconnect (non booted device)', function () {49    let idb;50    before(async function () {51      idb = new IDB({52        udid: simctl.udid,53      });54      try {55        await simctl.shutdownDevice();56      } catch (ign) {}57    });58    beforeEach(async function () {59      await idb.connect();60    });61    afterEach(async function () {62      await idb.disconnect();63    });64    it('should be able to call connect multiple times', async function () {65      await idb.connect().should.be.eventually.fulfilled;66    });67    it('should be able to call disconnect multiple times', async function () {68      await idb.disconnect().should.be.eventually.fulfilled;69    });...simulator.js
Source:simulator.js  
...14    // It is necessary to stop the corresponding xcodebuild process before killing15    // the simulator, otherwise it will be automatically restarted16    await resetTestProcesses(udid, true);17    simctl.udid = udid;18    await simctl.shutdownDevice();19  }20  await simKill();21}22async function shutdownSimulator (device) {23  // stop XCTest processes if running to avoid unexpected side effects24  await resetTestProcesses(device.udid, true);25  await device.shutdown();26}27async function deleteDeviceWithRetry (udid) {28  const simctl = new Simctl({udid});29  try {30    await retryInterval(10, 1000, simctl.deleteDevice.bind(simctl));31  } catch (ign) {}32}...device-helpers.js
Source:device-helpers.js  
...21}22async function deleteDevice (simctl) {23  if (simctl?.udid) {24    try {25      await simctl.shutdownDevice();26    } catch (ign) {}27    await retryInterval(10, 1000, async () => await simctl.deleteDevice());28  }29}...Using AI Code Generation
1const wd = require('wd');2const simctl = require('node-simctl');3async function main() {4    const caps = {5    };6    await driver.init(caps);7    await driver.sleep(5000);8    await simctl.shutdownDevice('iPhone 12');9    await driver.quit();10}11main();12{13    "scripts": {14    },15    "dependencies": {16    }17}18{19    "packages": {20        "": {21            "dependencies": {22            }23        },24        "node_modules/appium": {Using AI Code Generation
1const simctl = require('node-simctl');2simctl.shutdownDevice('iPhone 6');3const simctl = require('node-simctl');4simctl.getDevices();5const simctl = require('node-simctl');6simctl.getDevices('iPhone 6');7const simctl = require('node-simctl');8simctl.getDevices('iPhone 6', 'iOS 10.2');9const simctl = require('node-simctl');10simctl.getDevices('iPhone 6', 'iOS 10.2', 'com.apple.CoreSimulator.SimRuntime.iOS-10-2');11const simctl = require('node-simctl');12simctl.getDevices('iPhone 6', 'iOS 10.2', 'com.apple.CoreSimulator.SimRuntime.iOS-10-2', 'Booted');13const simctl = require('node-simctl');14simctl.getDevices('iPhone 6', 'iOS 10.2', 'com.apple.CoreSimulator.SimRuntime.iOS-10-2', 'Booted', 'iPhone 6');15const simctl = require('node-simctl');16simctl.getDevices('iPhone 6', 'iOS 10.2', 'com.apple.CoreSimulator.SimRuntime.iOS-10-2', 'Booted', 'iPhone 6', 'com.apple.CoreSimulator.SimDeviceType.iPhone-6');17const simctl = require('node-simctl');18simctl.getDevices('iPhoneUsing AI Code Generation
1var wd = require('wd');2var assert = require('assert');3var simctl = require('node-simctl');4var appium = require('appium');5var desiredCaps = {6};7var driver = wd.promiseChainRemote('localhost', 4723);8driver.init(desiredCaps).then(function() {9  return driver.sleep(3000);10}).then(function() {11  return driver.quit();12}).then(function() {13  return simctl.shutdownDevice('iPhone 6');14}).done();15var simctl = require('node-simctl');16simctl.getDevices(function(err, devices) {17    console.log(devices);18});19{ 'com.apple.CoreSimulator.SimRuntime.iOS-9-3': 20   [ { udid: 'A7A1E3E6-8D3C-4B2B-9F4C-8A9F4A4A4B4D',21       state: 'Shutdown' },22     { udid: '9F4C-8A9F4A4A4B4D',Using AI Code Generation
1const simctl = require('node-simctl');2const uuid = 'uuid';3const device = simctl.getDevice(uuid);4simctl.shutdownDevice(device.udid);5const simctl = require('node-simctl');6const udid = 'udid';7simctl.shutdownDevice(udid);8const simctl = require('node-simctl');9const device = 'device';10simctl.shutdownDevice(device);11const simctl = require('node-simctl');12const device = 'device';13simctl.shutdownDevice(device.udid);14const simctl = require('node-simctl');15const device = 'device';16simctl.shutdownDevice(device.udid);17const simctl = require('node-simctl');18const device = 'device';19simctl.shutdownDevice(device.udid);20const simctl = require('node-simctl');21const device = 'device';22simctl.shutdownDevice(device.udid);23const simctl = require('node-simctl');24const device = 'device';25simctl.shutdownDevice(device.udid);26const simctl = require('node-simctl');27const device = 'device';28simctl.shutdownDevice(device.udid);29const simctl = require('node-simctl');30const device = 'device';31simctl.shutdownDevice(device.udid);32const simctl = require('node-simctl');33const device = 'device';34simctl.shutdownDevice(device.udid);35const simctl = require('node-simctl');Using AI Code Generation
1const simctl = require('node-simctl');2async shutdownDevice (deviceName, iOSVersion) {3    const udid = await this.getUdid(deviceName, iOSVersion);4    await simExec('shutdown', udid);5  }6}Using AI Code Generation
1var simctl = require('appium-xcuitest-driver').simctl;2var args = {3};4simctl.shutdownDevice(args);5var args = {6};7simctl.shutdownDevice(args).then(function (result) {8  console.log(result);9});10var simctl = require('appium-xcuitest-driver').simctl;11var args = {12};13simctl.deleteDevice(args);14var args = {15};16simctl.deleteDevice(args).then(function (result) {17  console.log(result);18});19var simctl = require('appium-xcuitest-driver').simctl;20var args = {21};22simctl.installApp(args);23var args = {Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.
You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.
Get 100 minutes of automation test minutes FREE!!
