How to use simctl.shutdownDevice method in Appium Xcuitest Driver

Best JavaScript code snippet using appium-xcuitest-driver

simulator-xcode-6.js

Source:simulator-xcode-6.js Github

copy

Full Screen

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

Full Screen

Full Screen

simctl-e2e-specs.js

Source:simctl-e2e-specs.js Github

copy

Full Screen

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

Full Screen

Full Screen

simulator-management.js

Source:simulator-management.js Github

copy

Full Screen

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

Full Screen

Full Screen

index.js

Source:index.js Github

copy

Full Screen

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

Full Screen

Full Screen

idb-e2e-specs.js

Source:idb-e2e-specs.js Github

copy

Full Screen

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

Full Screen

Full Screen

simulator.js

Source:simulator.js Github

copy

Full Screen

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

Full Screen

Full Screen

device-helpers.js

Source:device-helpers.js Github

copy

Full Screen

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

Full Screen

Full Screen

Using AI Code Generation

copy

Full Screen

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": {

Full Screen

Using AI Code Generation

copy

Full Screen

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('iPhone

Full Screen

Using AI Code Generation

copy

Full Screen

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

Full Screen

Using AI Code Generation

copy

Full Screen

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

Full Screen

Using AI Code Generation

copy

Full Screen

1const simctl = require('node-simctl');2async shutdownDevice (deviceName, iOSVersion) {3 const udid = await this.getUdid(deviceName, iOSVersion);4 await simExec('shutdown', udid);5 }6}

Full Screen

Using AI Code Generation

copy

Full Screen

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

Full Screen

Automation Testing Tutorials

Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.

LambdaTest Learning Hubs:

YouTube

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

Run Appium Xcuitest Driver automation tests on LambdaTest cloud grid

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

Sign up Free
_

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful