diff --git a/lib/geolocation.js b/lib/geolocation.js new file mode 100644 index 00000000..b9d9b32c --- /dev/null +++ b/lib/geolocation.js @@ -0,0 +1,97 @@ +import _ from 'lodash'; +import log from './logger'; +import { exec } from 'teen_process'; +import { fs } from 'appium-support'; + +const LYFT_SET_LOCATION = 'set-simulator-location'; + +/** + * Set custom geolocation parameters for the given Simulator using LYFT_SET_LOCATION. + * + * @param {string} udid - The udid to set the given geolocation + * @param {string|number} latitude - The latitude value, which is going to be entered + * into the corresponding edit field, for example '39,0006'. + * @param {string|number} longitude - The longitude value, which is going to be entered + * into the corresponding edit field, for example '19,0068'. + * @throws {Error} If it failed to set the location + */ +async function setLocationWithLyft (udid, latitude, longitude) { + try { + await fs.which(LYFT_SET_LOCATION); + } catch (e) { + throw new Error(`'${LYFT_SET_LOCATION}' binary has not been found in your PATH. ` + + 'Please install it as "brew install lyft/formulae/set-simulator-location" by brew or ' + + 'read https://github.com/lyft/set-simulator-location to set the binary by manual to ' + + 'be able to set geolocation by the library.'); + } + + try { + await exec(LYFT_SET_LOCATION, [ + '-c', latitude, longitude, + '-u', udid + ]); + } catch (e) { + throw new Error(`Failed to set geolocation with '${LYFT_SET_LOCATION}'. ` + + `Original error: ${e.stderr || e.message}`); + } +} + +/** + * Set custom geolocation parameters for the given Simulator using idb. + * + * @param {Object} idb - The IDB instance + * @param {string|number} latitude - The latitude value, which is going to be entered + * into the corresponding edit field, for example '39,0006'. + * @param {string|number} longitude - The longitude value, which is going to be entered + * into the corresponding edit field, for example '19,0068'. + * @throws {Error} If it failed to set the location + */ +async function setLocationWithIdb (idb, latitude, longitude) { + if (idb) { + try { + await idb.setLocation(latitude, longitude); + } catch (e) { + throw new Error(`Failed to set geolocation with idb. Original error: ${e.stderr || e.message}`); + } + } + throw new Error('Failed to set geolocation with idb because it is not installed'); +} + + +/** + * Set custom geolocation parameters for the given Simulator using AppleScript + * + * @param {Object} sim - The SimulatorXcode object + * @param {string|number} latitude - The latitude value, which is going to be entered + * into the corresponding edit field, for example '39,0006'. + * @param {string|number} longitude - The longitude value, which is going to be entered + * into the corresponding edit field, for example '19,0068'. + * @throws {Error} If it failed to set the location + */ +async function setLocationWithAppleScript (sim, latitude, longitude) { + const output = await sim.executeUIClientScript(` + tell application "System Events" + tell process "Simulator" + set featureName to "Custom Location" + set dstMenuItem to menu item (featureName & "…") of menu 1 of menu item "Location" of menu 1 of menu bar item "Debug" of menu bar 1 + click dstMenuItem + delay 1 + set value of text field 1 of window featureName to ${latitude} as string + delay 0.5 + set value of text field 2 of window featureName to ${longitude} as string + delay 0.5 + click button "OK" of window featureName + delay 0.5 + set isInvisible to (not (exists (window featureName))) + end tell + end tell + `); + log.debug(`Geolocation parameters dialog accepted: ${output}`); + if (_.trim(output) !== 'true') { + throw new Error(`Failed to set geolocation with AppleScript. Original error: ${output}`); + } +} + +export { + setLocationWithLyft, setLocationWithIdb, setLocationWithAppleScript +}; diff --git a/lib/simulator-xcode-8.js b/lib/simulator-xcode-8.js index 3b2d095c..0c661ebb 100644 --- a/lib/simulator-xcode-8.js +++ b/lib/simulator-xcode-8.js @@ -6,6 +6,10 @@ import { waitForCondition } from 'asyncbox'; import { exec } from 'teen_process'; import { getAppContainer, openUrl as simctlOpenUrl, terminate, appInfo, spawn, startBootMonitor } from 'node-simctl'; +import { + setLocationWithLyft, + setLocationWithIdb, + setLocationWithAppleScript } from './geolocation'; // these sims are sloooooooow const STARTUP_TIMEOUT = 120 * 1000; @@ -282,30 +286,23 @@ class SimulatorXcode8 extends SimulatorXcode7 { * @throws {Error} If there was an error while setting the location */ async setGeolocation (latitude, longitude) { - if (this.idb) { - await this.idb.setLocation(latitude, longitude); - return true; + const locationSetters = [ + async () => await setLocationWithLyft(this.udid, latitude, longitude), + async () => await setLocationWithIdb(this.idb, latitude, longitude), + async () => await setLocationWithAppleScript(this, latitude, longitude) + ]; + + let lastError; + for (const setter of locationSetters) { + try { + await setter(); + return true; + } catch (e) { + log.info(e.message); + lastError = e; + } } - log.warn(`Cannot set geolocation with idb, because it is not installed. Defaulting to AppleScript`); - const output = await this.executeUIClientScript(` - tell application "System Events" - tell process "Simulator" - set featureName to "Custom Location" - set dstMenuItem to menu item (featureName & "…") of menu 1 of menu item "Location" of menu 1 of menu bar item "Debug" of menu bar 1 - click dstMenuItem - delay 1 - set value of text field 1 of window featureName to ${latitude} as string - delay 0.5 - set value of text field 2 of window featureName to ${longitude} as string - delay 0.5 - click button "OK" of window featureName - delay 0.5 - set isInvisible to (not (exists (window featureName))) - end tell - end tell - `); - log.debug(`Geolocation parameters dialog accepted: ${output}`); - return _.isString(output) && output.trim() === 'true'; + throw lastError; } }