appium-ios-simulator
Version:
iOS Simulator interface for Appium.
212 lines • 8.6 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MIN_SUPPORTED_XCODE_VERSION = exports.SIMULATOR_APP_NAME = exports.MOBILE_SAFARI_BUNDLE_ID = exports.SAFARI_STARTUP_TIMEOUT_MS = void 0;
exports.killAllSimulators = killAllSimulators;
exports.getSimulatorInfo = getSimulatorInfo;
exports.simExists = simExists;
exports.getDeveloperRoot = getDeveloperRoot;
exports.assertXcodeVersion = assertXcodeVersion;
exports.getDevices = getDevices;
const logger_1 = require("./logger");
const lodash_1 = __importDefault(require("lodash"));
const teen_process_1 = require("teen_process");
const asyncbox_1 = require("asyncbox");
const appium_xcode_1 = require("appium-xcode");
const path_1 = __importDefault(require("path"));
const node_simctl_1 = require("node-simctl");
// it's a hack needed to stub getDevices in tests
const utilsModule = __importStar(require("./utils"));
const DEFAULT_SIM_SHUTDOWN_TIMEOUT_MS = 30000;
exports.SAFARI_STARTUP_TIMEOUT_MS = 25 * 1000;
exports.MOBILE_SAFARI_BUNDLE_ID = 'com.apple.mobilesafari';
exports.SIMULATOR_APP_NAME = 'Simulator.app';
exports.MIN_SUPPORTED_XCODE_VERSION = 14;
/**
* @param appName - The application name to kill.
* @param forceKill - Whether to force kill the process.
* @returns Promise that resolves to 0 on success.
*/
async function pkill(appName, forceKill = false) {
const args = forceKill ? ['-9'] : [];
args.push('-x', appName);
try {
await (0, teen_process_1.exec)('pkill', args);
return 0;
}
catch (err) {
// pgrep/pkill exit codes:
// 0 One or more processes were matched.
// 1 No processes were matched.
// 2 Invalid options were specified on the command line.
// 3 An internal error occurred.
if (!lodash_1.default.isUndefined(err.code)) {
throw new Error(`Cannot forcefully terminate ${appName}. pkill error code: ${err.code}`);
}
logger_1.log.error(`Received unexpected error while trying to kill ${appName}: ${err.message}`);
throw err;
}
}
/**
* @param timeout - Timeout in milliseconds (default: DEFAULT_SIM_SHUTDOWN_TIMEOUT_MS).
* @returns Promise that resolves when all simulators are killed.
*/
async function killAllSimulators(timeout = DEFAULT_SIM_SHUTDOWN_TIMEOUT_MS) {
logger_1.log.debug('Killing all iOS Simulators');
const xcodeVersion = await (0, appium_xcode_1.getVersion)(true);
if (lodash_1.default.isString(xcodeVersion)) {
return;
}
const appName = path_1.default.parse(exports.SIMULATOR_APP_NAME).name;
const version = xcodeVersion;
// later versions are slower to close
timeout = timeout * (version.major >= 8 ? 2 : 1);
try {
await (0, teen_process_1.exec)('xcrun', ['simctl', 'shutdown', version.major > 8 ? 'all' : 'booted'], { timeout });
}
catch { }
const pids = [];
try {
const { stdout } = await (0, teen_process_1.exec)('pgrep', ['-f', `${appName}.app/Contents/MacOS/`]);
if (stdout.trim()) {
pids.push(...(stdout.trim().split(/\s+/)));
}
}
catch (e) {
if (e.code === 1) {
logger_1.log.debug(`${appName} is not running. Continuing...`);
return;
}
if (lodash_1.default.isEmpty(pids)) {
logger_1.log.warn(`pgrep error ${e.code} while detecting whether ${appName} is running. Trying to kill anyway.`);
}
}
if (!lodash_1.default.isEmpty(pids)) {
logger_1.log.debug(`Killing processes: ${pids.join(', ')}`);
try {
await (0, teen_process_1.exec)('kill', ['-9', ...(pids.map((pid) => `${pid}`))]);
}
catch { }
}
logger_1.log.debug(`Using pkill to kill application: ${appName}`);
try {
await pkill(appName, true);
}
catch { }
// wait for all the devices to be shutdown before Continuing
// but only print out the failed ones when they are actually fully failed
let remainingDevices = [];
async function allSimsAreDown() {
remainingDevices = [];
const devicesRecord = await utilsModule.getDevices();
const devices = lodash_1.default.flatten(lodash_1.default.values(devicesRecord));
return lodash_1.default.every(devices, (sim) => {
const state = sim.state.toLowerCase();
const done = ['shutdown', 'unavailable', 'disconnected'].includes(state);
if (!done) {
remainingDevices.push(`${sim.name} (${sim.sdk}, udid: ${sim.udid}) is still in state '${state}'`);
}
return done;
});
}
try {
await (0, asyncbox_1.waitForCondition)(allSimsAreDown, {
waitMs: timeout,
intervalMs: 200
});
}
catch (err) {
if (remainingDevices.length > 0) {
logger_1.log.warn(`The following devices are still not in the correct state after ${timeout} ms:`);
for (const device of remainingDevices) {
logger_1.log.warn(` ${device}`);
}
}
throw err;
}
}
/**
* @param udid - The simulator UDID.
* @param opts - Options including devicesSetPath.
* @returns Promise that resolves to simulator info or undefined if not found.
*/
async function getSimulatorInfo(udid, opts = {}) {
const { devicesSetPath } = opts;
// see the README for github.com/appium/node-simctl for example output of getDevices()
const devices = lodash_1.default.toPairs(await utilsModule.getDevices({ devicesSetPath }))
.map((pair) => pair[1])
.reduce((a, b) => a.concat(b), []);
return lodash_1.default.find(devices, (sim) => sim.udid === udid);
}
/**
* @param udid - The simulator UDID.
* @returns Promise that resolves to true if simulator exists, false otherwise.
*/
async function simExists(udid) {
return !!(await getSimulatorInfo(udid));
}
/**
* @returns Promise that resolves to the developer root path.
*/
async function getDeveloperRoot() {
const { stdout } = await (0, teen_process_1.exec)('xcode-select', ['-p']);
return stdout.trim();
}
/**
* Asserts that the Xcode version meets the minimum supported version requirement.
*
* @template V - The Xcode version type.
* @param xcodeVersion - The Xcode version to check.
* @returns The same Xcode version if it meets the requirement.
* @throws {Error} If the Xcode version is below the minimum supported version.
*/
function assertXcodeVersion(xcodeVersion) {
if (xcodeVersion.major < exports.MIN_SUPPORTED_XCODE_VERSION) {
throw new Error(`Tried to use an iOS simulator with xcode version ${xcodeVersion.versionString} but only Xcode version ` +
`${exports.MIN_SUPPORTED_XCODE_VERSION} and up are supported`);
}
return xcodeVersion;
}
/**
* @param simctlOpts - Optional simctl options
* @returns Promise that resolves to a record of devices grouped by SDK version
*/
async function getDevices(simctlOpts) {
return await new node_simctl_1.Simctl(simctlOpts).getDevices();
}
//# sourceMappingURL=utils.js.map