appium-xcuitest-driver
Version:
Appium driver for iOS using XCUITest for backend
122 lines • 6.34 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getGeoLocation = getGeoLocation;
exports.setGeoLocation = setGeoLocation;
exports.mobileResetLocationService = mobileResetLocationService;
const driver_1 = require("appium/driver");
const simulate_location_client_1 = require("../device/simulate-location-client");
const support_1 = require("appium/support");
const enum_1 = require("./enum");
const utils_1 = require("../utils");
/**
* Returns the geographic location of the device under test.
*
* Location Services for WDA must be set to 'Always' for reliable readings from the device
* (`/wda/device/location`). Latitude, longitude, and altitude may still be zero briefly after
* enabling Always, while the device updates its fix.
*
* On iOS 17 and newer, if `mobile:setSimulatedLocation` was used earlier in the session, this
* command may return that simulated position via `mobile:getSimulatedLocation` before falling
* back to the device endpoint above.
*
* @returns Coordinates with altitude
* @throws {Error} If WDA returns an error (for example, tvOS may report unsupported).
*/
async function getGeoLocation() {
if ((0, utils_1.isIos17OrNewer)(this.opts)) {
const { latitude, longitude } = await this.mobileGetSimulatedLocation();
if (support_1.util.hasValue(latitude) && support_1.util.hasValue(longitude)) {
this.log.debug('Returning the geolocation that has been previously set by mobile:setSimulatedLocation. ' +
'mobile:resetSimulatedLocation can reset the location configuration.');
return { latitude, longitude, altitude: 0 };
}
this.log.warn(`No location was set by mobile:setSimulatedLocation. Trying to return the location from the device.`);
}
// Prefer `/wda/device/location` over `/wda/simulatedLocation` for reads: they can disagree
// until a simulated location is applied; `/wda/simulatedLocation` may be null until then.
const { authorizationStatus, latitude, longitude, altitude } = (await this.proxyCommand('/wda/device/location', 'GET'));
// `3` === kCLAuthorizationStatusAuthorizedAlways (CLAuthorizationStatus)
// https://developer.apple.com/documentation/corelocation/clauthorizationstatus
if (authorizationStatus !== enum_1.AuthorizationStatus.authorizedAlways) {
throw this.log.errorWithException(`Location service must be set to 'Always' in order to ` +
`retrieve the current geolocation data. Please set it up manually via ` +
`'Settings > Privacy > Location Services -> WebDriverAgentRunner-Runner'. ` +
`Or please use 'mobile:getSimulatedLocation'/'mobile:setSimulatedLocation' commands ` +
`to simulate locations instead.`);
}
return { latitude, longitude, altitude };
}
/**
* Sets the geographic location of the device under test.
*
* On a simulator, coordinates are passed to the simulator API. On a real device running
* iOS 17 or newer, this uses `mobile:setSimulatedLocation` (XCTest session simulated location).
* On older real devices, it uses the legacy lockdown simulate-location service.
*
* @param location - Must include `latitude` and `longitude` (each coerced with `Number()`).
*/
async function setGeoLocation(location) {
for (const name of ['latitude', 'longitude']) {
if (!support_1.util.hasValue(location[name])) {
throw new driver_1.errors.InvalidArgumentError(`${name} should be set`);
}
if (!Number.isFinite(Number(location[name]))) {
throw new driver_1.errors.InvalidArgumentError(`${name} must be a number, got '${location[name]}' instead`);
}
}
const [latitudeNumber, longitudeNumber] = [Number(location.latitude), Number(location.longitude)];
if (this.isSimulator()) {
await this.device.setGeolocation(`${latitudeNumber}`, `${longitudeNumber}`);
return { latitude: latitudeNumber, longitude: longitudeNumber, altitude: 0 };
}
if ((0, utils_1.isIos17OrNewer)(this.opts)) {
this.log.info(`Proxying to mobile:setSimulatedLocation method for iOS 17+`);
await this.mobileSetSimulatedLocation(latitudeNumber, longitudeNumber);
}
else {
await withLegacySimulateLocationSession(this, 'Device UDID is required to set geolocation on a real device', (session) => session.setLocation(latitudeNumber, longitudeNumber), (udid, msg) => `Can't set the location on device '${udid}'. Original error: ${msg}`);
}
return { latitude: latitudeNumber, longitude: longitudeNumber, altitude: 0 };
}
/**
* Resets simulated or legacy location state.
*
* - iOS 17 and newer: `mobile:resetSimulatedLocation` (simulator or real device).
* - Real device, older iOS: legacy simulate-location session over lockdown (UDID required).
* - Simulator, older iOS: not supported.
*
* @throws {errors.NotImplementedError} When the target is a simulator on iOS < 17.
* @throws {errors.InvalidArgumentError} When the legacy path runs without a UDID.
* @throws {Error} When the underlying reset fails.
*/
async function mobileResetLocationService() {
if ((0, utils_1.isIos17OrNewer)(this.opts)) {
this.log.info(`Proxying to mobile:resetSimulatedLocation method for iOS 17+`);
await this.mobileResetSimulatedLocation();
return;
}
if (this.isSimulator()) {
throw new driver_1.errors.NotImplementedError();
}
await withLegacySimulateLocationSession(this, 'Device UDID is required to reset location on a real device', (session) => session.resetLocation(), (udid, msg) => `Failed to reset location on device '${udid}'. Original error: ${msg}`);
}
/**
* Opens a legacy simulate-location session, runs `run`, closes the session, and maps errors.
*/
async function withLegacySimulateLocationSession(driver, udidRequiredMessage, run, formatError) {
const { udid } = driver.opts;
if (!udid) {
throw new driver_1.errors.InvalidArgumentError(udidRequiredMessage);
}
const session = await simulate_location_client_1.SimulateLocationClient.startSession(udid);
try {
await run(session);
}
catch (e) {
throw driver.log.errorWithException(formatError(udid, e.message));
}
finally {
session.close();
}
}
//# sourceMappingURL=location.js.map