Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add support of executeMethodMap #327

Merged
merged 37 commits into from
Feb 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
3d77b54
mmacos: setValue
KazuCocoa Feb 1, 2025
4703e09
macosClick
KazuCocoa Feb 1, 2025
0ded063
set []
KazuCocoa Feb 1, 2025
2127ab5
macos scroll
KazuCocoa Feb 1, 2025
2a92253
fix lint
KazuCocoa Feb 1, 2025
66f32e1
macosScroll
KazuCocoa Feb 1, 2025
6c57bdb
macosRightClick
KazuCocoa Feb 1, 2025
b1726bd
macosHover
KazuCocoa Feb 1, 2025
028fe6f
macosDoubleClick
KazuCocoa Feb 1, 2025
53331a4
macosClickAndDrag
KazuCocoa Feb 1, 2025
5eed11b
macosClickAndDragAndHold
KazuCocoa Feb 1, 2025
0ef62b7
macosKeys
KazuCocoa Feb 1, 2025
41d89e4
add assertion
KazuCocoa Feb 1, 2025
e789dbe
arrange the order
KazuCocoa Feb 1, 2025
0031125
fix typo
KazuCocoa Feb 1, 2025
5dd2c2d
macos tap
KazuCocoa Feb 1, 2025
fee8b25
macosDoubleTap
KazuCocoa Feb 2, 2025
e9e3848
make executeMethodMap work
KazuCocoa Feb 2, 2025
d16e13c
remove unused method
KazuCocoa Feb 2, 2025
79d7ef6
press
KazuCocoa Feb 2, 2025
163969a
macosPressAndDrag
KazuCocoa Feb 2, 2025
3cd9537
tweak
KazuCocoa Feb 2, 2025
6b0049a
macosPressAndDragAndHold
KazuCocoa Feb 2, 2025
f7e0541
fix lint
KazuCocoa Feb 2, 2025
f28d3b5
source
KazuCocoa Feb 2, 2025
058ea9b
macosDeepLink
KazuCocoa Feb 2, 2025
2605bd4
macosScreenshots
KazuCocoa Feb 2, 2025
93f58b5
macosExecAppleScript
KazuCocoa Feb 2, 2025
8e8c2dc
pther app management
KazuCocoa Feb 2, 2025
41e85ce
record
KazuCocoa Feb 2, 2025
5d33deb
remove setvalue
KazuCocoa Feb 2, 2025
c873109
remove unnecessary todo
KazuCocoa Feb 2, 2025
c8180dc
fix comments
KazuCocoa Feb 3, 2025
17066db
review
KazuCocoa Feb 4, 2025
2688951
deltaX and deltaY in scroll are also required
KazuCocoa Feb 4, 2025
dff7c50
use isString/isNumber and move to the bottom
KazuCocoa Feb 5, 2025
54e8f5b
modify a bit
KazuCocoa Feb 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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