Skip to content

Commit

Permalink
feat: Align the implementation of execute method map with other andro…
Browse files Browse the repository at this point in the history
…id drivers (#780)
  • Loading branch information
mykola-mokhnach committed May 12, 2024
1 parent 2d528cb commit d376a9b
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 733 deletions.
155 changes: 62 additions & 93 deletions lib/commands/execute.js
Original file line number Diff line number Diff line change
@@ -1,120 +1,89 @@
import _ from 'lodash';
import {errors, PROTOCOLS} from 'appium/driver';
import {AndroidUiautomator2Driver} from '../driver';

const MOBILE_SCRIPT_NAME_PREFIX = 'mobile:';
import {AndroidDriver} from 'appium-android-driver';

/**
* @override
* @privateRemarks Because the "mobile" commands (execute methods) in this
* driver universally accept an options object, this method will _not_ call
* into `BaseDriver.executeMethod`.
* @this {AndroidUiautomator2Driver}
* @param {string} script
* @param {any[]} [args]
* @returns {Promise<any>}
* @returns {import('@appium/types').StringRecord<string>}
*/
export async function execute(script, args) {
const mobileScriptName = toExecuteMethodName(script);
const isWebContext = this.isWebContext();
if (mobileScriptName && isWebContext || !isWebContext) {
if (mobileScriptName) {
const executeMethodArgs = preprocessExecuteMethodArgs(args);
this.log.info(`Executing method '${mobileScriptName}'`);
return await this.executeMobile(mobileScriptName, executeMethodArgs);
}
// Just pass the script name through and let it fail with a proper error message
return await this.executeMobile(`${script}`, {});
}
const endpoint =
/** @type {import('appium-chromedriver').Chromedriver} */ (this.chromedriver).jwproxy
.downstreamProtocol === PROTOCOLS.MJSONWP
? '/execute'
: '/execute/sync';
return await /** @type {import('appium-chromedriver').Chromedriver} */ (
this.chromedriver
).jwproxy.command(endpoint, 'POST', {
script,
args,
});
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',

pressKey: 'mobilePressKey',

screenshots: 'mobileScreenshots',

scheduleAction: 'mobileScheduleAction',
getActionHistory: 'mobileGetActionHistory',
unscheduleAction: 'mobileUnscheduleAction',
};
}

/**
* @override
* @this {AndroidUiautomator2Driver}
* @param {string} script Must be of the form `mobile: <something>`, which
* differs from its parent class implementation.
* @param {string} mobileCommand
* @param {import('@appium/types').StringRecord} [opts={}]
* @returns {Promise<any>}
*/
export async function executeMobile(script, opts = {}) {
if (!(script in AndroidUiautomator2Driver.executeMethodMap)) {
const commandNames = _.map(
_.keys(AndroidUiautomator2Driver.executeMethodMap),
(value) => value.slice(8)
);
throw new errors.UnknownCommandError(
`Unknown mobile command "${script}". ` +
`Only ${commandNames.join(', ')} commands are supported.`
);
}
const methodName =
AndroidUiautomator2Driver.executeMethodMap[
/** @type {keyof import('../execute-method-map').Uiautomator2ExecuteMethodMap} */ (script)
].command;

return await /** @type {(opts?: any) => Promise<unknown>} */ (this[methodName])(opts);
export async function executeMobile(mobileCommand, opts = {}) {
return await new AndroidDriver().executeMobile.call(this, mobileCommand, preprocessOptions(opts));
}

// #region Internal Helpers

/**
* Messages the arguments going into an execute method.
* @remarks A similar method is implemented in `appium-xcuitest-driver`, but it
* appears the methods in here handle unwrapping of `Element` objects, so we do
* not do that here.
* @param {readonly any[] | readonly [StringRecord] | Readonly<StringRecord>} [args]
* 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 {StringRecord<unknown>}
* @returns {import('@appium/types').StringRecord|undefined}
*/
function preprocessExecuteMethodArgs(args) {
if (_.isArray(args)) {
args = _.first(args);
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'`);
}
const executeMethodArgs = /** @type {StringRecord<unknown>} */ (args ?? {});
/**
* 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.
*/
if (!('elementId' in executeMethodArgs) && 'element' in executeMethodArgs) {
executeMethodArgs.elementId = executeMethodArgs.element;
delete executeMethodArgs.element;
}

return executeMethodArgs;
}

/**
* Type guard to check if a script is an execute method.
* @param {any} script
* @internal
* @returns {string?}
*/
function toExecuteMethodName(script) {
return _.startsWith(script, MOBILE_SCRIPT_NAME_PREFIX)
? script.replace(new RegExp(`${MOBILE_SCRIPT_NAME_PREFIX}\\s*`), `${MOBILE_SCRIPT_NAME_PREFIX} `)
: null;
return opts;
}

// #endregion

/**
* @typedef {import('../uiautomator2').UiAutomator2Server} UiAutomator2Server
* @typedef {import('appium-adb').ADB} ADB
*/

/**
* @template [T=any]
* @typedef {import('@appium/types').StringRecord<T>} StringRecord
* @typedef {import('../driver').AndroidUiautomator2Driver} AndroidUiautomator2Driver
*/
5 changes: 2 additions & 3 deletions lib/commands/gestures.js
Original file line number Diff line number Diff line change
Expand Up @@ -238,8 +238,7 @@ export async function mobileScrollBackTo(opts) {
*/
export async function mobileScroll(opts) {
const {
element,
elementId, // `element` is deprecated, use `elementId` instead
elementId,
strategy,
selector,
maxSwipes,
Expand All @@ -253,7 +252,7 @@ export async function mobileScroll(opts) {
'/gestures/scroll_to',
'POST',
{
origin: toOrigin(elementId || element),
origin: toOrigin(elementId),
params: {strategy, selector, maxSwipes},
}
);
Expand Down
7 changes: 2 additions & 5 deletions lib/driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import path from 'node:path';
import {checkPortStatus, findAPortNotInUse} from 'portscanner';
import type {ExecError} from 'teen_process';
import UIAUTOMATOR2_CONSTRAINTS, {type Uiautomator2Constraints} from './constraints';
import {executeMethodMap} from './execute-method-map';
import {APKS_EXTENSION, APK_EXTENSION} from './extensions';
import {newMethodMap} from './method-map';
import { signApp } from './helpers';
Expand Down Expand Up @@ -81,8 +80,8 @@ import {
mobileReplaceElementValue,
} from './commands/element';
import {
execute,
executeMobile,
mobileCommandsMapping,
} from './commands/execute';
import {
doFindElementOrEls,
Expand Down Expand Up @@ -266,8 +265,6 @@ class AndroidUiautomator2Driver
{
static newMethodMap = newMethodMap;

static executeMethodMap = executeMethodMap;

uiautomator2: UiAutomator2Server;

systemPort: number | undefined;
Expand Down Expand Up @@ -1030,8 +1027,8 @@ class AndroidUiautomator2Driver
clear = clear;
mobileReplaceElementValue = mobileReplaceElementValue;

execute = execute;
executeMobile = executeMobile;
mobileCommandsMapping = mobileCommandsMapping;

doFindElementOrEls = doFindElementOrEls;

Expand Down
Loading

0 comments on commit d376a9b

Please sign in to comment.