appium-xcuitest-driver
Version:
Appium driver for iOS using XCUITest for backend
283 lines • 11.3 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.active = active;
exports.touchId = touchId;
exports.toggleEnrollTouchId = toggleEnrollTouchId;
exports.getWindowSize = getWindowSize;
exports.getDeviceTime = getDeviceTime;
exports.mobileGetDeviceTime = mobileGetDeviceTime;
exports.getWindowRect = getWindowRect;
exports.removeApp = removeApp;
exports.launchApp = launchApp;
exports.closeApp = closeApp;
exports.setUrl = setUrl;
exports.getViewportRect = getViewportRect;
exports.getScreenInfo = getScreenInfo;
exports.getStatusBarHeight = getStatusBarHeight;
exports.getDevicePixelRatio = getDevicePixelRatio;
exports.mobilePressButton = mobilePressButton;
exports.mobileSiriCommand = mobileSiriCommand;
const lodash_1 = __importDefault(require("lodash"));
const driver_1 = require("appium/driver");
const moment_timezone_1 = __importDefault(require("moment-timezone"));
const appium_ios_device_1 = require("appium-ios-device");
const teen_process_1 = require("teen_process");
const MOMENT_FORMAT_ISO8601 = 'YYYY-MM-DDTHH:mm:ssZ';
/**
* Gets the currently active element.
*
* In web context, returns the active element from the DOM.
* In native context, returns the active element from the current view.
*
* @returns The active element
*/
async function active() {
if (this.isWebContext()) {
return this.cacheWebElements(await this.executeAtom('active_element', []));
}
return await this.proxyCommand(`/element/active`, 'GET');
}
/**
* Trigger a touch/fingerprint match or match failure.
*
* @param match - Whether the match should be a success or failure
*/
async function touchId(match = true) {
await this.mobileSendBiometricMatch('touchId', match);
}
/**
* Toggle whether the device is enrolled in the touch ID program.
*
* @param isEnabled - Whether to enable or disable the touch ID program
*/
async function toggleEnrollTouchId(isEnabled = true) {
await this.mobileEnrollBiometric(isEnabled);
}
/**
* Get the window size.
*
* @returns The window size (width and height)
*/
async function getWindowSize() {
const { width, height } = await this.getWindowRect();
return { width, height };
}
/**
* Retrieves the actual device time.
*
* @param format - The format specifier string. Read the [MomentJS documentation](https://momentjs.com/docs/) to get the full list of supported datetime format specifiers. The default format is `YYYY-MM-DDTHH:mm:ssZ`, which complies to ISO-8601.
* @returns Formatted datetime string or the raw command output (if formatting fails)
*/
async function getDeviceTime(format = MOMENT_FORMAT_ISO8601) {
this.log.info('Attempting to capture iOS device date and time');
if (!this.isRealDevice()) {
this.log.info('On simulator. Assuming device time is the same as host time');
const cmd = 'date';
const args = ['+%Y-%m-%dT%H:%M:%S%z'];
const inputFormat = 'YYYY-MM-DDTHH:mm:ssZZ';
const stdout = (await (0, teen_process_1.exec)(cmd, args)).stdout.trim();
this.log.debug(`Got the following output out of '${cmd} ${args.join(' ')}': ${stdout}`);
const parsedTimestamp = moment_timezone_1.default.utc(stdout, inputFormat);
if (!parsedTimestamp.isValid()) {
this.log.warn(`Cannot parse the timestamp '${stdout}' returned by '${cmd}' command. Returning it as is`);
return stdout;
}
// @ts-expect-error This internal prop of moment is evidently a private API
return parsedTimestamp.utcOffset(parsedTimestamp._tzm || 0).format(format);
}
const { timestamp, utcOffset, timeZone } = await appium_ios_device_1.utilities.getDeviceTime(this.opts.udid);
this.log.debug(`timestamp: ${timestamp}, utcOffset: ${utcOffset}, timeZone: ${timeZone}`);
const utc = moment_timezone_1.default.unix(timestamp).utc();
// at some point of time Apple started to return timestamps
// in utcOffset instead of actual UTC offsets
if (Math.abs(utcOffset) <= 12 * 60) {
return utc.utcOffset(utcOffset).format(format);
}
// timeZone could either be a time zone name or
// an UTC offset in seconds
if (lodash_1.default.includes(timeZone, '/')) {
return utc.tz(timeZone).format(format);
}
if (Math.abs(timeZone) <= 12 * 60 * 60) {
return utc.utcOffset(timeZone / 60).format(format);
}
this.log.warn('Did not know how to apply the UTC offset. Returning the timestamp without it');
return utc.format(format);
}
/**
* Retrieves the current device time.
*
* This is a wrapper around {@linkcode getDeviceTime}.
*
* @param format - See {@linkcode getDeviceTime.format}
* @returns Formatted datetime string or the raw command output if formatting fails
*/
async function mobileGetDeviceTime(format = MOMENT_FORMAT_ISO8601) {
return await this.getDeviceTime(format);
}
/**
* Gets the window rectangle (position and size).
*
* For W3C compatibility. In web context, returns the browser window dimensions.
* In native context, returns the device window dimensions.
*
* @returns The window rectangle
*/
async function getWindowRect() {
if (this.isWebContext()) {
const script = 'return {' +
'x: window.screenX || 0,' +
'y: window.screenY || 0,' +
'width: window.innerWidth,' +
'height: window.innerHeight' +
'}';
return await this.executeAtom('execute_script', [script]);
}
return (await this.proxyCommand('/window/rect', 'GET'));
}
/**
* Removes/uninstalls the given application from the device under test.
*
* This is a wrapper around {@linkcode mobileRemoveApp mobile: removeApp}.
*
* @param bundleId - The bundle identifier of the application to be removed
* @returns `true` if the application has been removed successfully; `false` otherwise
*/
async function removeApp(bundleId) {
return await this.mobileRemoveApp(bundleId);
}
/**
* Launches the app.
*
* @deprecated This API has been deprecated and is not supported anymore.
* Consider using corresponding 'mobile:' extensions to manage the state of the app under test.
* @throws {Error} Always throws an error indicating the API is deprecated
*/
async function launchApp() {
throw new Error(`The launchApp API has been deprecated and is not supported anymore. ` +
`Consider using corresponding 'mobile:' extensions to manage the state of the app under test.`);
}
/**
* Closes the app.
*
* @deprecated This API has been deprecated and is not supported anymore.
* Consider using corresponding 'mobile:' extensions to manage the state of the app under test.
* @throws {Error} Always throws an error indicating the API is deprecated
*/
async function closeApp() {
throw new Error(`The closeApp API has been deprecated and is not supported anymore. ` +
`Consider using corresponding 'mobile:' extensions to manage the state of the app under test.`);
}
/**
* Sets the URL for the current session.
*
* In web context, navigates to the URL using the remote debugger.
* In native context on real devices, uses the proxy command.
* In native context on simulators, uses simctl to open the URL.
*
* @param url - The URL to navigate to
*/
async function setUrl(url) {
this.log.debug(`Attempting to set url '${url}'`);
if (this.isWebContext()) {
this.setCurrentUrl(url);
// make sure to clear out any leftover web frames
this.curWebFrames = [];
await this.remote.navToUrl(url);
return;
}
if (this.isRealDevice()) {
await this.proxyCommand('/url', 'POST', { url });
}
else {
await this.device.simctl.openUrl(url);
}
}
/**
* Retrieves the viewport dimensions.
*
* The viewport is the device's screen size with status bar size subtracted if the latter is present/visible.
*
* @returns The viewport rectangle
*/
async function getViewportRect() {
const scale = await this.getDevicePixelRatio();
// status bar height comes in unscaled, so scale it
const statusBarHeight = Math.trunc((await this.getStatusBarHeight()) * scale);
const windowSize = await this.getWindowRect();
// ios returns coordinates/dimensions in logical pixels, not device pixels,
// so scale up to device pixels. status bar height is already scaled.
return {
left: 0,
top: statusBarHeight,
width: Math.trunc(windowSize.width * scale),
height: Math.trunc(windowSize.height * scale) - statusBarHeight,
};
}
/**
* Get information about the screen.
*
* @privateRemarks memoized in constructor
* @returns Screen information including dimensions, scale, and status bar size
*/
async function getScreenInfo() {
return (await this.proxyCommand('/wda/screen', 'GET'));
}
/**
* Gets the status bar height.
*
* @returns The height of the status bar in logical pixels
*/
async function getStatusBarHeight() {
const { statusBarSize } = await this.getScreenInfo();
return statusBarSize.height;
}
/**
* Gets the device pixel ratio.
*
* @privateRemarks memoized in constructor
* @returns The device pixel ratio (scale factor)
*/
async function getDevicePixelRatio() {
const { scale } = await this.getScreenInfo();
return scale;
}
/**
* Emulates press action on the given physical device button.
*
* This executes different methods based on the platform:
*
* - iOS: [`pressButton:`](https://developer.apple.com/documentation/xctest/xcuidevice/1619052-pressbutton)
* - tvOS: [`pressButton:`](https://developer.apple.com/documentation/xctest/xcuiremote/1627475-pressbutton) or [`pressButton:forDuration:`](https://developer.apple.com/documentation/xctest/xcuiremote/1627476-pressbutton)
*
* Use {@linkcode mobilePerformIoHidEvent} to call a more universal API to perform a button press with duration on any supported device.
*
* @param name - The name of the button to be pressed
* @param durationSeconds - The duration of the button press in seconds (float)
*/
async function mobilePressButton(name, durationSeconds) {
if (!name) {
throw new driver_1.errors.InvalidArgumentError('Button name is mandatory');
}
if (!lodash_1.default.isNil(durationSeconds) && !lodash_1.default.isNumber(durationSeconds)) {
throw new driver_1.errors.InvalidArgumentError('durationSeconds should be a number');
}
return await this.proxyCommand('/wda/pressButton', 'POST', { name, duration: durationSeconds });
}
/**
* Process a string as speech and send it to Siri.
*
* Presents the Siri UI, if it is not currently active, and accepts a string which is then processed as if it were recognized speech. See [the documentation of `activateWithVoiceRecognitionText`](https://developer.apple.com/documentation/xctest/xcuisiriservice/2852140-activatewithvoicerecognitiontext?language=objc) for more details.
*
* @param text - Text to be sent to Siri
*/
async function mobileSiriCommand(text) {
if (!text) {
throw new driver_1.errors.InvalidArgumentError('"text" argument is mandatory');
}
await this.proxyCommand('/wda/siri/activate', 'POST', { text });
}
//# sourceMappingURL=general.js.map