Skip to content

Commit

Permalink
feat: Add simulatorDevicesSetPath capability (#1242)
Browse files Browse the repository at this point in the history
  • Loading branch information
mykola-mokhnach authored Sep 28, 2020
1 parent cc63712 commit 1162fe6
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 19 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `{"<bundleId1>": {"<serviceName1>": "<serviceStatus1>", ...}, ...}` 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`|
Expand Down
3 changes: 3 additions & 0 deletions lib/desired-caps.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ let desiredCapConstraints = _.defaults({
simulatorPasteboardAutomaticSync: {
isString: true
},
simulatorDevicesSetPath: {
isString: true
},
calendarAccessAuthorized: {
isBoolean: true
},
Expand Down
15 changes: 13 additions & 2 deletions lib/driver.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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}'`);
Expand Down Expand Up @@ -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;
Expand Down
45 changes: 31 additions & 14 deletions lib/simulator-management.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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') {
Expand All @@ -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;
Expand Down Expand Up @@ -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 = {}) {
Expand Down Expand Up @@ -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)) {
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down

0 comments on commit 1162fe6

Please sign in to comment.