appium-xcuitest-driver
Version:
Appium driver for iOS using XCUITest for backend
134 lines (118 loc) • 5.28 kB
JavaScript
import _ from 'lodash';
import {buildSafariPreferences} from './app-utils';
import {utilities} from 'appium-ios-device';
const DEFAULT_APP_INSTALLATION_TIMEOUT_MS = 8 * 60 * 1000;
/**
* @typedef {Object} InstallOptions
*
* @property {boolean} [skipUninstall] Whether to skip app uninstall before installing it
* @property {number} [timeout=480000] App install timeout
* @property {boolean} [shouldEnforceUninstall] Whether to enforce the app uninstallation. e.g. fullReset, or enforceAppInstall is true
*/
/**
* @this {import('./driver').XCUITestDriver}
* @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={}]
*/
export async function installToRealDevice(app, bundleId, opts = {}) {
const device = /** @type {RealDevice} */ (this.device);
if (!device.udid || !app || !bundleId) {
this.log.debug('No device id, app or bundle id, not installing to real device.');
return;
}
const {
skipUninstall,
timeout = DEFAULT_APP_INSTALLATION_TIMEOUT_MS,
} = opts;
if (!skipUninstall) {
this.log.info(`Reset requested. Removing app with id '${bundleId}' from the device`);
await device.remove(bundleId);
}
this.log.debug(`Installing '${app}' on the device with UUID '${device.udid}'`);
try {
await device.install(app, bundleId, {
timeoutMs: timeout,
});
this.log.debug('The app has been installed successfully.');
} catch (e) {
// Want to clarify the device's application installation state in this situation.
if (!skipUninstall || !e.message.includes('MismatchedApplicationIdentifierEntitlement')) {
// Other error cases that could not be recoverable by here.
// Exact error will be in the log.
// We cannot recover 'ApplicationVerificationFailed' situation since this reason is clearly the app's provisioning profile was invalid.
// [XCUITest] Error installing app '/path/to.app': Unexpected data: {"Error":"ApplicationVerificationFailed","ErrorDetail":-402620395,"ErrorDescription":"Failed to verify code signature of /path/to.app : 0xe8008015 (A valid provisioning profile for this executable was not found.)"}
throw e;
}
// If the error was by below error case, we could recover the situation
// by uninstalling the device's app bundle id explicitly regard less the app exists on the device or not (e.g. offload app).
// [XCUITest] Error installing app '/path/to.app': Unexpected data: {"Error":"MismatchedApplicationIdentifierEntitlement","ErrorDescription":"Upgrade's application-identifier entitlement string (TEAM_ID.com.kazucocoa.example) does not match installed application's application-identifier string (ANOTHER_TEAM_ID.com.kazucocoa.example); rejecting upgrade."}
this.log.info(`The application identified by '${bundleId}' cannot be installed because it might ` +
`be already cached on the device, probably with a different signature. ` +
`Will try to remove it and install a new copy. Original error: ${e.message}`);
await device.remove(bundleId);
await device.install(app, bundleId, {
timeoutMs: timeout,
});
this.log.debug('The app has been installed after one retrial.');
}
}
/**
* @this {import('./driver').XCUITestDriver}
* @returns {Promise<void>}
*/
export async function runRealDeviceReset() {
if (!this.opts.noReset || this.opts.fullReset) {
this.log.debug('Reset: running ios real device reset flow');
if (!this.opts.noReset) {
await /** @type {RealDevice} */ (this.device).reset(this.opts);
}
} else {
this.log.debug('Reset: fullReset not set. Leaving as is');
}
}
/**
* Configures Safari startup options based on the given session capabilities.
*
* !!! This method mutates driver options.
*
* @this {import('./driver').XCUITestDriver}
* @return {boolean} true if process arguments have been modified
*/
export function applySafariStartupArgs() {
const prefs = buildSafariPreferences(this.opts);
if (_.isEmpty(prefs)) {
return false;
}
const args = _.toPairs(prefs)
.flatMap(([key, value]) => [_.startsWith(key, '-') ? key : `-${key}`, String(value)]);
this.log.debug(`Generated Safari command line arguments: ${args.join(' ')}`);
if (_.isPlainObject(this.opts.processArguments)) {
this.opts.processArguments.args = [...(this.opts.processArguments.args ?? []), ...args];
} else {
this.opts.processArguments = {args};
}
return true;
}
/**
* @this {XCUITestDriver}
* @returns {Promise<string>}
*/
export async function detectUdid() {
this.log.debug('Auto-detecting real device udid...');
const udids = await utilities.getConnectedDevices();
if (_.isEmpty(udids)) {
throw new Error('No real devices are connected to the host');
}
const udid = _.last(udids);
if (udids.length > 1) {
this.log.info(`Multiple devices found: ${udids.join(', ')}`);
this.log.info(`Choosing '${udid}'. Consider settings the 'udid' capability if another device must be selected`);
}
this.log.debug(`Detected real device udid: '${udid}'`);
return udid;
}
/**
* @typedef {import('./real-device').RealDevice} RealDevice}
* @typedef {import('./driver').XCUITestDriver} XCUITestDriver
*/