diff --git a/lib/commands/actions.js b/lib/commands/actions.js index 85d0a4a82..36d196067 100644 --- a/lib/commands/actions.js +++ b/lib/commands/actions.js @@ -1,29 +1,51 @@ /** - * See https://github.com/appium/appium-uiautomator2-driver/blob/master/docs/scheduled-actions.md#mobile-scheduleaction + * @see https://github.com/appium/appium-uiautomator2-driver/blob/master/docs/scheduled-actions.md#mobile-scheduleaction * @this {AndroidUiautomator2Driver} - * @param {Record} [opts={}] + * @param {string} name + * @param {import('@appium/types').StringRecord[]} steps + * @param {number} [maxPass] + * @param {number} [maxFail] + * @param {number} [times] + * @param {number} [intervalMs] + * @param {number} [maxHistoryItems] * @returns {Promise} */ -export async function mobileScheduleAction(opts = {}) { +export async function mobileScheduleAction( + name, + steps, + maxPass, + maxFail, + times, + intervalMs, + maxHistoryItems, +) { return await this.uiautomator2.jwproxy.command( '/appium/schedule_action', 'POST', - opts + { + name, + steps, + maxFail, + maxPass, + times, + intervalMs, + maxHistoryItems, + } ); } /** * @see https://github.com/appium/appium-uiautomator2-driver/blob/master/docs/scheduled-actions.md#mobile-getactionhistory * @this {AndroidUiautomator2Driver} - * @param {import('./types').ActionArgs} [opts={}] + * @param {string} name * @returns {Promise} */ -export async function mobileGetActionHistory(opts) { +export async function mobileGetActionHistory(name) { return /** @type {import('./types').ActionResult} */ ( await this.uiautomator2.jwproxy.command( '/appium/action_history', 'POST', - opts ?? {} + {name} ) ); } @@ -31,14 +53,14 @@ export async function mobileGetActionHistory(opts) { /** * @this {AndroidUiautomator2Driver} * @see https://github.com/appium/appium-uiautomator2-driver/blob/master/docs/scheduled-actions.md#mobile-unscheduleaction - * @param {import('./types').ActionArgs} [opts={}] + * @param {string} name * @returns {Promise} */ -export async function mobileUnscheduleAction(opts) { +export async function mobileUnscheduleAction(name) { return await this.uiautomator2.jwproxy.command( '/appium/unschedule_action', 'POST', - opts ?? {} + {name} ); } diff --git a/lib/commands/alert.js b/lib/commands/alert.js index a74dc493f..96160ec99 100644 --- a/lib/commands/alert.js +++ b/lib/commands/alert.js @@ -14,14 +14,16 @@ export async function getAlertText() { /** * @this {AndroidUiautomator2Driver} - * @param {import('./types').AcceptAlertOptions} [opts={}] + * @param {string} [buttonLabel] The name of the button to click in order to accept the alert. + * If the name is not provided + * then the script will try to detect the button automatically. * @returns {Promise} */ -export async function mobileAcceptAlert(opts = {}) { +export async function mobileAcceptAlert(buttonLabel) { await this.uiautomator2.jwproxy.command( '/alert/accept', 'POST', - opts + {buttonLabel} ); } @@ -35,14 +37,16 @@ export async function postAcceptAlert() { /** * @this {AndroidUiautomator2Driver} - * @param {import('./types').DismissAlertOptions} [opts={}] + * @param {string} [buttonLabel] The name of the button to click in order to dismiss the alert. + * If the name is not provided + * then the script will try to detect the button automatically. * @returns {Promise} */ -export async function mobileDismissAlert(opts = {}) { +export async function mobileDismissAlert(buttonLabel) { await this.uiautomator2.jwproxy.command( '/alert/dismiss', 'POST', - opts + {buttonLabel} ); } diff --git a/lib/commands/app-management.js b/lib/commands/app-management.js index de31128d9..ad4f2fa04 100644 --- a/lib/commands/app-management.js +++ b/lib/commands/app-management.js @@ -5,30 +5,33 @@ import {APK_EXTENSION} from '../extensions'; /** * Install multiple APKs with `install-multiple` option. - * @this {AndroidUiautomator2Driver}= - * @param {import('./types').InstallMultipleApksOptions} opts + * @this {AndroidUiautomator2Driver} + * @param {string[]} apks The list of APKs to install. Each APK should be a path to a apk + * or downloadable URL as HTTP/HTTPS. + * @param {import('./types').InstallOptions} [options] Installation options. * @throws {Error} if an error occured while installing the given APKs. * @returns {Promise} */ -export async function mobileInstallMultipleApks(opts) { - if (!_.isArray(opts.apks) || _.isEmpty(opts.apks)) { +export async function mobileInstallMultipleApks(apks, options) { + if (!_.isArray(apks) || _.isEmpty(apks)) { throw new errors.InvalidArgumentError('No apks are given to install'); } - const apks = await B.all( - opts.apks.map((app) => this.helpers.configureApp(app, [APK_EXTENSION])) + const configuredApks = await B.all( + apks.map((app) => this.helpers.configureApp(app, [APK_EXTENSION])) ); - await this.adb.installMultipleApks(apks, opts.options); + await this.adb.installMultipleApks(configuredApks, options); } /** - * Puts the app to background and waits the given number of seconds Then restores the app + * Puts the app to background and waits the given number of seconds then restores the app * if necessary. The call is blocking. + * * @this {AndroidUiautomator2Driver} - * @param {import('./types').BackgroundAppOptions} [opts={}] + * @param {number} [seconds=-1] The amount of seconds to wait between putting the app to background and restoring it. + * Any negative value means to not restore the app after putting it to background. * @returns {Promise} */ -export async function mobileBackgroundApp(opts = {}) { - const {seconds = -1} = opts; +export async function mobileBackgroundApp(seconds = -1) { await this.background(seconds); } diff --git a/lib/commands/app-strings.js b/lib/commands/app-strings.js deleted file mode 100644 index 45ae9c829..000000000 --- a/lib/commands/app-strings.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Retrives app strings from its resources for the given language - * or the default device language. - * @this {AndroidUiautomator2Driver} - * @param {import('./types').GetAppStringsOptions} [opts={}] - * @returns {Promise} - */ -export async function mobileGetAppStrings(opts) { - return await this.getStrings(opts?.language); -} - -/** - * @typedef {import('appium-adb').ADB} ADB - * @typedef {import('@appium/types').StringRecord} StringRecord - * @typedef {import('../driver').AndroidUiautomator2Driver} AndroidUiautomator2Driver - */ diff --git a/lib/commands/clipboard.js b/lib/commands/clipboard.js index 4c262c0d3..f8d5fc9c3 100644 --- a/lib/commands/clipboard.js +++ b/lib/commands/clipboard.js @@ -15,15 +15,6 @@ export async function getClipboard() { ); } -/** - * @this {AndroidUiautomator2Driver} - * @returns {Promise} Base64-encoded content of the clipboard - * or an empty string if the clipboard is empty. - */ -export async function mobileGetClipboard() { - return await this.getClipboard(); -} - /** * @this {AndroidUiautomator2Driver} * @param {string} content Base64-encoded clipboard payload @@ -41,15 +32,6 @@ export async function setClipboard(content, contentType, label) { ); } -/** - * @this {AndroidUiautomator2Driver} - * @param {import('./types').SetClipboardOpts} opts - * @returns {Promise} - */ -export async function mobileSetClipboard(opts) { - await this.setClipboard(opts.content, opts.contentType, opts.label); -} - /** * @typedef {import('../driver').AndroidUiautomator2Driver} AndroidUiautomator2Driver */ diff --git a/lib/commands/element.js b/lib/commands/element.js index f9ff594ea..94b530891 100644 --- a/lib/commands/element.js +++ b/lib/commands/element.js @@ -1,7 +1,6 @@ import B from 'bluebird'; import _ from 'lodash'; import {PROTOCOLS} from 'appium/driver'; -import {utils} from 'appium-android-driver'; /** * @this {AndroidUiautomator2Driver} @@ -229,12 +228,12 @@ export async function getElementRect(elementId) { /** * Sends text to the given element by replacing its previous content * @this {AndroidUiautomator2Driver} - * @param {import('./types').ReplaceValueOptions} opts + * @param {string} elementId The id of the element whose content will be replaced. + * @param {string} text The actual text to set. * @throws {Error} If there was a faulre while setting the text * @returns {Promise} */ -export async function mobileReplaceElementValue(opts) { - const {elementId, text} = utils.requireArgs(['elementId', 'text'], opts); +export async function mobileReplaceElementValue(elementId, text) { await this.uiautomator2.jwproxy.command( `/element/${elementId}/value`, 'POST', diff --git a/lib/commands/execute.js b/lib/commands/execute.js deleted file mode 100644 index f1b7bfa62..000000000 --- a/lib/commands/execute.js +++ /dev/null @@ -1,93 +0,0 @@ -import _ from 'lodash'; -import {AndroidDriver} from 'appium-android-driver'; - -/** - * @this {AndroidUiautomator2Driver} - * @returns {import('@appium/types').StringRecord} - */ -export function mobileCommandsMapping() { - const commonMapping = new AndroidDriver().mobileCommandsMapping.call(this); - return { - ...commonMapping, - dragGesture: 'mobileDragGesture', - flingGesture: 'mobileFlingGesture', - doubleClickGesture: 'mobileDoubleClickGesture', - clickGesture: 'mobileClickGesture', - longClickGesture: 'mobileLongClickGesture', - pinchCloseGesture: 'mobilePinchCloseGesture', - pinchOpenGesture: 'mobilePinchOpenGesture', - swipeGesture: 'mobileSwipeGesture', - scrollGesture: 'mobileScrollGesture', - scrollBackTo: 'mobileScrollBackTo', - scroll: 'mobileScroll', - viewportScreenshot: 'mobileViewportScreenshot', - viewportRect: 'mobileViewPortRect', - - deepLink: 'mobileDeepLink', - - acceptAlert: 'mobileAcceptAlert', - dismissAlert: 'mobileDismissAlert', - - batteryInfo: 'mobileGetBatteryInfo', - - deviceInfo: 'mobileGetDeviceInfo', - - openNotifications: 'openNotifications', - - type: 'mobileType', - replaceElementValue: 'mobileReplaceElementValue', - - getAppStrings: 'mobileGetAppStrings', - - installMultipleApks: 'mobileInstallMultipleApks', - backgroundApp: 'mobileBackgroundApp', - - pressKey: 'mobilePressKey', - - screenshots: 'mobileScreenshots', - - scheduleAction: 'mobileScheduleAction', - getActionHistory: 'mobileGetActionHistory', - unscheduleAction: 'mobileUnscheduleAction', - - setClipboard: 'mobileSetClipboard', - getClipboard: 'mobileGetClipboard', - }; -} - -/** - * @override - * @this {AndroidUiautomator2Driver} - * @param {string} mobileCommand - * @param {import('@appium/types').StringRecord} [opts={}] - * @returns {Promise} - */ -export async function executeMobile(mobileCommand, opts = {}) { - return await new AndroidDriver().executeMobile.call(this, mobileCommand, preprocessOptions(opts)); -} - -// #region Internal Helpers - -/** - * Renames the deprecated `element` key to `elementId`. Historically, - * all of the pre-Execute-Method-Map execute methods accepted an `element` _or_ and `elementId` param. - * This assigns the `element` value to `elementId` if `elementId` is not already present. - * - * @param {import('@appium/types').StringRecord} [opts={}] - * @internal - * @returns {import('@appium/types').StringRecord|undefined} - */ -function preprocessOptions(opts = {}) { - if (_.isPlainObject(opts) && !('elementId' in opts) && 'element' in opts) { - opts.elementId = opts.element; - delete opts.element; - this.log.debug(`Replaced the obsolete 'element' key with 'elementId'`); - } - return opts; -} - -// #endregion - -/** - * @typedef {import('../driver').AndroidUiautomator2Driver} AndroidUiautomator2Driver - */ diff --git a/lib/commands/gestures.js b/lib/commands/gestures.js index 39eda3a36..b7801c6aa 100644 --- a/lib/commands/gestures.js +++ b/lib/commands/gestures.js @@ -6,12 +6,16 @@ import {errors} from 'appium/driver'; * Performs a simple click/tap gesture * * @this {AndroidUiautomator2Driver} - * @param {import('./types').ClickOptions} [opts={}] + * @param {string} [elementId] The id of the element to be clicked. + * If the element is missing then both click offset coordinates must be provided. + * If both the element id and offset are provided then the coordinates are parsed + * as relative offsets from the top left corner of the element. + * @param {number} [x] The x coordinate to click on. + * @param {number} [y] The y coordinate to click on. * @returns {Promise} * @throws {Error} if provided options are not valid */ -export async function mobileClickGesture(opts = {}) { - const {elementId, x, y} = opts; +export async function mobileClickGesture(elementId, x, y) { await this.uiautomator2.jwproxy.command( '/appium/gestures/click', 'POST', @@ -26,12 +30,18 @@ export async function mobileClickGesture(opts = {}) { * Performs a click that lasts for the given duration * * @this {AndroidUiautomator2Driver} - * @param {import('./types').LongClickOptions} [opts={}] + * @param {string} [elementId] The id of the element to be clicked. + * If the element is missing then both click offset coordinates must be provided. + * If both the element id and offset are provided then the coordinates are parsed + * as relative offsets from the top left corner of the element. + * @param {number} [x] The x coordinate to click on. + * @param {number} [y] The y coordinate to click on. + * @param {number} [duration] Click duration in milliseconds. The value must not be negative. + * Default is 500. * @returns {Promise} * @throws {Error} if provided options are not valid */ -export async function mobileLongClickGesture(opts = {}) { - const {elementId, x, y, duration} = opts; +export async function mobileLongClickGesture(elementId, x, y, duration) { await this.uiautomator2.jwproxy.command( '/appium/gestures/long_click', 'POST', @@ -46,12 +56,16 @@ export async function mobileLongClickGesture(opts = {}) { /** * Performs a click that lasts for the given duration * @this {AndroidUiautomator2Driver} - * @param {import('./types').DoubleClickOptions} [opts={}] + * @param {string} [elementId] The id of the element to be clicked. + * If the element is missing then both click offset coordinates must be provided. + * If both the element id and offset are provided then the coordinates are parsed + * as relative offsets from the top left corner of the element. + * @param {number} [x] The x coordinate to click on. + * @param {number} [y] The y coordinate to click on. * @returns {Promise} * @throws {Error} if provided options are not valid */ -export async function mobileDoubleClickGesture(opts = {}) { - const {elementId, x, y} = opts; +export async function mobileDoubleClickGesture(elementId, x, y) { await this.uiautomator2.jwproxy.command( '/appium/gestures/double_click', 'POST', @@ -65,12 +79,28 @@ export async function mobileDoubleClickGesture(opts = {}) { /** * Drags this object to the specified location. * @this {AndroidUiautomator2Driver} - * @param {import('./types').DragOptions} opts + * @param {string} [elementId] The id of the element to be dragged. + * If the element id is missing then the start coordinates must be provided. + * If both the element id and the start coordinates are provided then these + * coordinates are considered as offsets from the top left element corner. + * @param {number} [startX] The x coordinate where the dragging starts + * @param {number} [startY] The y coordinate where the dragging starts + * @param {number} [endX] The x coordinate where the dragging ends + * @param {number} [endY] The y coordinate where the dragging ends + * @param {number} [speed] The speed at which to perform this gesture in pixels per second. + * The value must not be negative. + * Default is 2500 * displayDensity. * @returns {Promise} * @throws {Error} if provided options are not valid */ -export async function mobileDragGesture(opts) { - const {elementId, startX, startY, endX, endY, speed} = opts; +export async function mobileDragGesture( + elementId, + startX, + startY, + endX, + endY, + speed, +) { await this.uiautomator2.jwproxy.command( '/appium/gestures/drag', 'POST', @@ -88,11 +118,30 @@ export async function mobileDragGesture(opts) { * * @throws {Error} if provided options are not valid * @this {AndroidUiautomator2Driver} - * @param {import('./types').FlingOptions} opts + * @param {string} direction Direction of the fling. + * Acceptable values are: `up`, `down`, `left` and `right` (case insensitive). + * @param {string} [elementId] The id of the element to be flinged. + * If the element id is missing then fling bounding area must be provided. + * If both the element id and the fling bounding area are provided then this + * area is effectively ignored. + * @param {number} [left] The left coordinate of the fling bounding area. + * @param {number} [top] The top coordinate of the fling bounding area. + * @param {number} [width] The width of the fling bounding area. + * @param {number} [height] The height of the fling bounding area. + * @param {number} [speed] The speed at which to perform this gesture in pixels per second. + * The value must be greater than the minimum fling velocity for the given view (50 by default). + * Default is 7500 * displayDensity. * @returns {Promise} True if the object can still scroll in the given direction. */ -export async function mobileFlingGesture(opts) { - const {elementId, left, top, width, height, direction, speed} = opts; +export async function mobileFlingGesture( + direction, + elementId, + left, + top, + width, + height, + speed, +) { return /** @type {boolean} */ ( await this.uiautomator2.jwproxy.command( '/appium/gestures/fling', @@ -110,12 +159,31 @@ export async function mobileFlingGesture(opts) { /** * Performs a pinch close gesture. * @this {AndroidUiautomator2Driver} - * @param {import('./types').PinchOptions} opts + * @param {number} percent The size of the pinch as a percentage of the pinch area size. + * Valid values must be float numbers in range 0..1, where 1.0 is 100% + * @param {string} [elementId] The id of the element to be pinched. + * If the element id is missing then pinch bounding area must be provided. + * If both the element id and the pinch bounding area are provided then the + * area is effectively ignored. + * @param {number} [left] The left coordinate of the pinch bounding area. + * @param {number} [top] The top coordinate of the pinch bounding area. + * @param {number} [width] The width of the pinch bounding area. + * @param {number} [height] The height of the pinch bounding area. + * @param {number} [speed] The speed at which to perform this gesture in pixels per second. + * The value must not be negative. + * Default is 2500 * displayDensity. * @returns {Promise} * @throws {Error} if provided options are not valid */ -export async function mobilePinchCloseGesture(opts) { - const {elementId, left, top, width, height, percent, speed} = opts; +export async function mobilePinchCloseGesture( + percent, + elementId, + left, + top, + width, + height, + speed, +) { await this.uiautomator2.jwproxy.command( '/appium/gestures/pinch_close', 'POST', @@ -131,12 +199,31 @@ export async function mobilePinchCloseGesture(opts) { /** * Performs a pinch open gesture. * @this {AndroidUiautomator2Driver} - * @param {import('./types').PinchOptions} opts + * @param {number} percent The size of the pinch as a percentage of the pinch area size. + * Valid values must be float numbers in range 0..1, where 1.0 is 100% + * @param {string} [elementId] The id of the element to be pinched. + * If the element id is missing then pinch bounding area must be provided. + * If both the element id and the pinch bounding area are provided then the + * area is effectively ignored. + * @param {number} [left] The left coordinate of the pinch bounding area. + * @param {number} [top] The top coordinate of the pinch bounding area. + * @param {number} [width] The width of the pinch bounding area. + * @param {number} [height] The height of the pinch bounding area. + * @param {number} [speed] The speed at which to perform this gesture in pixels per second. + * The value must not be negative. + * Default is 2500 * displayDensity. * @returns {Promise} * @throws {Error} if provided options are not valid */ -export async function mobilePinchOpenGesture(opts) { - const {elementId, left, top, width, height, percent, speed} = opts; +export async function mobilePinchOpenGesture( + percent, + elementId, + left, + top, + width, + height, + speed, +) { await this.uiautomator2.jwproxy.command( '/appium/gestures/pinch_open', 'POST', @@ -152,12 +239,34 @@ export async function mobilePinchOpenGesture(opts) { /** * Performs a swipe gesture. * @this {AndroidUiautomator2Driver} - * @param {import('./types').SwipeOptions} opts + * @param {string} direction Direction of the swipe. + * Acceptable values are: `up`, `down`, `left` and `right` (case insensitive). + * @param {number} percent The size of the swipe as a percentage of the swipe area size. + * Valid values must be float numbers in range 0..1, where 1.0 is 100%. + * @param {string} [elementId] The id of the element to be swiped. + * If the element id is missing then swipe bounding area must be provided. + * If both the element id and the swipe bounding area are provided then the + * area is effectively ignored. + * @param {number} [left] The left coordinate of the swipe bounding area. + * @param {number} [top] The top coordinate of the swipe bounding area. + * @param {number} [width] The width of the swipe bounding area. + * @param {number} [height] The height of the swipe bounding area. + * @param {number} [speed] The speed at which to perform this gesture in pixels per second. + * The value must not be negative. + * Default is 5000 * displayDensity. * @returns {Promise} * @throws {Error} if provided options are not valid */ -export async function mobileSwipeGesture(opts) { - const {elementId, left, top, width, height, direction, percent, speed} = opts; +export async function mobileSwipeGesture( + direction, + percent, + elementId, + left, + top, + width, + height, + speed, +) { await this.uiautomator2.jwproxy.command( '/appium/gestures/swipe', 'POST', @@ -176,11 +285,33 @@ export async function mobileSwipeGesture(opts) { * * @throws {Error} if provided options are not valid * @this {AndroidUiautomator2Driver} - * @param {import('./types').ScrollGestureOptions} opts + * @param {string} direction Direction of the scroll. + * Acceptable values are: `up`, `down`, `left` and `right` (case insensitive). + * @param {number} percent The size of the scroll as a percentage of the scrolling area size. + * Valid values must be float numbers greater than zero, where 1.0 is 100%. + * @param {string} [elementId] The id of the element to be scrolled. + * If the element id is missing then scroll bounding area must be provided. + * If both the element id and the scroll bounding area are provided then this + * area is effectively ignored. + * @param {number} [left] The left coordinate of the scroll bounding area. + * @param {number} [top] The top coordinate of the scroll bounding area. + * @param {number} [width] The width of the scroll bounding area. + * @param {number} [height] The height of the scroll bounding area. + * @param {number} [speed] The speed at which to perform this gesture in pixels per second. + * The value must not be negative. + * Default is 5000 * displayDensity. * @returns {Promise} True if the object can still scroll in the given direction. */ -export async function mobileScrollGesture(opts) { - const {elementId, left, top, width, height, direction, percent, speed} = opts; +export async function mobileScrollGesture( + direction, + percent, + elementId, + left, + top, + width, + height, + speed, +) { return /** @type {boolean} */ ( await this.uiautomator2.jwproxy.command( '/appium/gestures/scroll', @@ -204,12 +335,16 @@ export async function mobileScrollGesture(opts) { * by scrolling its parent to the end step by step. The scroll direction (vertical or horizontal) * is detected automatically. * @this {AndroidUiautomator2Driver} - * @param {import('./types').ScrollElementToElementOpts} opts + * @param {string} elementId The identifier of the scrollable element, which is going to be scrolled. + * It is required this element is a valid scrollable container and it was located + * by `-android uiautomator` strategy. + * @param {string} elementToId The identifier of the item, which belongs to the scrollable element above, + * and which should become visible after the scrolling operation is finished. + * It is required this element was located by `-android uiautomator` strategy. * @returns {Promise} * @throws {Error} if the scrolling operation cannot be performed */ -export async function mobileScrollBackTo(opts) { - const {elementId, elementToId} = opts; +export async function mobileScrollBackTo(elementId, elementToId) { if (!elementId || !elementToId) { throw new errors.InvalidArgumentError( `Both elementId and elementToId arguments must be provided` @@ -231,18 +366,29 @@ export async function mobileScrollBackTo(opts) { * to the very beginning of the scrollable control and tries to reach the destination element * by scrolling its parent to the end step by step. The scroll direction (vertical or horizontal) * is detected automatically. + * * @this {AndroidUiautomator2Driver} - * @param {import('./types').ScrollOptions} opts + * @param {string} strategy The following strategies are supported: + * - `accessibility id` (UiSelector().description) + * - `class name` (UiSelector().className) + * - `-android uiautomator` (UiSelector) + * @param {string} selector The corresponding lookup value for the given strategy. + * @param {string} [elementId] The identifier of an element. It is required this element is a valid scrollable container + * and it was located by `-android uiautomator` strategy. + * If this property is not provided then the first currently available scrollable view + * is selected for the interaction. + * @param {number} [maxSwipes] The maximum number of swipes to perform on the target scrollable view in order to reach + * the destination element. In case this value is unset then it would be retrieved from the + * scrollable element itself (via `getMaxSearchSwipes()` property). * @returns {Promise} * @throws {Error} if the scrolling operation cannot be performed */ -export async function mobileScroll(opts) { - const { - elementId, - strategy, - selector, - maxSwipes, - } = opts; +export async function mobileScroll( + strategy, + selector, + elementId, + maxSwipes, +) { if (!strategy || !selector) { throw new errors.InvalidArgumentError( `Both strategy and selector arguments must be provided` diff --git a/lib/commands/keyboard.js b/lib/commands/keyboard.js index 741057173..18423c239 100644 --- a/lib/commands/keyboard.js +++ b/lib/commands/keyboard.js @@ -1,3 +1,4 @@ +import { errors } from 'appium/driver'; import _ from 'lodash'; /** @@ -40,12 +41,16 @@ export async function longPressKeyCode(keycode, metastate, flags) { /** * @this {AndroidUiautomator2Driver} - * @param {import('./types').PressKeyOptions} opts + * @param {number} keycode A valid Android key code. See https://developer.android.com/reference/android/view/KeyEvent + * for the list of available key codes. + * @param {number} [metastate] An integer in which each bit set to 1 represents a pressed meta key. See + * https://developer.android.com/reference/android/view/KeyEvent for more details. + * @param {string} [flags] Flags for the particular key event. See + * https://developer.android.com/reference/android/view/KeyEvent for more details. + * @param {boolean} [isLongPress=false] Whether to emulate long key press * @returns {Promise} */ -export async function mobilePressKey(opts) { - const {keycode, metastate, flags, isLongPress = false} = opts; - +export async function mobilePressKey(keycode, metastate, flags, isLongPress = false) { await this.uiautomator2.jwproxy.command( `/appium/device/${isLongPress ? 'long_' : ''}press_keycode`, 'POST', @@ -61,15 +66,15 @@ export async function mobilePressKey(opts) { * Types the given Unicode string. * It is expected that the focus is already put * to the destination input field before this method is called. + * * @this {AndroidUiautomator2Driver} - * @param {import('./types').TypingOptions} opts + * @param {string | number | boolean} text The text to type. Can be a string, number or boolean. * @returns {Promise} `true` if the input text has been successfully sent to adb * @throws {Error} if `text` property has not been provided */ -export async function mobileType(opts) { - const {text} = opts; +export async function mobileType(text) { if (_.isUndefined(text)) { - throw this.log.errorWithException(`The 'text' argument is mandatory`); + throw new errors.InvalidArgumentError(`The 'text' argument is mandatory`); } return await this.settingsApp.typeUnicode(String(text)); } diff --git a/lib/commands/navigation.js b/lib/commands/navigation.js index 35520b513..4032dcc64 100644 --- a/lib/commands/navigation.js +++ b/lib/commands/navigation.js @@ -10,11 +10,13 @@ export async function setUrl(url) { /** * Start URL that take users directly to specific content in the app * @this {AndroidUiautomator2Driver} - * @param {import('./types').DeepLinkOpts} opts + * @param {string} url The name of URL to start. + * @param {string} [pkg] The name of the package to start the URI with. + * @param {boolean} [waitForLaunch=true] If `false` then adb won't wait for + * the started activity to return the control. * @returns {Promise} */ -export async function mobileDeepLink(opts) { - const {url, package: pkg, waitForLaunch} = opts; +export async function mobileDeepLink(url, pkg, waitForLaunch) { return await this.adb.startUri(url, pkg, {waitForLaunch}); } diff --git a/lib/commands/screenshot.js b/lib/commands/screenshot.js index b7464d6c8..ec614f498 100644 --- a/lib/commands/screenshot.js +++ b/lib/commands/screenshot.js @@ -49,10 +49,12 @@ export async function getScreenshot() { * Retrieves screenshots of each display available to Android. * This functionality is only supported since Android 10. * @this {AndroidUiautomator2Driver} - * @param {import('./types').ScreenshotsOpts} [opts={}] + * @param {number | string} [displayId] Android display identifier to take a screenshot for. + * If not provided then screenshots of all displays are going to be returned. + * If no matches were found then an error is thrown. * @returns {Promise>} */ -export async function mobileScreenshots(opts = {}) { +export async function mobileScreenshots(displayId) { const displaysInfo = await /** @type {import('appium-adb').ADB} */ (this.adb).shell([ 'dumpsys', 'SurfaceFlinger', @@ -82,7 +84,6 @@ export async function mobileScreenshots(opts = {}) { 'base64' ); - const {displayId} = opts; // @ts-ignore isNaN works properly here const displayIdStr = isNaN(displayId) ? null : `${displayId}`; if (displayIdStr) { diff --git a/lib/commands/types.ts b/lib/commands/types.ts index c5bad4910..dc63e6c94 100644 --- a/lib/commands/types.ts +++ b/lib/commands/types.ts @@ -1,55 +1,5 @@ import {Rect, StringRecord} from '@appium/types'; -/** - * Represents options for pressing a key on an Android device. - */ -export interface PressKeyOptions { - /** - * A valid Android key code. See https://developer.android.com/reference/android/view/KeyEvent - * for the list of available key codes. - */ - keycode: number; - /** - * An integer in which each bit set to 1 represents a pressed meta key. See - * https://developer.android.com/reference/android/view/KeyEvent for more details. - */ - metastate?: number; - /** - * Flags for the particular key event. See - * https://developer.android.com/reference/android/view/KeyEvent for more details. - */ - flags?: string; - /** - * Whether to emulate long key press. Defaults to `false`. - */ - isLongPress: boolean; -} - -export interface AcceptAlertOptions { - /** - * The name of the button to click in order to accept the alert. If the name is not provided - * then the script will try to detect the button automatically. - */ - buttonLabel?: string; -} - -export interface DismissAlertOptions { - /** - * The name of the button to click in order to dismiss the alert. If the name is not provided - * then the script will try to detect the button automatically. - */ - buttonLabel?: string; -} - -export interface GetAppStringsOptions { - /** - * The language abbreviation to fetch app strings mapping for. If no - * language is provided then strings for the default language on the device under test - * would be returned. Examples: en, fr - */ - language?: string; -} - export type BatteryState = -1 | 1 | 2 | 3 | 4 | 5; export interface BatteryInfo { @@ -70,43 +20,10 @@ export interface BatteryInfo { state: BatteryState; } -export interface ReplaceValueOptions { - /** - * The id of the element whose content will be replaced. - */ - elementId: string; - /** - * The actual text to set. - */ - text: string; -} - export type MapKey = Pick> & { [P in N]: T[K]; }; -export interface DeepLinkOpts { - /** - * The name of URL to start. - */ - url: string; - /** - * The name of the package to start the URI with. - */ - package?: string; - /** - * If `false` then adb won't wait for the started activity to return the control. - * @defaultValue true - */ - waitForLaunch?: boolean; -} - -export interface TypingOptions { - /** - * The text to type. Can be a string, number or boolean. - */ - text: string | number | boolean; -} export interface InstallOptions { /** * Set to true in order to allow test packages installation. @@ -138,301 +55,6 @@ export interface InstallOptions { partialInstall?: boolean; } -export interface InstallMultipleApksOptions { - /** - * The list of APKs to install. Each APK should be a path to a apk - * or downloadable URL as HTTP/HTTPS. - */ - apks: string[]; - /** - * The installation options. - */ - options?: InstallOptions; -} -export interface BackgroundAppOptions { - /** - * The amount of seconds to wait between putting the app to background and restoring it. - * Any negative value means to not restore the app after putting it to background. - * @defaultValue -1 - */ - seconds?: number; -} - -export interface ClickOptions { - /** - * The id of the element to be clicked. If the element is missing then both click offset coordinates must be provided. If both the element id and offset are provided then the coordinates are parsed as relative offsets from the top left corner of the element. - */ - elementId?: string; - /** - * The x coordinate to click on. - */ - x?: number; - /** - * The y coordinate to click on. - */ - y?: number; -} - -export interface LongClickOptions { - /** - * The id of the element to be clicked. - * If the element is missing then both click offset coordinates must be provided. - * If both the element id and offset are provided then the coordinates - * are parsed as relative offsets from the top left corner of the element. - */ - elementId?: string; - /** - * The x coordinate to click on. - */ - x?: number; - /** - * The y coordinate to click on. - */ - y?: number; - /** - * Click duration in milliseconds. The value must not be negative. - * Default is 500. - */ - duration?: number; -} - -export type DoubleClickOptions = ClickOptions; - -export interface DragOptions { - /** - * The id of the element to be dragged. - * If the element id is missing then the start coordinates must be provided. - * If both the element id and the start coordinates are provided then these - * coordinates are considered as offsets from the top left element corner. - */ - elementId?: string; - /** - * The x coordinate where the dragging starts - */ - startX?: number; - /** - * The y coordinate where the dragging starts - */ - startY?: number; - /** - * The x coordinate where the dragging ends - */ - endX?: number; - /** - * The y coordinate where the dragging ends - */ - endY?: number; - /** - * The speed at which to perform this gesture in pixels per second. - * The value must not be negative. - * Default is 2500 * displayDensity. - */ - speed?: number; -} - -export interface FlingOptions { - /** - * The id of the element to be flinged. - * If the element id is missing then fling bounding area must be provided. - * If both the element id and the fling bounding area are provided then this - * area is effectively ignored. - */ - elementId?: string; - /** - * The left coordinate of the fling bounding area. - */ - left?: number; - /** - * The top coordinate of the fling bounding area. - */ - top?: number; - /** - * The width of the fling bounding area. - */ - width?: number; - /** - * The height of the fling bounding area. - */ - height?: number; - /** - * Direction of the fling. - * Acceptable values are: `up`, `down`, `left` and `right` (case insensitive). - */ - direction: string; - /** - * The speed at which to perform this gesture in pixels per second. - * The value must be greater than the minimum fling velocity for the given view (50 by default). - * Default is 7500 * displayDensity. - */ - speed?: number; -} - -export interface PinchOptions { - /** - * The id of the element to be pinched. - * If the element id is missing then pinch bounding area must be provided. - * If both the element id and the pinch bounding area are provided then the - * area is effectively ignored. - */ - elementId?: string; - /** - * The left coordinate of the pinch bounding area. - */ - left?: number; - /** - * The top coordinate of the pinch bounding area. - */ - top?: number; - /** - * The width of the pinch bounding area. - */ - width?: number; - /** - * The height of the pinch bounding area. - */ - height?: number; - /** - * The size of the pinch as a percentage of the pinch area size. - * Valid values must be float numbers in range 0..1, where 1.0 is 100% - */ - percent: number; - /** - * The speed at which to perform this gesture in pixels per second. - * The value must not be negative. - * Default is 2500 * displayDensity. - */ - speed?: number; -} - -export interface SwipeOptions { - /** - * The id of the element to be swiped. - * If the element id is missing then swipe bounding area must be provided. - * If both the element id and the swipe bounding area are provided then the - * area is effectively ignored. - */ - elementId?: string; - /** - * The left coordinate of the swipe bounding area. - */ - left?: number; - /** - * The top coordinate of the swipe bounding area. - */ - top?: number; - /** - * The width of the swipe bounding area. - */ - width?: number; - /** - * The height of the swipe bounding area. - */ - height?: number; - /** - * Direction of the swipe. - * Acceptable values are: `up`, `down`, `left` and `right` (case insensitive). - */ - direction: string; - /** - * The size of the swipe as a percentage of the swipe area size. - * Valid values must be float numbers in range 0..1, where 1.0 is 100%. - */ - percent: number; - /** - * The speed at which to perform this gesture in pixels per second. - * The value must not be negative. - * Default is 5000 * displayDensity. - */ - speed?: number; -} -export interface ScrollGestureOptions { - /** - * The id of the element to be scrolled. - * If the element id is missing then scroll bounding area must be provided. - * If both the element id and the scroll bounding area are provided then this - * area is effectively ignored. - */ - elementId?: string; - /** - * The left coordinate of the scroll bounding area. - */ - left?: number; - /** - * The top coordinate of the scroll bounding area. - */ - top?: number; - /** - * The width of the scroll bounding area. - */ - width?: number; - /** - * The height of the scroll bounding area. - */ - height?: number; - /** - * Direction of the scroll. - * Acceptable values are: `up`, `down`, `left` and `right` (case insensitive). - */ - direction: string; - /** - * The size of the scroll as a percentage of the scrolling area size. - * Valid values must be float numbers greater than zero, where 1.0 is 100%. - */ - percent: number; - /** - * The speed at which to perform this gesture in pixels per second. - * The value must not be negative. - * Default is 5000 * displayDensity. - */ - speed?: number; -} - -export interface ScrollElementToElementOpts { - /** - * The identifier of the scrollable element, which is going to be scrolled. - * It is required this element is a valid scrollable container and it was located - * by `-android uiautomator` strategy. - */ - elementId: string; - /** - * The identifier of the item, which belongs to the scrollable element above, - * and which should become visible after the scrolling operation is finished. - * It is required this element was located by `-android uiautomator` strategy. - */ - elementToId: string; -} - -export interface ScrollOptions { - /** - * The identifier of an element. It is required this element is a valid scrollable container - * and it was located by `-android uiautomator` strategy. - * If this property is not provided then the first currently available scrollable view - * is selected for the interaction. - */ - elementId?: string; - /** - * The following strategies are supported: - * - `accessibility id` (UiSelector().description) - * - `class name` (UiSelector().className) - * - `-android uiautomator` (UiSelector) - */ - strategy: string; - /** - * The corresponding lookup value for the given strategy. - */ - selector: string; - /** - * The maximum number of swipes to perform on the target scrollable view in order to reach - * the destination element. In case this value is unset then it would be retrieved from the - * scrollable element itself (via `getMaxSearchSwipes()` property). - */ - maxSwipes?: number; - /** - * @deprecated - */ - element?: string; -} - export type RelativeRect = Pick & {left: Rect['x']; top: Rect['y']}; export interface Screenshot { @@ -454,35 +76,7 @@ export interface Screenshot { payload: string; } -export interface ScreenshotsOpts { - /** - * Android display identifier to take a screenshot for. - * If not provided then screenshots of all displays are going to be returned. - * If no matches were found then an error is thrown. - */ - displayId?: number | string; -} - export interface ActionResult { repeats: number; stepResults: StringRecord[][]; } - -export interface ActionArgs { - name: string; -} - -export interface SetClipboardOpts { - /** - * Base64-encoded clipboard payload - */ - content: string; - /** - * Only a single content type is supported, which is 'plaintext' - */ - contentType?: 'plaintext'; - /** - * Optinal label to identify the current clipboard payload - */ - label?: string; -} diff --git a/lib/driver.ts b/lib/driver.ts index d24fa2514..870d5734e 100644 --- a/lib/driver.ts +++ b/lib/driver.ts @@ -55,17 +55,12 @@ import { mobileInstallMultipleApks, mobileBackgroundApp, } from './commands/app-management'; -import { - mobileGetAppStrings, -} from './commands/app-strings'; import { mobileGetBatteryInfo, } from './commands/battery'; import { getClipboard, - mobileGetClipboard, setClipboard, - mobileSetClipboard, } from './commands/clipboard'; import { active, @@ -85,10 +80,6 @@ import { clear, mobileReplaceElementValue, } from './commands/element'; -import { - executeMobile, - mobileCommandsMapping, -} from './commands/execute'; import { doFindElementOrEls, } from './commands/find'; @@ -141,6 +132,7 @@ import { getWindowSize, mobileViewPortRect, } from './commands/viewport'; +import { executeMethodMap } from './execute-method-map'; // The range of ports we can use on the system for communicating to the // UiAutomator2 HTTP server on the device @@ -269,6 +261,7 @@ class AndroidUiautomator2Driver > { static newMethodMap = newMethodMap; + static executeMethodMap = executeMethodMap; uiautomator2: UiAutomator2Server; @@ -1018,8 +1011,6 @@ class AndroidUiautomator2Driver mobileInstallMultipleApks = mobileInstallMultipleApks; mobileBackgroundApp = mobileBackgroundApp; - mobileGetAppStrings = mobileGetAppStrings; - mobileGetBatteryInfo = mobileGetBatteryInfo; active = active; @@ -1039,9 +1030,6 @@ class AndroidUiautomator2Driver clear = clear; mobileReplaceElementValue = mobileReplaceElementValue; - executeMobile = executeMobile; - mobileCommandsMapping = mobileCommandsMapping; - doFindElementOrEls = doFindElementOrEls; mobileClickGesture = mobileClickGesture; @@ -1071,9 +1059,7 @@ class AndroidUiautomator2Driver mobileGetDeviceInfo = mobileGetDeviceInfo; getClipboard = getClipboard; - mobileGetClipboard = mobileGetClipboard; setClipboard = setClipboard; - mobileSetClipboard = mobileSetClipboard; setUrl = setUrl; mobileDeepLink = mobileDeepLink; diff --git a/lib/execute-method-map.ts b/lib/execute-method-map.ts new file mode 100644 index 000000000..5bdde2a6e --- /dev/null +++ b/lib/execute-method-map.ts @@ -0,0 +1,269 @@ +import { ExecuteMethodMap } from '@appium/types'; +import { AndroidDriver } from 'appium-android-driver'; + +export const executeMethodMap = { + ...AndroidDriver.executeMethodMap, + + 'mobile: dragGesture': { + command: 'mobileDragGesture', + params: { + optional: [ + 'elementId', + 'startX', + 'startY', + 'endX', + 'endY', + 'speed', + ], + }, + }, + 'mobile: flingGesture': { + command: 'mobileFlingGesture', + params: { + required: [ + 'direction', + ], + optional: [ + 'elementId', + 'left', + 'top', + 'width', + 'height', + 'speed', + ], + }, + }, + 'mobile: doubleClickGesture': { + command: 'mobileDoubleClickGesture', + params: { + optional: [ + 'elementId', + 'x', + 'y', + ], + }, + }, + 'mobile: clickGesture': { + command: 'mobileClickGesture', + params: { + optional: [ + 'elementId', + 'x', + 'y', + ], + }, + }, + 'mobile: longClickGesture': { + command: 'mobileLongClickGesture', + params: { + optional: [ + 'elementId', + 'x', + 'y', + 'duration', + ], + }, + }, + 'mobile: pinchCloseGesture': { + command: 'mobilePinchCloseGesture', + params: { + required: [ + 'percent', + ], + optional: [ + 'elementId', + 'left', + 'top', + 'width', + 'height', + 'speed', + ], + }, + }, + 'mobile: pinchOpenGesture': { + command: 'mobilePinchOpenGesture', + params: { + required: [ + 'percent', + ], + optional: [ + 'elementId', + 'left', + 'top', + 'width', + 'height', + 'speed', + ], + }, + }, + 'mobile: swipeGesture': { + command: 'mobileSwipeGesture', + params: { + required: [ + 'direction', + 'percent', + ], + optional: [ + 'elementId', + 'left', + 'top', + 'width', + 'height', + 'speed', + ], + }, + }, + 'mobile: scrollGesture': { + command: 'mobileScrollGesture', + params: { + required: [ + 'direction', + 'percent', + ], + optional: [ + 'elementId', + 'left', + 'top', + 'width', + 'height', + 'speed', + ], + }, + }, + 'mobile: scrollBackTo': { + command: 'mobileScrollBackTo', + params: { + required: [ + 'elementId', + 'elementToId', + ], + }, + }, + 'mobile: scroll': { + command: 'mobileScroll', + params: { + required: [ + 'strategy', + 'selector', + ], + optional: [ + 'elementId', + 'maxSwipes', + ], + }, + }, + + 'mobile: viewportScreenshot': { + command: 'mobileViewportScreenshot', + }, + 'mobile: viewportRect': { + command: 'mobileViewPortRect', + }, + + 'mobile: deepLink': { + command: 'mobileDeepLink', + params: { + required: ['url'], + optional: ['package', 'waitForLaunch'], + } + }, + + 'mobile: acceptAlert': { + command: 'mobileAcceptAlert', + params: { + optional: ['buttonLabel'], + } + }, + 'mobile: dismissAlert': { + command: 'mobileDismissAlert', + params: { + optional: ['buttonLabel'], + } + }, + + 'mobile: batteryInfo': { + command: 'mobileGetBatteryInfo', + }, + + 'mobile: deviceInfo': { + command: 'mobileGetDeviceInfo', + }, + + 'mobile: openNotifications': { + command: 'openNotifications', + }, + + 'mobile: type': { + command: 'mobileType', + params: { + required: ['text'], + } + }, + 'mobile: replaceElementValue': { + command: 'mobileReplaceElementValue', + params: { + required: ['elementId', 'text'], + } + }, + + 'mobile: installMultipleApks': { + command: 'mobileInstallMultipleApks', + params: { + required: ['apks'], + optional: ['options'], + } + }, + + 'mobile: backgroundApp': { + command: 'mobileBackgroundApp', + params: { + optional: ['seconds'], + } + }, + + 'mobile: pressKey': { + command: 'mobilePressKey', + params: { + required: ['keycode'], + optional: ['metastate', 'flags', 'isLongPress'], + } + }, + + 'mobile: screenshots': { + command: 'mobileScreenshots', + params: { + optional: ['displayId'], + } + }, + + 'mobile: scheduleAction': { + command: 'mobileScheduleAction', + params: { + required: ['name', 'steps'], + optional: ['maxPass', 'maxFail', 'times', 'intervalMs', 'maxHistoryItems'], + } + }, + 'mobile: getActionHistory': { + command: 'mobileGetActionHistory', + params: { + required: ['name'], + } + }, + 'mobile: unscheduleAction': { + command: 'mobileUnscheduleAction', + params: { + required: ['name'], + } + }, + + 'mobile: setClipboard': { + command: 'setClipboard', + params: { + required: ['content'], + optional: ['contentType', 'label'], + } + }, + 'mobile: getClipboard': { + command: 'getClipboard', + }, +} as const satisfies ExecuteMethodMap; diff --git a/package.json b/package.json index 2cfa3b21f..5cc3777cc 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ }, "dependencies": { "appium-adb": "^12.12.0", - "appium-android-driver": "^9.15.0", + "appium-android-driver": "^10.1.0", "appium-uiautomator2-server": "^7.0.24", "asyncbox": "^3.0.0", "axios": "^1.6.5", diff --git a/test/unit/commands/general-specs.js b/test/unit/commands/general-specs.js index 78b31d635..3bdf24006 100644 --- a/test/unit/commands/general-specs.js +++ b/test/unit/commands/general-specs.js @@ -46,7 +46,7 @@ describe('General', function () { describe('mobile command', function () { it('should raise error on non-existent mobile command', async function () { await expect(driver.execute('mobile: fruta', {})).to.be.rejectedWith( - /Unknown mobile command "fruta"/ + /Unsupported/ ); }); }); @@ -56,10 +56,10 @@ describe('General', function () { // true, because the "am I an emulator?" check happens in the sensorSet // implementation, which is stubbed out. it('should call sensorSet', async function () { - mockDriver.expects('sensorSet').once().withExactArgs({ - sensorType: 'acceleration', - value: '0:9.77631:0.812349', - }); + mockDriver.expects('sensorSet').once().withArgs( + 'acceleration', + '0:9.77631:0.812349', + ); await driver.execute('mobile: sensorSet', { sensorType: 'acceleration', value: '0:9.77631:0.812349', @@ -101,9 +101,7 @@ describe('General', function () { }); it('should reject with default args', async function () { - await expect(driver.execute('mobile: installMultipleApks')).to.be.rejectedWith( - 'No apks are given to install' - ); + await expect(driver.execute('mobile: installMultipleApks')).to.be.rejected; }); }); });