UNPKG

kuben-appium-xcuitest-driver

Version:

Appium driver for iOS using XCUITest for backend

193 lines (175 loc) 7.25 kB
import path from 'path'; import { getSimulator } from 'kuben-appium-ios-simulator'; import { createDevice, getDevices, terminate, shutdown } from 'kuben-node-simctl'; import { resetXCTestProcesses } from './utils'; import _ from 'lodash'; import log from './logger'; import { tempDir, fs, mkdirp } from 'appium-support'; const INSTALL_DAEMON_CACHE = 'com.apple.mobile.installd.staging'; /** * Create a new simulator with `appiumTest-` prefix and return the object. * * @param {object} caps - Capability set by a user. The options available are: * - `deviceName` - a name for the device * - `platformVersion` - the version of iOS to use * @returns {object} Simulator object associated with the udid passed in. */ async function createSim (caps) { const appiumTestDeviceName = `appiumTest-${caps.deviceName}`; const udid = await createDevice(appiumTestDeviceName, caps.deviceName, caps.platformVersion); return await getSimulator(udid); } /** * Get a simulator which is already running. * * @param {object} opts - Capability set by a user. The options available are: * - `deviceName` - a name for the device * - `platformVersion` - the version of iOS to use * @returns {?object} Simulator object associated with the udid passed in. Or null if no device is running. */ async function getExistingSim (opts) { const devices = await getDevices(opts.platformVersion); const appiumTestDeviceName = `appiumTest-${opts.deviceName}`; let appiumTestDevice; for (const device of _.values(devices)) { if (device.name === opts.deviceName) { return await getSimulator(device.udid); } if (device.name === appiumTestDeviceName) { appiumTestDevice = device; } } if (appiumTestDevice) { log.warn(`Unable to find device '${opts.deviceName}'. Found '${appiumTestDevice.name}' (udid: '${appiumTestDevice.udid}') instead`); return await getSimulator(appiumTestDevice.udid); } return null; } async function shutdownSimulator (device) { // stop XCTest processes if running to avoid unexpected side effects await resetXCTestProcesses(device.udid, true); await device.shutdown(); } async function runSimulatorReset (device, opts) { if (opts.noReset && !opts.fullReset) { // noReset === true && fullReset === false log.debug('Reset: noReset is on. Leaving simulator as is'); return; } if (!device) { log.debug('Reset: no device available. Skipping'); return; } if (opts.fullReset) { log.debug('Reset: fullReset is on. Cleaning simulator'); await shutdownSimulator(device); let isKeychainsBackupSuccessful = false; if (opts.keychainsExcludePatterns || opts.keepKeyChains) { isKeychainsBackupSuccessful = await device.backupKeychains(); } await device.clean(); if (isKeychainsBackupSuccessful) { await device.restoreKeychains(opts.keychainsExcludePatterns || []); log.info(`Successfully restored keychains after full reset`); } else if (opts.keychainsExcludePatterns || opts.keepKeyChains) { log.warn('Cannot restore keychains after full reset, because ' + 'the backup operation did not succeed'); } } else if (opts.bundleId) { // Terminate the app under test if it is still running on Simulator // Termination is not needed if Simulator is not running if (await device.isRunning()) { if (device.xcodeVersion.major >= 8) { try { await terminate(device.udid, opts.bundleId); } catch (err) { log.warn(`Reset: failed to terminate Simulator application with id "${opts.bundleId}"`); } } else { await shutdownSimulator(device); } } if (opts.app) { log.info('Not scrubbing third party app in anticipation of uninstall'); return; } const isSafari = (opts.browserName || '').toLowerCase() === 'safari'; try { if (isSafari) { await device.cleanSafari(); } else { await device.scrubCustomApp(path.basename(opts.app), opts.bundleId); } } catch (err) { log.warn(err.message); log.warn(`Reset: could not scrub ${isSafari ? 'Safari browser' : 'application with id "' + opts.bundleId + '"'}. Leaving as is.`); } } } async function installToSimulator (device, app, bundleId, noReset = true) { if (!app) { log.debug('No app path is given. Nothing to install.'); return; } if (bundleId) { if (await device.isAppInstalled(bundleId)) { if (noReset) { log.debug(`App '${bundleId}' is already installed. No need to reinstall.`); return; } log.debug(`Reset requested. Removing app with id '${bundleId}' from the device`); await device.removeApp(bundleId); } } const installdCacheRoot = path.resolve(device.getDir(), 'Library', 'Caches', INSTALL_DAEMON_CACHE); let tmpRoot = null; if (await fs.exists(installdCacheRoot)) { // Cleanup of installd cache helps to save disk space while running multiple tests // without restarting the Simulator: https://github.com/appium/appium/issues/9410 tmpRoot = await tempDir.openDir(); log.debug('Cleaning installd cache to save the disk space'); await fs.mv(installdCacheRoot, path.resolve(tmpRoot, INSTALL_DAEMON_CACHE), {mkdirp: true}); await mkdirp(installdCacheRoot); } log.debug(`Installing '${app}' on Simulator with UUID '${device.udid}'...`); try { try { await device.installApp(app); } catch (e) { // on Xcode 10 sometimes this is too fast and it fails log.info(`Got an error on '${app}' install: ${e.message}`); if (e.message.includes('domain=MIInstallerErrorDomain, code=35') && tmpRoot) { // https://github.com/appium/appium/issues/11350 log.info(`installd requires the cache to be available in order to install '${app}'. ` + `Restoring the cache`); await fs.rimraf(installdCacheRoot); await fs.mv(path.resolve(tmpRoot, INSTALL_DAEMON_CACHE), installdCacheRoot, {mkdirp: true}); } log.info('Retrying application install'); await device.installApp(app); } log.debug('The app has been installed successfully.'); } finally { if (tmpRoot && await fs.exists(tmpRoot)) { await fs.rimraf(tmpRoot); } } } async function shutdownOtherSimulators (currentDevice) { const allDevices = _.flatMap(_.values(await getDevices())); const otherBootedDevices = allDevices.filter((device) => device.udid !== currentDevice.udid && device.state === 'Booted'); if (_.isEmpty(otherBootedDevices)) { log.info('No other running simulators have been detected'); return; } log.info(`Detected ${otherBootedDevices.length} other running Simulator${otherBootedDevices.length === 1 ? '' : 's'}.` + `Shutting ${otherBootedDevices.length === 1 ? 'it' : 'them'} down...`); for (const {udid} of otherBootedDevices) { // It is necessary to stop the corresponding xcodebuild process before killing // the simulator, otherwise it will be automatically restarted await resetXCTestProcesses(udid, true); await shutdown(udid); } } export { createSim, getExistingSim, runSimulatorReset, installToSimulator, shutdownSimulator, shutdownOtherSimulators };