diff --git a/README.md b/README.md index a46909ae6..dd463141d 100644 --- a/README.md +++ b/README.md @@ -150,12 +150,13 @@ Differences are noted here: |`permissions`| Allows to set permissions for the specified application bundle on Simulator only. The capability value is expected to be a valid JSON string with `{"": {"": "", ...}, ...}` format. Since Xcode SDK 11.4 Apple provides native APIs to interact with application settings. Check the output of `xcrun simctl privacy booted` command to get the list of available permission names. Use `yes`, `no` and `unset` as values in order to `grant`, `revoke` or `reset` the corresponding permission. Below Xcode SDK 11.4 it is required that `applesimutils` package is installed and available in PATH. The list of available service names and statuses can be found at https://github.com/wix/AppleSimulatorUtils. | e. g. `{"com.apple.mobilecal": {"calendar": "YES"}}` | |`iosSimulatorLogsPredicate`|Set the `--predicate` flag in the ios simulator logs|e.g.: `'process != "locationd" AND process != "DTServiceHub"' AND process != "mobileassetd"`| |`simulatorPasteboardAutomaticSync`| Handle the `-PasteboardAutomaticSync` flag when simulator process launches. It could improve launching simulator performance not to sync pasteboard with the system when this value is `off`. `on` forces the flag enabled. `system` does not provide the flag to the launching command. `on`, `off`, or `system` is available. They are case insensitive. Defaults to `off` | e.g. `system` | +|`simulatorDevicesSetPath`| This capability allows to set an alternative path to the simulator devices set in case you have multiple sets deployed on your local system. Such feature could be useful if you, for example, would like to save disk space on the main system volume. | e.g. `/MyVolume/Devices` | ### General capabilities: |Capability|Description|Values| |----------|-----------|------| -|`resetOnSessionStartOnly`|Whether to perform reset on test session finish (`false`) or not (`true`). Keeping this variable set to `true` and Simulator running (the default behaviour since version 1.6.4) may significantly shorten the duratiuon of test session initialization.|Either `true` or `false`. Defaults to `true`| +|`resetOnSessionStartOnly`|Whether to perform reset on test session finish (`false`) or not (`true`). Keeping this variable set to `true` and Simulator running (the default behaviour since version 1.6.4) may significantly shorten the duration of test session initialization.|Either `true` or `false`. Defaults to `true`| |`commandTimeouts`|Custom timeout(s) in milliseconds for WDA backend commands execution. This might be useful if WDA backend freezes unexpectedly or requires too much time to fail and blocks automated test execution. The value is expected to be of type string and can either contain max milliseconds to wait for each WDA command to be executed before terminating the session forcefully or a valid JSON string, where keys are internal Appium command names (you can find these in logs, look for "Executing command 'command_name'" records) and values are timeouts in milliseconds. You can also set the 'default' key to assign the timeout for all other commands not explicitly enumerated as JSON keys.|`'120000'`, `'{"findElement": 40000, "findElements": 40000, "setValue": 20000, "default": 120000}'`| |`maxTypingFrequency`|Maximum frequency of keystrokes for typing and clear. If your tests are failing because of typing errors, you may want to adjust this. Defaults to 60 keystrokes per minute.|e.g., `30`| |`simpleIsVisibleCheck`|Use native methods for determining visibility of elements. In some cases this takes a long time. Setting this capability to `false` will cause the system to use the position and size of elements to make sure they are visible on the screen. This can, however, lead to false results in some situations. Defaults to `false`, except iOS 9.3, where it defaults to `true`.|e.g., `true`, `false`| diff --git a/lib/desired-caps.js b/lib/desired-caps.js index 69e2cfe54..62604d711 100644 --- a/lib/desired-caps.js +++ b/lib/desired-caps.js @@ -103,6 +103,9 @@ let desiredCapConstraints = _.defaults({ simulatorPasteboardAutomaticSync: { isString: true }, + simulatorDevicesSetPath: { + isString: true + }, calendarAccessAuthorized: { isBoolean: true }, diff --git a/lib/driver.js b/lib/driver.js index 08e71f1c6..93325baa2 100644 --- a/lib/driver.js +++ b/lib/driver.js @@ -279,6 +279,15 @@ class XCUITestDriver extends BaseDriver { this.opts.udid = udid; this.opts.realDevice = realDevice; + if (this.opts.simulatorDevicesSetPath) { + if (realDevice) { + log.info(`The 'simulatorDevicesSetPath' capability is only supported for Simulator devices`); + } else { + log.info(`Setting simulator devices set path to '${this.opts.simulatorDevicesSetPath}'`); + this.opts.device.devicesSetPath = this.opts.simulatorDevicesSetPath; + } + } + // at this point if there is no platformVersion, get it from the device if (!this.opts.platformVersion && this.opts.device) { this.opts.platformVersion = await this.opts.device.getPlatformVersion(); @@ -801,7 +810,9 @@ class XCUITestDriver extends BaseDriver { // check for a particular simulator log.debug(`No real device with udid '${this.opts.udid}'. Looking for simulator`); try { - const device = await getSimulator(this.opts.udid); + const device = await getSimulator(this.opts.udid, { + devicesSetPath: this.opts.simulatorDevicesSetPath, + }); return {device, realDevice: false, udid: this.opts.udid}; } catch (ign) { throw new Error(`Unknown device or simulator UDID: '${this.opts.udid}'`); @@ -878,7 +889,7 @@ class XCUITestDriver extends BaseDriver { const platformName = this.isTvOS() ? PLATFORM_NAME_TVOS : PLATFORM_NAME_IOS; // create sim for caps - let sim = await createSim(this.opts, platformName); + const sim = await createSim(this.opts, platformName); log.info(`Created simulator with udid '${sim.udid}'.`); return sim; diff --git a/lib/simulator-management.js b/lib/simulator-management.js index 3d2aa97cf..15de34bf1 100644 --- a/lib/simulator-management.js +++ b/lib/simulator-management.js @@ -23,7 +23,8 @@ const APPIUM_SIM_PREFIX = 'appiumTest'; * @returns {object} Simulator object associated with the udid passed in. */ async function createSim (caps, platform = PLATFORM_NAME_IOS) { - const udid = await new Simctl().createDevice( + const devicesSetPath = caps.simulatorDevicesSetPath; + const udid = await new Simctl({devicesSetPath}).createDevice( `${APPIUM_SIM_PREFIX}-${util.uuidV4().toUpperCase()}-${caps.deviceName}`, caps.deviceName, caps.platformVersion, @@ -32,29 +33,42 @@ async function createSim (caps, platform = PLATFORM_NAME_IOS) { return await getSimulator(udid, { platform, checkExistence: false, + devicesSetPath, }); } +/** + * @typedef {Object} SimulatorLookupOptions + * @property {!string} deviceName - The name of the device to lookup + * @property {!string} platformVersion - The platform version string + * @property {?string} simulatorDevicesSetPath - The full path to the simulator devices set + */ + /** * 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. + * @param {?SimulatorLookupOptions} opts + * @returns {?Simulator} The matched Simulator instance or `null` if no matching device is found. */ -async function getExistingSim (opts) { - let appiumTestDevice; +async function getExistingSim (opts = {}) { + const { + platformVersion, + deviceName, + simulatorDevicesSetPath: devicesSetPath, + } = opts; - for (const device of _.values(await new Simctl().getDevices(opts.platformVersion))) { - if (device.name === opts.deviceName) { + let appiumTestDevice; + const simctl = new Simctl({devicesSetPath}); + for (const device of _.values(await simctl.getDevices(platformVersion))) { + if (device.name === deviceName) { return await getSimulator(device.udid, { platform: device.platform, checkExistence: false, + devicesSetPath, }); } - if (device.name.startsWith(APPIUM_SIM_PREFIX) && device.name.endsWith(opts.deviceName)) { + if (device.name.startsWith(APPIUM_SIM_PREFIX) && device.name.endsWith(deviceName)) { appiumTestDevice = device; // choose the first booted simulator if (device.state === 'Booted') { @@ -64,10 +78,12 @@ async function getExistingSim (opts) { } if (appiumTestDevice) { - log.warn(`Unable to find device '${opts.deviceName}'. Found '${appiumTestDevice.name}' (udid: '${appiumTestDevice.udid}') instead`); + log.warn(`Unable to find device '${deviceName}'. ` + + `Found '${appiumTestDevice.name}' (udid: '${appiumTestDevice.udid}') instead`); return await getSimulator(appiumTestDevice.udid, { platform: appiumTestDevice.platform, checkExistence: false, + devicesSetPath, }); } return null; @@ -151,8 +167,7 @@ async function runSimulatorReset (device, opts) { /** * @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 {string} bundleId The bundle id to ensure it is already installed and uninstall it * @param {?InstallOptions} opts */ async function installToSimulator (device, app, bundleId, opts = {}) { @@ -188,7 +203,9 @@ async function installToSimulator (device, app, bundleId, opts = {}) { } async function shutdownOtherSimulators (currentDevice) { - const simctl = new Simctl(); + const simctl = new Simctl({ + devicesSetPath: currentDevice.devicesSetPath + }); const allDevices = _.flatMap(_.values(await simctl.getDevices())); const otherBootedDevices = allDevices.filter((device) => device.udid !== currentDevice.udid && device.state === 'Booted'); if (_.isEmpty(otherBootedDevices)) { diff --git a/package.json b/package.json index f0bf754ff..8b78aa9d8 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "appium-idb": "^0.5.0", "appium-ios-device": "^1.5.0", "appium-ios-driver": "^4.8.0", - "appium-ios-simulator": "^3.24.0", + "appium-ios-simulator": "^3.25.1", "appium-remote-debugger": "^8.13.0", "appium-support": "^2.47.1", "appium-webdriveragent": "^2.22.2", @@ -60,7 +60,7 @@ "lodash": "^4.17.10", "moment": "^2.24.0", "moment-timezone": "0.5.28", - "node-simctl": "^6.1.0", + "node-simctl": "^6.4.0", "portscanner": "2.2.0", "semver": "^7.0.0", "source-map-support": "^0.5.5",