Skip to content

Commit

Permalink
feat: Add support of executeMethodMap (#327)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Arguments of the following driver methods were changed:
- macosSetValue
- macosClick
- macosScroll
- macosSwipe
- macosRightClick
- macosHover
- macosClickAndDrag
- macosClickAndDragAndHold
- macosKeys
- macosTap
- doubleTap
- macosPressAndHold
- macosPressAndDrag
- macosPressAndDragAndHold
- macosSource
- macosDeepLink
- macosExecAppleScript
- macosScreenshots
- macosLaunchApp
- macosActivateApp
- macosTerminateApp
- macosQueryAppState
- startRecordingScreen
- stopRecordingScreen
  • Loading branch information
KazuCocoa authored Feb 6, 2025
1 parent faa305e commit a6c9a6b
Show file tree
Hide file tree
Showing 12 changed files with 786 additions and 918 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ Name | Type | Required | Description | Example
--- | --- | --- | --- | ---
elementId ("element" prior to Appium v 1.22) | string | if `x` or `y` are unset | Unique identifier of the element to perform the click on. Either this property or/and x and y must be set. If both are set then x and y are considered as relative element coordinates. If only x and y are set then these are parsed as absolute coordinates. | 21045BC8-013C-43BD-9B1E-4C6DC7AB0744
x | number | if `y` is set or `elementId` is unset | click X coordinate | 100
y | number | if `y` is set or `elementId` is unset | click Y coordinate | 100
y | number | if `x` is set or `elementId` is unset | click Y coordinate | 100
keyModifierFlags | number | no | if set then the given key modifiers will be applied while click is performed. See the official documentation on [XCUIKeyModifierFlags enumeration](https://developer.apple.com/documentation/xctest/xcuikeymodifierflags) for more details | `1 << 1 | 1 << 2`

#### References
Expand Down
43 changes: 30 additions & 13 deletions lib/commands/app-management.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,23 @@
* app with the given identifier cannot be found.
*
* @this {Mac2Driver}
* @param {import('../types').LaunchAppOptions} [opts={}]
* @param {string} [bundleId] Bundle identifier of the app to be launched or activated.
* Either this property or `path` must be provided
* @param {string} [path] Full path to the app bundle. Either this property or
* `bundleId` must be provided
* @param {string[]} [args] The list of command line arguments for the app to be launched with.
* This parameter is ignored if the app is already running.
* @param {import('@appium/types').StringRecord} [environment] Environment variables mapping.
* Custom variables are added to the default process environment.
*/
export async function macosLaunchApp (opts = {}) {
const { bundleId, environment, path } = opts;
export async function macosLaunchApp (
bundleId,
path,
args,
environment,
) {
return await this.wda.proxy.command('/wda/apps/launch', 'POST', {
arguments: opts.arguments,
arguments: args,
environment,
bundleId,
path,
Expand All @@ -21,10 +32,12 @@ export async function macosLaunchApp (opts = {}) {
* app cannot be found or is not running.
*
* @this {Mac2Driver}
* @param {import('../types').ActivateAppOptions} [opts={}]
* @param {string} [bundleId] Bundle identifier of the app to be activated.
* Either this property or `path` must be provided
* @param {string} [path] Full path to the app bundle. Either this property
* or `bundleId` must be provided
*/
export async function macosActivateApp (opts = {}) {
const { bundleId, path } = opts;
export async function macosActivateApp (bundleId, path) {
return await this.wda.proxy.command('/wda/apps/activate', 'POST', { bundleId, path });
};

Expand All @@ -33,12 +46,14 @@ export async function macosActivateApp (opts = {}) {
* app cannot be found.
*
* @this {Mac2Driver}
* @param {import('../types').TerminateAppOptions} opts
* @param {string} [bundleId] Bundle identifier of the app to be terminated.
* Either this property or `path` must be provided
* @param {string} [path] Full path to the app bundle. Either this property
* or `bundleId` must be provided
* @returns {Promise<boolean>} `true` if the app was running and has been successfully terminated.
* `false` if the app was not running before.
*/
export async function macosTerminateApp (opts) {
const { bundleId, path } = opts ?? {};
export async function macosTerminateApp (bundleId, path) {
return /** @type {boolean} */ (
await this.wda.proxy.command('/wda/apps/terminate', 'POST', { bundleId, path })
);
Expand All @@ -49,13 +64,15 @@ export async function macosTerminateApp (opts) {
* app cannot be found.
*
* @this {Mac2Driver}
* @param {import('../types').QueryAppStateOptions} opts
* @param {string} [bundleId] Bundle identifier of the app whose state should be queried.
* Either this property or `path` must be provided
* @param {string} [path] Full path to the app bundle. Either this property
* or `bundleId` must be provided
* @returns {Promise<number>} The application state code. See
* https://developer.apple.com/documentation/xctest/xcuiapplicationstate?language=objc
* for more details
*/
export async function macosQueryAppState (opts) {
const { bundleId, path } = opts ?? {};
export async function macosQueryAppState (bundleId, path) {
return /** @type {number} */ (
await this.wda.proxy.command('/wda/apps/state', 'POST', { bundleId, path })
);
Expand Down
25 changes: 16 additions & 9 deletions lib/commands/applescript.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,28 @@ const APPLE_SCRIPT_FEATURE = 'apple_script';
* process in System Preferences -> Privacy list.
*
* @this {Mac2Driver}
* @param {import('../types').ExecAppleScriptOptions} opts
* @param {string} [script] A valid AppleScript to execute
* @param {string} [language] Overrides the scripting language. Basically, sets
* the value of `-l` command line argument of `osascript` tool.
* If unset the AppleScript language is assumed.
* @param {string} [command] A valid AppleScript as a single command (no line breaks) to execute
* @param {string} [cwd] The path to an existing folder, which is going to be set as
* the working directory for the command/script being executed.
* @param {number} [timeout] The number of seconds to wait until a long-running command is finished.
* An error is thrown if the command is still running after this timeout expires.
* @returns {Promise<string>} The actual stdout of the given command/script
* @throws {Error} If the exit code of the given command/script is not zero.
* The actual stderr output is set to the error message value.
*/
export async function macosExecAppleScript (opts = {}) {
export async function macosExecAppleScript (
script,
language,
command,
cwd,
timeout,
) {
this.assertFeatureEnabled(APPLE_SCRIPT_FEATURE);

const {
script,
language,
command,
cwd,
timeout,
} = opts;
if (!script && !command) {
throw this.log.errorWithException('AppleScript script/command must not be empty');
}
Expand Down
65 changes: 13 additions & 52 deletions lib/commands/execute.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,6 @@
import _ from 'lodash';
import { errors } from 'appium/driver';

const EXTENSION_COMMANDS_MAPPING = {
setValue: 'macosSetValue',
click: 'macosClick',
scroll: 'macosScroll',
swipe: 'macosSwipe',
rightClick: 'macosRightClick',
hover: 'macosHover',
doubleClick: 'macosDoubleClick',
clickAndDrag: 'macosClickAndDrag',
clickAndDragAndHold: 'macosClickAndDragAndHold',
keys: 'macosKeys',

tap: 'macosTap',
doubleTap: 'macosDoubleTap',
press: 'macosPress',
pressAndDrag: 'macosPressAndDrag',
pressAndDragAndHold: 'macosPressAndDragAndHold',

source: 'macosSource',

launchApp: 'macosLaunchApp',
activateApp: 'macosActivateApp',
terminateApp: 'macosTerminateApp',
queryAppState: 'macosQueryAppState',

appleScript: 'macosExecAppleScript',

startRecordingScreen: 'startRecordingScreen',
stopRecordingScreen: 'stopRecordingScreen',

screenshots: 'macosScreenshots',

deepLink: 'macosDeepLink',
};
const EXECUTE_SCRIPT_PREFIX = 'macos:';

/**
*
Expand All @@ -44,29 +10,24 @@ const EXTENSION_COMMANDS_MAPPING = {
* @returns {Promise<any>}
*/
export async function execute (script, args) {
if (script.match(/^macos:/)) {
this.log.info(`Executing extension command '${script}'`);
script = script.replace(/^macos:/, '').trim();
return await this.executeMacosCommand(script, _.isArray(args) ? args[0] : args);
}
throw new errors.NotImplementedError();
this.log.info(`Executing extension command '${script}'`);
const formattedScript = String(script).trim().replace(/^macos:\s*/, `${EXECUTE_SCRIPT_PREFIX} `);
const preprocessedArgs = preprocessExecuteMethodArgs(args);
return await this.executeMethod(formattedScript, [preprocessedArgs]);
};

/**
* Massages the arguments going into an execute method.
*
* @this {Mac2Driver}
* @param {string} command
* @param {import('@appium/types').StringRecord} [opts={}]
* @returns {Promise<any>}
* @param {ExecuteMethodArgs} [args]
* @returns {StringRecord}
*/
export async function executeMacosCommand (command, opts = {}) {
if (!_.has(EXTENSION_COMMANDS_MAPPING, command)) {
throw new errors.UnknownCommandError(`Unknown extension command "${command}". ` +
`Only ${_.keys(EXTENSION_COMMANDS_MAPPING)} commands are supported.`);
}
return await this[/** @type {string} */ (EXTENSION_COMMANDS_MAPPING[command])](opts);
};
function preprocessExecuteMethodArgs(args) {
return /** @type {StringRecord} */ ((_.isArray(args) ? _.first(args) : args) ?? {});
}

/**
* @typedef {import('../driver').Mac2Driver} Mac2Driver
* @typedef {import('@appium/types').StringRecord} StringRecord
* @typedef {readonly any[] | readonly [StringRecord] | Readonly<StringRecord>} ExecuteMethodArgs
*/
Loading

0 comments on commit a6c9a6b

Please sign in to comment.