UNPKG

gst-atom-xcuitest-driver

Version:

ATOM driver for iOS using XCUITest for backend

211 lines (191 loc) 7.17 kB
import { getSimulator } from 'appium-ios-simulator'; import Simctl from 'node-simctl'; import { resetTestProcesses } from 'appium-webdriveragent'; import _ from 'lodash'; import log from './logger'; import { util } from 'appium-support'; import { PLATFORM_NAME_IOS } from './desired-caps'; const APPIUM_SIM_PREFIX = 'appiumTest'; /** * Capability set by a user * * @property {string} deviceName - A name for the device * @property {string} platformVersion - The version of iOS to use */ /** * Create a new simulator with `appiumTest-` prefix and return the object. * * @param {object} SimCreationCaps - Capability set by a user. The options available are: * @property {string} platform [iOS] - Platform name in order to specify runtime such as 'iOS', 'tvOS', 'watchOS' * @returns {object} Simulator object associated with the udid passed in. */ async function createSim (caps, platform = PLATFORM_NAME_IOS) { const udid = await new Simctl().createDevice( `${APPIUM_SIM_PREFIX}-${util.uuidV4().toUpperCase()}-${caps.deviceName}`, caps.deviceName, caps.platformVersion, {platform}, ); return await getSimulator(udid, { platform, checkExistence: false, }); } /** * 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) { let appiumTestDevice; for (const device of _.values(await new Simctl().getDevices(opts.platformVersion))) { if (device.name === opts.deviceName) { return await getSimulator(device.udid, { platform: device.platform, checkExistence: false, }); } if (device.name.startsWith(APPIUM_SIM_PREFIX) && device.name.endsWith(opts.deviceName)) { appiumTestDevice = device; // choose the first booted simulator if (device.state === 'Booted') { break; } } } if (appiumTestDevice) { log.warn(`Unable to find device '${opts.deviceName}'. Found '${appiumTestDevice.name}' (udid: '${appiumTestDevice.udid}') instead`); return await getSimulator(appiumTestDevice.udid, { platform: appiumTestDevice.platform, checkExistence: false, }); } return null; } async function shutdownSimulator (device) { // stop XCTest processes if running to avoid unexpected side effects await resetTestProcesses(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) { // fastReset or noReset // 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 (opts.enforceSimulatorShutdown) { await shutdownSimulator(device); } else { try { await device.simctl.terminateApp(opts.bundleId); } catch (err) { log.warn(`Reset: failed to terminate Simulator application with id "${opts.bundleId}"`); } } } 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 { // iOS 8+ does not need basename await device.scrubCustomApp('', 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.`); } } } /** * @typedef {Object} InstallOptions * * @property {?boolean} noReset [false] Whether to disable reset * @property {?boolean} newSimulator [false] Whether the simulator is brand new */ /** * @param {object} device The simulator device object * @param {?string} app The app to the path * @param {string} bundleId The bundle id to ensure * it is already installed and uninstall it * @param {?InstallOptions} opts */ async function installToSimulator (device, app, bundleId, opts = {}) { if (!app) { log.debug('No app path is given. Nothing to install.'); return; } const { noReset = true, newSimulator = false, } = opts; if (!newSimulator && bundleId && 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); } log.debug(`Installing '${app}' on Simulator with UUID '${device.udid}'...`); try { await device.installApp(app); } catch (e) { // it sometimes fails on Xcode 10 because of a race condition log.info(`Got an error on '${app}' install: ${e.message}`); log.info('Retrying application install'); await device.installApp(app); } log.debug('The app has been installed successfully.'); } async function shutdownOtherSimulators (currentDevice) { const simctl = new Simctl(); const allDevices = _.flatMap(_.values(await simctl.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 ${util.pluralize('Simulator', otherBootedDevices.length)}.` + `Shutting 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 resetTestProcesses(udid, true); simctl.udid = udid; await simctl.shutdownDevice(); } } export { createSim, getExistingSim, runSimulatorReset, installToSimulator, shutdownSimulator, shutdownOtherSimulators };