diff --git a/README.md b/README.md index efb4b40..dc9c118 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/lib/commands/app-management.js b/lib/commands/app-management.js index bf756cd..9b335bd 100644 --- a/lib/commands/app-management.js +++ b/lib/commands/app-management.js @@ -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, @@ -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 }); }; @@ -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} `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 }) ); @@ -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} 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 }) ); diff --git a/lib/commands/applescript.js b/lib/commands/applescript.js index 0571ebe..8f967e1 100644 --- a/lib/commands/applescript.js +++ b/lib/commands/applescript.js @@ -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} 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'); } diff --git a/lib/commands/execute.js b/lib/commands/execute.js index 375efa8..7d80a2c 100644 --- a/lib/commands/execute.js +++ b/lib/commands/execute.js @@ -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:'; /** * @@ -44,29 +10,24 @@ const EXTENSION_COMMANDS_MAPPING = { * @returns {Promise} */ 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} + * @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} ExecuteMethodArgs */ diff --git a/lib/commands/gestures.js b/lib/commands/gestures.js index 54999d6..e2b6e00 100644 --- a/lib/commands/gestures.js +++ b/lib/commands/gestures.js @@ -1,63 +1,48 @@ +import _ from 'lodash'; import { util } from 'appium/support'; import { errors } from 'appium/driver'; /** - * - * @param {import('@appium/types').StringRecord} [options={}] - * @param {string[]} [keyNames] - * @returns {string|null} - */ -function extractUuid (options = {}, keyNames = ['elementId', 'element']) { - for (const name of keyNames) { - if (options[name]) { - const result = util.unwrapElement(options[name]); - if (result) { - return result; - } - } - } - return null; -} - -/** - * - * @param {import('@appium/types').StringRecord} [options={}] - * @param {string[]} [keyNames] - * @returns {string|null} - */ -function requireUuid (options = {}, keyNames = ['elementId', 'element']) { - const result = extractUuid(options, keyNames); - if (!result) { - throw new errors.InvalidArgumentError(`${keyNames[0]} field is mandatory`); - } - return result; -} - -/** - * Set value to the given element + * Set value to the given element. + * Note: + * This is not exposed as 'macos: setValue' because this is the same as + * element.send_keys in W3C WebDriver spec. * * @this {Mac2Driver} - * @param {import('../types').SetValueOptions} opts + * @param {string} elementId Uuid of the element to set value for. + * @param {any} [value] Value to set. Could also be an array. + * @param {string} [text] Text to set. If both value and text are set then `value` is preferred + * @param {number} [keyModifierFlags] If set then the given key modifiers will + * be applied while the element value is being set. See + * https://developer.apple.com/documentation/xctest/xcuikeymodifierflags + * for more details. */ -export async function macosSetValue (opts) { - const uuid = requireUuid(opts); - const { value, text, keyModifierFlags } = opts ?? {}; - return await this.wda.proxy.command(`/element/${uuid}/value`, 'POST', { +export async function macosSetValue (elementId, value, text, keyModifierFlags) { + return await this.wda.proxy.command(`/element/${elementId}/value`, 'POST', { value, text, keyModifierFlags, }); }; + /** * Perform click gesture on an element or by relative/absolute coordinates * * @this {Mac2Driver} - * @param {import('../types').ClickOptions} opts + * @param {string} [elementId] Uuid of the element to click. 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. + * @param {number} [x] Click X coordinate + * @param {number} [y] Click Y coordinate + * @param {number} [keyModifierFlags] If set then the given key modifiers will be + * applied while click is performed. See + * https://developer.apple.com/documentation/xctest/xcuikeymodifierflags + * for more details */ -export async function macosClick (opts = {}) { - const uuid = extractUuid(opts); - const { x, y, keyModifierFlags } = opts; - const url = uuid ? `/element/${uuid}/click` : '/wda/click'; +export async function macosClick (elementId, x, y, keyModifierFlags) { + requireElementIdOrXY(elementId, x, y); + const url = elementId ? `/element/${elementId}/click` : '/wda/click'; return await this.wda.proxy.command(url, 'POST', { x, y, keyModifierFlags, @@ -68,16 +53,27 @@ export async function macosClick (opts = {}) { * Perform scroll gesture on an element or by relative/absolute coordinates * * @this {Mac2Driver} - * @param {import('../types').ScrollOptions} opts + * @param {number} deltaX Horizontal delta as float number + * @param {number} deltaY Vertical delta as float number + * @param {string} [elementId] Uuid of the element to be scrolled. 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. + * @param {number} [x] Scroll X coordinate + * @param {number} [y] Scroll Y coordinate + * @param {number} [keyModifierFlags] If set then the given key modifiers will be + * applied while scroll is performed. See + * https://developer.apple.com/documentation/xctest/xcuikeymodifierflags + * for more details */ -export async function macosScroll (opts = {}) { - const uuid = extractUuid(opts); - const { - x, y, - deltaX, deltaY, - keyModifierFlags, - } = opts; - const url = uuid ? `/wda/element/${uuid}/scroll` : '/wda/scroll'; +export async function macosScroll ( + deltaX, deltaY, + elementId, + x, y, + keyModifierFlags +) { + requireElementIdOrXY(elementId, x, y); + const url = elementId ? `/wda/element/${elementId}/scroll` : '/wda/scroll'; return await this.wda.proxy.command(url, 'POST', { deltaX, deltaY, x, y, @@ -89,17 +85,32 @@ export async function macosScroll (opts = {}) { * Perform swipe gesture on an element * * @this {Mac2Driver} - * @param {import('../types').SwipeOptions} opts + * @param {'up'|'down'|'left'|'right'} direction Swipe direction + * @param {string} [elementId] Uuid of the element to be swiped. 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. + * @param {number} [x] Swipe X coordinate + * @param {number} [y] Swipe Y coordinate + * @param {number} [velocity] The value is measured in pixels per second and same + * values could behave differently on different devices depending + * on their display density. Higher values make swipe gesture faster + * (which usually scrolls larger areas if we apply it to a list) + * and lower values slow it down. Only values greater than zero have effect. + * @param {number} [keyModifierFlags] If set then the given key modifiers will be + * applied while scroll is performed. See + * https://developer.apple.com/documentation/xctest/xcuikeymodifierflags + * for more details */ -export async function macosSwipe (opts) { - const uuid = extractUuid(opts); - const { - x, y, - direction, - velocity, - keyModifierFlags, - } = opts ?? {}; - const url = uuid ? `/wda/element/${uuid}/swipe` : `/wda/swipe`; +export async function macosSwipe ( + direction, + elementId, + x, y, + velocity, + keyModifierFlags +) { + requireElementIdOrXY(elementId, x, y); + const url = elementId ? `/wda/element/${elementId}/swipe` : `/wda/swipe`; return await this.wda.proxy.command(url, 'POST', { x, y, direction, @@ -112,12 +123,20 @@ export async function macosSwipe (opts) { * Perform right click gesture on an element or by relative/absolute coordinates * * @this {Mac2Driver} - * @param {import('../types').RightClickOptions} opts + * @param {string} [elementId] Uuid of the element to click. 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. + * @param {number} [x] Click X coordinate + * @param {number} [y] Click Y coordinate + * @param {number} [keyModifierFlags] If set then the given key modifiers will be + * applied while click is performed. See + * https://developer.apple.com/documentation/xctest/xcuikeymodifierflags + * for more details */ -export async function macosRightClick (opts = {}) { - const uuid = extractUuid(opts); - const { x, y, keyModifierFlags } = opts; - const url = uuid ? `/wda/element/${uuid}/rightClick` : '/wda/rightClick'; +export async function macosRightClick (elementId, x, y, keyModifierFlags) { + requireElementIdOrXY(elementId, x, y); + const url = elementId ? `/wda/element/${elementId}/rightClick` : '/wda/rightClick'; return await this.wda.proxy.command(url, 'POST', { x, y, keyModifierFlags, @@ -128,12 +147,20 @@ export async function macosRightClick (opts = {}) { * Perform hover gesture on an element or by relative/absolute coordinates * * @this {Mac2Driver} - * @param {import('../types').HoverOptions} opts + * @param {string} [elementId] Uuid of the element to hover. 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. + * @param {number} [x] Click X coordinate + * @param {number} [y] Click Y coordinate + * @param {number} [keyModifierFlags] If set then the given key modifiers will be + * applied while click is performed. See + * https://developer.apple.com/documentation/xctest/xcuikeymodifierflags + * for more details */ -export async function macosHover (opts = {}) { - const uuid = extractUuid(opts); - const { x, y, keyModifierFlags } = opts; - const url = uuid ? `/wda/element/${uuid}/hover` : '/wda/hover'; +export async function macosHover (elementId, x, y, keyModifierFlags) { + requireElementIdOrXY(elementId, x, y); + const url = elementId ? `/wda/element/${elementId}/hover` : '/wda/hover'; return await this.wda.proxy.command(url, 'POST', { x, y, keyModifierFlags, @@ -144,12 +171,20 @@ export async function macosHover (opts = {}) { * Perform double click gesture on an element or by relative/absolute coordinates * * @this {Mac2Driver} - * @param {import('../types').DoubleClickOptions} opts + * @param {string} [elementId] Uuid of the element to hover. 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. + * @param {number} [x] Click X coordinate + * @param {number} [y] Click Y coordinate + * @param {number} [keyModifierFlags] If set then the given key modifiers will be + * applied while click is performed. See + * https://developer.apple.com/documentation/xctest/xcuikeymodifierflags + * for more details */ -export async function macosDoubleClick (opts = {}) { - const uuid = extractUuid(opts); - const { x, y, keyModifierFlags } = opts; - const url = uuid ? `/wda/element/${uuid}/doubleClick` : '/wda/doubleClick'; +export async function macosDoubleClick (elementId, x, y, keyModifierFlags) { + requireElementIdOrXY(elementId, x, y); + const url = elementId ? `/wda/element/${elementId}/doubleClick` : '/wda/doubleClick'; return await this.wda.proxy.command(url, 'POST', { x, y, keyModifierFlags, @@ -160,21 +195,38 @@ export async function macosDoubleClick (opts = {}) { * Perform long click and drag gesture on an element or by absolute coordinates * * @this {Mac2Driver} - * @param {import('../types').ClickAndDragOptions} opts + * @param {number} duration Long click duration in float seconds + * @param {string} [sourceElementId] Uuid of the element to start the drag from. + * Either this property and `destinationElement` must be provided + * or `startX`, `startY`, `endX`, `endY` coordinates must be set. + * @param {string} [destinationElementId] Uuid of the element to end the drag on. + * Either this property and `sourceElement` must be provided or + * `startX`, `startY`, `endX`, `endY` coordinatesmust be set. + * @param {number} [startX] Starting X coordinate + * @param {number} [startY] Starting Y coordinate + * @param {number} [endX] Ending X coordinate + * @param {number} [endY] Ending Y coordinate + * @param {number} [keyModifierFlags] If set then the given key modifiers will be + * applied while drag is performed. See + * https://developer.apple.com/documentation/xctest/xcuikeymodifierflags + * for more details */ -export async function macosClickAndDrag (opts) { - const sourceUuid = extractUuid(opts, ['sourceElementId', 'sourceElement']); - const destUuid = extractUuid(opts, ['destinationElementId', 'destinationElement']); - const { - startX, startY, - endX, endY, - duration, - keyModifierFlags - } = opts ?? {}; - const url = sourceUuid && destUuid - ? `/wda/element/${sourceUuid}/clickAndDrag` +export async function macosClickAndDrag ( + duration, + sourceElementId, + destinationElementId, + startX, startY, + endX, endY, + keyModifierFlags +) { + requireSourceDestWithElementsOrCoordinates( + sourceElementId, destinationElementId, + startX, startY, endX, endY, + ); + const url = sourceElementId && destinationElementId + ? `/wda/element/${sourceElementId}/clickAndDrag` : '/wda/clickAndDrag'; - const dest = destUuid && util.wrapElement(destUuid); + const dest = destinationElementId && util.wrapElement(destinationElementId); return await this.wda.proxy.command(url, 'POST', { startX, startY, endX, endY, @@ -188,22 +240,44 @@ export async function macosClickAndDrag (opts) { * Perform long click, drag and hold gesture on an element or by absolute coordinates * * @this {Mac2Driver} - * @param {import('../types').ClickAndDragAndHoldOptions} opts - */ -export async function macosClickAndDragAndHold (opts) { - const sourceUuid = extractUuid(opts, ['sourceElementId', 'sourceElement']); - const destUuid = extractUuid(opts, ['destinationElementId', 'destinationElement']); - const { - startX, startY, - endX, endY, - duration, holdDuration, - velocity, - keyModifierFlags - } = opts ?? {}; - const url = sourceUuid && destUuid - ? `/wda/element/${sourceUuid}/clickAndDragAndHold` + * @param {number} duration Long click duration in float seconds + * @param {number} holdDuration Touch hold duration in float seconds + * @param {string} [sourceElementId] Uuid of the element to start the drag from. + * Either this property and `destinationElement` must be provided + * or `startX`, `startY`, `endX`, `endY` coordinates must be set. + * @param {string} [destinationElementId] Uuid of the element to end the drag on. + * Either this property and `sourceElement` must be provided + * or `startX`, `startY`, `endX`, `endY` coordinates must be set. + * @param {number} [startX] Starting X coordinate + * @param {number} [startY] Starting Y coordinate + * @param {number} [endX] Ending X coordinate + * @param {number} [endY] Ending Y coordinate + * @param {number} [velocity] Dragging velocity in pixels per second. + * If not provided then the default velocity is used. See + * https://developer.apple.com/documentation/xctest/xcuigesturevelocity + * for more details + * @param {number} [keyModifierFlags] If set then the given key modifiers will be + * applied while drag is performed. See + * https://developer.apple.com/documentation/xctest/xcuikeymodifierflags + * for more details */ +export async function macosClickAndDragAndHold ( + duration, + holdDuration, + sourceElementId, + destinationElementId, + startX, startY, + endX, endY, + velocity, + keyModifierFlags +) { + requireSourceDestWithElementsOrCoordinates( + sourceElementId, destinationElementId, + startX, startY, endX, endY, + ); + const url = sourceElementId && destinationElementId + ? `/wda/element/${sourceElementId}/clickAndDragAndHold` : '/wda/clickAndDragAndHold'; - const dest = destUuid && util.wrapElement(destUuid); + const dest = destinationElementId && util.wrapElement(destinationElementId); return await this.wda.proxy.command(url, 'POST', { startX, startY, endX, endY, @@ -218,28 +292,40 @@ export async function macosClickAndDragAndHold (opts) { * Send keys to the given element or to the application under test * * @this {Mac2Driver} - * @param {import('../types').KeysOptions} opts + * @param {(import('../types').KeyOptions | string)[]} keys Array of keys to type. + * Each item could either be a string, that represents a key itself (see + * https://developer.apple.com/documentation/xctest/xcuielement/1500604-typekey + * and https://developer.apple.com/documentation/xctest/xcuikeyboardkey) + * or a dictionary, if the key should also be entered with modifiers. + * @param {string} [elementId] Uuid of the element to send the keys to. + * If unset then keys are sent to the current application + * under test. */ -export async function macosKeys (opts) { - const uuid = extractUuid(opts); - const { keys } = opts ?? {}; - const url = uuid ? `/wda/element/${uuid}/keys` : '/wda/keys'; +export async function macosKeys (keys, elementId) { + const url = elementId ? `/wda/element/${elementId}/keys` : '/wda/keys'; return await this.wda.proxy.command(url, 'POST', { keys }); }; /** - * Perform press gesture on a Touch Bar element or by relative/absolute coordinates + * Perform tap gesture on a Touch Bar element or by relative/absolute coordinates * * @this {Mac2Driver} - * @param {import('../types').PressOptions} opts + * @param {string} [elementId] Uuid of the Touch Bar element to tap. 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 Touch Bar coordinates. + * @param {number} [x] Tap X coordinate + * @param {number} [y] Tap Y coordinate + * @param {number} [keyModifierFlags] If set then the given key modifiers will be + * applied while click is performed. See + * https://developer.apple.com/documentation/xctest/xcuikeymodifierflags + * for more details */ -export async function macosPressAndHold (opts) { - const uuid = extractUuid(opts); - const { x, y, duration, keyModifierFlags } = opts ?? {}; - const url = uuid ? `/wda/element/${uuid}/press` : '/wda/press'; +export async function macosTap (elementId, x, y, keyModifierFlags) { + requireElementIdOrXY(elementId, x, y); + const url = elementId ? `/wda/element/${elementId}/tap` : '/wda/tap'; return await this.wda.proxy.command(url, 'POST', { x, y, - duration, keyModifierFlags, }); }; @@ -248,12 +334,20 @@ export async function macosPressAndHold (opts) { * Perform tap gesture on a Touch Bar element or by relative/absolute coordinates * * @this {Mac2Driver} - * @param {import('../types').TapOptions} opts + * @param {string} [elementId] Uuid of the Touch Bar element to tap. 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 Touch Bar coordinates. + * @param {number} [x] Tap X coordinate + * @param {number} [y] Tap Y coordinate + * @param {number} [keyModifierFlags] If set then the given key modifiers will be + * applied while click is performed. See + * https://developer.apple.com/documentation/xctest/xcuikeymodifierflags + * for more details */ -export async function macosTap (opts = {}) { - const uuid = extractUuid(opts); - const { x, y, keyModifierFlags } = opts ?? {}; - const url = uuid ? `/wda/element/${uuid}/tap` : '/wda/tap'; +export async function macosDoubleTap (elementId, x, y, keyModifierFlags) { + requireElementIdOrXY(elementId, x, y); + const url = elementId ? `/wda/element/${elementId}/doubleTap` : '/wda/doubleTap'; return await this.wda.proxy.command(url, 'POST', { x, y, keyModifierFlags, @@ -261,17 +355,26 @@ export async function macosTap (opts = {}) { }; /** - * Perform tap gesture on a Touch Bar element or by relative/absolute coordinates + * Perform press gesture on a Touch Bar element or by relative/absolute coordinates * * @this {Mac2Driver} - * @param {import('../types').DoubleTapOptions} opts + * @param {number} duration The number of float seconds to hold the mouse button + * @param {string} [elementId] Uuid of the Touch Bar element to be pressed. 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 Touch Bar coordinates. + * @param {number} [x] Press X coordinate + * @param {number} [y] Press Y coordinate + * @param {number} [keyModifierFlags] If set then the given key modifiers will be + * applied while click is performed. See + * https://developer.apple.com/documentation/xctest/xcuikeymodifierflags + * for more details */ -export async function macosDoubleTap (opts = {}) { - const uuid = extractUuid(opts); - const { x, y, keyModifierFlags } = opts; - const url = uuid ? `/wda/element/${uuid}/doubleTap` : '/wda/doubleTap'; +export async function macosPressAndHold (duration, elementId, x, y, keyModifierFlags) { + const url = elementId ? `/wda/element/${elementId}/press` : '/wda/press'; return await this.wda.proxy.command(url, 'POST', { x, y, + duration, keyModifierFlags, }); }; @@ -280,21 +383,38 @@ export async function macosDoubleTap (opts = {}) { * Perform long press and drag gesture on a Touch Bar element or by absolute coordinates * * @this {Mac2Driver} - * @param {import('../types').PressAndDragOptions} opts + * @param {number} duration Long press duration in float seconds + * @param {string} [sourceElementId] Uuid of a Touch Bar element to start the drag from. + * Either this property and `destinationElement` must be provided or + * `startX`, `startY`, `endX`, `endY` coordinates must be set. + * @param {string} [destinationElementId] Uuid of a Touch Bar element to end the drag on. + * Either this property and `sourceElement` must be provided or + * `startX`, `startY`, `endX`, `endY` coordinates must be set. + * @param {number} [startX] Starting X coordinate + * @param {number} [startY] Starting Y coordinate + * @param {number} [endX] Ending X coordinate + * @param {number} [endY] Ending Y coordinate + * @param {number} [keyModifierFlags] If set then the given key modifiers will be + * applied while drag is performed. See + * https://developer.apple.com/documentation/xctest/xcuikeymodifierflags + * for more details */ -export async function macosPressAndDrag (opts) { - const sourceUuid = extractUuid(opts, ['sourceElementId', 'sourceElement']); - const destUuid = extractUuid(opts, ['destinationElementId', 'destinationElement']); - const { - startX, startY, - endX, endY, - duration, - keyModifierFlags - } = opts ?? {}; - const url = sourceUuid && destUuid - ? `/wda/element/${sourceUuid}/pressAndDrag` +export async function macosPressAndDrag ( + duration, + sourceElementId, + destinationElementId, + startX, startY, + endX, endY, + keyModifierFlags +) { + // requireSourceDestWithElementsOrCoordinates( + // sourceElementId, destinationElementId, + // startX, startY, endX, endY, + // ); + const url = sourceElementId && destinationElementId + ? `/wda/element/${sourceElementId}/pressAndDrag` : '/wda/pressAndDrag'; - const dest = destUuid && util.wrapElement(destUuid); + const dest = destinationElementId && util.wrapElement(destinationElementId); return await this.wda.proxy.command(url, 'POST', { startX, startY, endX, endY, @@ -308,22 +428,45 @@ export async function macosPressAndDrag (opts) { * Perform press, drag and hold gesture on a Touch Bar element or by absolute Touch Bar coordinates * * @this {Mac2Driver} - * @param {import('../types').PressAndDragAndHoldOptions} opts + * @param {number} duration Long press duration in float seconds + * @param {number} holdDuration Touch hold duration in float seconds + * @param {string} [sourceElementId] Uuid of a Touch Bar element to start the drag from. + * Either this property and `destinationElement` must be provided or + * `startX`, `startY`, `endX`, `endY` coordinates must be set. + * @param {string} [destinationElementId] Uuid of a Touch Bar element to end the drag on. + * Either this property and `sourceElement` must be provided or + * `startX`, `startY`, `endX`, `endY` coordinates must be set. + * @param {number} [startX] Starting X coordinate + * @param {number} [startY] Starting Y coordinate + * @param {number} [endX] Ending X coordinate + * @param {number} [endY] Ending Y coordinate + * @param {number} [velocity] Dragging velocity in pixels per second. + * If not provided then the default velocity is used. See + * https://developer.apple.com/documentation/xctest/xcuigesturevelocity + * for more details + * @param {number} [keyModifierFlags] If set then the given key modifiers will be + * applied while drag is performed. See + * https://developer.apple.com/documentation/xctest/xcuikeymodifierflags + * for more details */ -export async function macosPressAndDragAndHold (opts) { - const sourceUuid = extractUuid(opts, ['sourceElementId', 'sourceElement']); - const destUuid = extractUuid(opts, ['destinationElementId', 'destinationElement']); - const { - startX, startY, - endX, endY, - duration, holdDuration, - velocity, - keyModifierFlags - } = opts ?? {}; - const url = sourceUuid && destUuid - ? `/wda/element/${sourceUuid}/pressAndDragAndHold` +export async function macosPressAndDragAndHold ( + duration, + holdDuration, + sourceElementId, + destinationElementId, + startX, startY, + endX, endY, + velocity, + keyModifierFlags +) { + requireSourceDestWithElementsOrCoordinates( + sourceElementId, destinationElementId, + startX, startY, endX, endY, + ); + const url = sourceElementId && destinationElementId + ? `/wda/element/${sourceElementId}/pressAndDragAndHold` : '/wda/pressAndDragAndHold'; - const dest = destUuid && util.wrapElement(destUuid); + const dest = destinationElementId && util.wrapElement(destinationElementId); return await this.wda.proxy.command(url, 'POST', { startX, startY, endX, endY, @@ -334,6 +477,43 @@ export async function macosPressAndDragAndHold (opts) { }); }; +/** + * Raise invalid argument error if element id was unset or x and y were unset. + * @param {string} [elementId] + * @param {number} [x] + * @param {number} [y] + * @returns {void} + */ +function requireElementIdOrXY (elementId, x, y) { + if (!_.isString(elementId) && !(_.isNumber(x) && _.isNumber(y))) { + throw new errors.InvalidArgumentError(`'elementId' or 'x' and 'y' is required.`); + } +} + +/** + * Raise invalid argument error if sourceElementId and destinationElementId were unset + * or startX, startY, endX and endY were unset. + * @param {string} [sourceElementId] + * @param {string} [destinationElementId] + * @param {number} [startX] + * @param {number} [startY] + * @param {number} [endX] + * @param {number} [endY] + * @returns {void} + */ +function requireSourceDestWithElementsOrCoordinates ( + sourceElementId, destinationElementId, + startX, startY, endX, endY +) { + if ( + !(_.isString(sourceElementId) && _.isString(destinationElementId)) + && !(_.isNumber(startX) && _.isNumber(startY) && _.isNumber(endX) && _.isNumber(endY)) + ) { + throw new errors.InvalidArgumentError(`'sourceElementId' and 'destinationElementId' ` + + `or 'startX', 'startY', 'endX' and 'endY' are required.`); + } +} + /** * @typedef {import('../driver').Mac2Driver} Mac2Driver */ diff --git a/lib/commands/navigation.js b/lib/commands/navigation.js index 182e252..6adee00 100644 --- a/lib/commands/navigation.js +++ b/lib/commands/navigation.js @@ -3,11 +3,13 @@ * Xcode must be at version 14.3+. * * @this {Mac2Driver} - * @param {import('../types').DeepLinkOptions} opts + * @param {string} url The URL to be opened. + * @param {string} [bundleId] The bundle identifier of an application to open + * the given url with. If not provided then the default application + * for the given url scheme is going to be used. * @returns {Promise} */ -export async function macosDeepLink (opts) { - const {url, bundleId} = opts; +export async function macosDeepLink (url, bundleId) { return await this.wda.proxy.command('/url', 'POST', {url, bundleId}); }; diff --git a/lib/commands/record-screen.js b/lib/commands/record-screen.js index ef33571..5dc2644 100644 --- a/lib/commands/record-screen.js +++ b/lib/commands/record-screen.js @@ -209,22 +209,44 @@ class ScreenRecorder { * in System Preferences->Security & Privacy->Screen Recording. * The resulting video uses H264 codec and is ready to be played by media players built-in into web browsers. * - * @param {import('../types').StartRecordingOptions} options - The available options. * @this {Mac2Driver} + * @param {string|number} deviceId Screen device index to use for the recording. + * The list of available devices could be retrieved using + * `ffmpeg -f avfoundation -list_devices true -i` command. + * @param {string|number} [timeLimit] The maximum recording time, in seconds. The default + * value is 600 seconds (10 minutes). + * @param {string} [videoFilter] The video filter spec to apply for ffmpeg. + * See https://trac.ffmpeg.org/wiki/FilteringGuide for more details on the possible values. + * Example: Set it to `scale=ifnot(gte(iw\,1024)\,iw\,1024):-2` in order to limit the video width + * to 1024px. The height will be adjusted automatically to match the actual ratio. + * @param {string|number} [fps] The count of frames per second in the resulting video. + * The greater fps it has the bigger file size is. 15 by default. + * @param {'ultrafast'|'superfast'|'veryfast'|'faster'|'fast'|'medium'|'slow'|'slower'|'veryslow'} [preset] + * One of the supported encoding presets. A preset is a collection of options that will provide a + * certain encoding speed to compression ratio. + * A slower preset will provide better compression (compression is quality per filesize). + * This means that, for example, if you target a certain file size or constant bit rate, you will + * achieve better quality with a slower preset. Read https://trac.ffmpeg.org/wiki/Encode/H.264 for more details. + * `veryfast` by default + * @param {boolean} [captureCursor] Whether to capture the mouse cursor while recording the screen. + * False by default + * @param {boolean} [captureClicks] Whether to capture mouse clicks while recording the screen. + * False by default. + * @param {boolean} [forceRestart=true] Whether to ignore the call if a screen recording is currently running + * (`false`) or to start a new recording immediately and terminate the existing one + * if running (`true`). The default value is `true`. * @throws {Error} If screen recording has failed to start or is not supported on the device under test. */ -export async function startRecordingScreen (options) { - const { - timeLimit, - videoFilter, - fps, - preset, - captureCursor, - captureClicks, - deviceId, - forceRestart = true, - } = options ?? {}; - +export async function startRecordingScreen ( + deviceId, + timeLimit, + videoFilter, + fps, + preset, + captureCursor, + captureClicks, + forceRestart = true +) { if (_.isNil(deviceId)) { throw new Error(`'deviceId' option must be provided. Run 'ffmpeg -f avfoundation -list_devices true -i' ` + 'to fetch the list of available device ids'); @@ -266,7 +288,21 @@ export async function startRecordingScreen (options) { * Stop recording the screen. * If no screen recording has been started before then the method returns an empty string. * - * @param {import('../types').StopRecordingOptions} [options={}] - The available options. + * @param {string} [remotePath] The path to the remote location, where the resulting video should be uploaded. + * The following protocols are supported: http/https, ftp. + * Null or empty string value (the default setting) means the content of resulting + * file should be encoded as Base64 and passed as the endpoint response value. + * An exception will be thrown if the generated media file is too big to + * fit into the available process memory. + * @param {string} [user] The name of the user for the remote authentication. + * @param {string} [pass] The password for the remote authentication. + * @param {string} [method] The http multipart upload method name. The 'PUT' one is used by default. + * @param {import('@appium/types').StringRecord|[string, any][]} [headers] + * Additional headers mapping for multipart http(s) uploads + * @param {string} [fileFieldName] The name of the form field, where the file content BLOB should + * be stored for http(s) uploads + * @param {import('@appium/types').StringRecord|[string, string][]} [formFields] + * Additional form fields for multipart http(s) uploads * @returns {Promise} Base64-encoded content of the recorded media file if 'remotePath' * parameter is falsy or an empty string. * @this {Mac2Driver} @@ -274,7 +310,15 @@ export async function startRecordingScreen (options) { * or the file content cannot be uploaded to the remote location * or screen recording is not supported on the device under test. */ -export async function stopRecordingScreen (options = {}) { +export async function stopRecordingScreen ( + remotePath, + user, + pass, + method, + headers, + fileFieldName, + formFields +) { if (!this._screenRecorder) { this.log.debug('No screen recording has been started. Doing nothing'); return ''; @@ -286,7 +330,15 @@ export async function stopRecordingScreen (options = {}) { this.log.debug('No video data is found. Returning an empty string'); return ''; } - return await uploadRecordedMedia.bind(this)(videoPath, options.remotePath, options); + const options = { + user, + pass, + method, + headers, + fileFieldName, + formFields + }; + return await uploadRecordedMedia.bind(this)(videoPath, remotePath, options); }; /** diff --git a/lib/commands/screenshots.js b/lib/commands/screenshots.js index 537f5f3..793fedf 100644 --- a/lib/commands/screenshots.js +++ b/lib/commands/screenshots.js @@ -2,11 +2,12 @@ * Retrieves screenshots of each display available to macOS * * @this {Mac2Driver} - * @param {import('../types').ScreenshotsOpts} [opts={}] + * @param {number} [displayId] macOS 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 macosScreenshots (opts = {}) { - const {displayId} = opts; +export async function macosScreenshots (displayId) { return /** @type {import('../types').ScreenshotsInfo} */ ( await this.wda.proxy.command('/wda/screenshots', 'POST', {displayId}) ); diff --git a/lib/commands/source.js b/lib/commands/source.js index b8dc622..8318a52 100644 --- a/lib/commands/source.js +++ b/lib/commands/source.js @@ -2,13 +2,15 @@ * Retrieves the string representation of the current application * * @this {Mac2Driver} - * @param {import('../types').SourceOptions} [opts={}] + * @param {string} [format='xml'] The format of the application source to retrieve. + * Only two formats are supported: + * - xml: Returns the source formatted as XML document (the default setting) + * - description: Returns the source formatted as debugDescription output. + * See https://developer.apple.com/documentation/xctest/xcuielement/1500909-debugdescription?language=objc + * for more details. * @returns {Promise} the page source in the requested format */ -export async function macosSource (opts = {}) { - const { - format = 'xml', - } = opts; +export async function macosSource (format = 'xml') { return /** @type {String} */ ( await this.wda.proxy.command(`/source?format=${encodeURIComponent(format)}`, 'GET') ); diff --git a/lib/driver.js b/lib/driver.js index 73f0c11..29666c0 100644 --- a/lib/driver.js +++ b/lib/driver.js @@ -13,6 +13,7 @@ import * as screenshotCommands from './commands/screenshots'; import * as sourceCommands from './commands/source'; import log from './logger'; import { newMethodMap } from './method-map'; +import { executeMethodMap } from './execute-method-map'; /** @type {import('@appium/types').RouteMatcher[]} */ const NO_PROXY = [ @@ -34,6 +35,7 @@ export class Mac2Driver extends BaseDriver { wda; static newMethodMap = newMethodMap; + static executeMethodMap = executeMethodMap; constructor (opts = {}) { // @ts-ignore TODO: Make opts typed @@ -160,7 +162,6 @@ export class Mac2Driver extends BaseDriver { macosExecAppleScript = appleScriptCommands.macosExecAppleScript; - executeMacosCommand = executeCommands.executeMacosCommand; execute = executeCommands.execute; findElOrEls = findCommands.findElOrEls; diff --git a/lib/execute-method-map.ts b/lib/execute-method-map.ts new file mode 100644 index 0000000..1f2fbe4 --- /dev/null +++ b/lib/execute-method-map.ts @@ -0,0 +1,305 @@ +import { ExecuteMethodMap } from '@appium/types'; + +export const executeMethodMap = { + 'macos: click': { + command: 'macosClick', + params: { + optional: [ + 'elementId', + 'x', + 'y', + 'keyModifierFlags', + ], + }, + }, + 'macos: scroll': { + command: 'macosScroll', + params: { + required: [ + 'deltaX', + 'deltaY', + ], + optional: [ + 'elementId', + 'x', + 'y', + 'keyModifierFlags', + ], + }, + }, + 'macos: swipe': { + command: 'macosSwipe', + params: { + required: [ + 'direction', + ], + optional: [ + 'elementId', + 'x', + 'y', + 'velocity', + 'keyModifierFlags', + ], + }, + }, + 'macos: rightClick': { + command: 'macosRightClick', + params: { + optional: [ + 'elementId', + 'x', + 'y', + 'keyModifierFlags', + ], + }, + }, + 'macos: hover': { + command: 'macosHover', + params: { + optional: [ + 'elementId', + 'x', + 'y', + 'keyModifierFlags', + ], + }, + }, + 'macos: doubleClick': { + command: 'macosDoubleClick', + params: { + optional: [ + 'elementId', + 'x', + 'y', + 'keyModifierFlags', + ], + }, + }, + 'macos: clickAndDrag': { + command: 'macosClickAndDrag', + params: { + required: [ + 'duration', + ], + optional: [ + 'sourceElementId', + 'destinationElementId', + 'startX', + 'startY', + 'endX', + 'endY', + 'keyModifierFlags', + ], + }, + }, + 'macos: clickAndDragAndHold': { + command: 'macosClickAndDragAndHold', + params: { + required: [ + 'duration', + 'holdDuration', + ], + optional: [ + 'sourceElementId', + 'destinationElementId', + 'startX', + 'startY', + 'endX', + 'endY', + 'velocity', + 'keyModifierFlags', + ], + }, + }, + 'macos: keys': { + command: 'macosKeys', + params: { + required: [ + 'keys' + ], + optional: [ + 'elementId' + ], + }, + }, + 'macos: tap': { + command: 'macosTap', + params: { + optional: [ + 'elementId', + 'x', + 'y', + 'keyModifierFlags', + ], + }, + }, + 'macos: doubleTap': { + command: 'macosDoubleTap', + params: { + optional: [ + 'elementId', + 'x', + 'y', + 'keyModifierFlags', + ], + }, + }, + 'macos: press': { + command: 'macosPressAndHold', + params: { + required: [ + 'duration', + ], + optional: [ + 'elementId', + 'x', + 'y', + 'keyModifierFlags', + ], + }, + }, + 'macos: pressAndDrag': { + command: 'macosPressAndDrag', + params: { + required: [ + 'duration', + ], + optional: [ + 'sourceElementId', + 'destinationElementId', + 'startX', + 'startY', + 'endX', + 'endY', + 'keyModifierFlags', + ], + }, + }, + 'macos: pressAndDragAndHold': { + command: 'macosPressAndDragAndHold', + params: { + required: [ + 'duration', + 'holdDuration', + ], + optional: [ + 'sourceElementId', + 'destinationElementId', + 'startX', + 'startY', + 'endX', + 'endY', + 'velocity', + 'keyModifierFlags', + ], + }, + }, + 'macos: source': { + command: 'macosSource', + params: { + optional: [ + 'format', + ], + }, + }, + 'macos: deepLink': { + command: 'macosDeepLink', + params: { + required: [ + 'url', + ], + optional: [ + 'bundleId' + ], + }, + }, + 'macos: screenshots': { + command: 'macosScreenshots', + params: { + optional: [ + 'displayId' + ], + }, + }, + 'macos: appleScript': { + command: 'macosExecAppleScript', + params: { + optional: [ + 'script', + 'language', + 'command', + 'cwd', + 'timeout', + ], + }, + }, + 'macos: launchApp': { + command: 'macosLaunchApp', + params: { + optional: [ + 'bundleId', + 'path', + 'arguments', + 'environment', + ], + }, + }, + 'macos: activateApp': { + command: 'macosActivateApp', + params: { + optional: [ + 'bundleId', + 'path', + ], + }, + }, + 'macos: terminateApp': { + command: 'macosTerminateApp', + params: { + optional: [ + 'bundleId', + 'path', + ], + }, + }, + 'macos: queryAppState': { + command: 'macosQueryAppState', + params: { + optional: [ + 'bundleId', + 'path', + ], + }, + }, + 'macos: startRecordingScreen': { + command: 'startRecordingScreen', + params: { + required: [ + 'deviceId' + ], + optional: [ + 'timeLimit', + 'videoFilter', + 'fps', + 'preset', + 'captureCursor', + 'captureClicks', + 'forceRestart' + ], + }, + }, + 'macos: stopRecordingScreen': { + command: 'stopRecordingScreen', + params: { + optional: [ + 'remotePath', + 'user', + 'pass', + 'method', + 'headers', + 'fileFieldName', + 'formFields' + ], + }, + }, +} as const satisfies ExecuteMethodMap; diff --git a/lib/types.ts b/lib/types.ts index 3a4f338..79432b1 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -1,382 +1,5 @@ import type { StringRecord } from '@appium/types'; -export interface LaunchAppOptions { - /** - * Bundle identifier of the app to be launched - * or activated. Either this property or `path` must be provided - */ - bundleId?: string; - /** - * Full path to the app bundle. Either this property - * or `bundleId` must be provided - */ - path?: string; - /** - * The list of command line arguments - * for the app to be be launched with. This parameter is ignored if the app - * is already running. - */ - arguments?: string[]; - /** - * Environment variables mapping. Custom - * variables are added to the default process environment. - */ - environment?: StringRecord; -} - -export interface ActivateAppOptions { - /** - * Bundle identifier of the app to be activated. - * Either this property or `path` must be provided - */ - bundleId?: string; - /** - * Full path to the app bundle. Either this property - * or `bundleId` must be provided - */ - path?: string; -} - -export interface TerminateAppOptions { - /** - * Bundle identifier of the app to be terminated. - * Either this property or `path` must be provided - */ - bundleId?: string; - /** - * Full path to the app bundle. Either this property - * or `bundleId` must be provided - */ - path?: string; -} - -export interface QueryAppStateOptions { - /** - * Bundle identifier of the app whose state should be queried. - * Either this property or `path` must be provided - */ - bundleId?: string; - /** - * Full path to the app bundle. Either this property - * or `bundleId` must be provided - */ - path?: string; -} - -export interface ExecAppleScriptOptions { - /** - * A valid AppleScript to execute - */ - script?: string; - /** - * Overrides the scripting language. Basically, sets the value of `-l` command - * line argument of `osascript` tool. If unset the AppleScript language is assumed. - */ - language?: string; - /** - * A valid AppleScript as a single command (no line breaks) to execute - */ - command?: string; - /** - * [20000] 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. - */ - timeout?: number; - /** - * The path to an existing folder, which is going to be set as the - * working directory for the command/script being executed. - */ - cwd?: string; -} - -export interface SetValueOptions { - /** - * Uuid of the element to set value for - */ - elementId: string; - /** - * Value to set. Could also be an array - */ - value?: any; - /** - * Text to set. If both value and text are set - * then `value` is preferred - */ - text?: string; - /** - * If set then the given key modifiers will be - * applied while the element value is being set. See - * https://developer.apple.com/documentation/xctest/xcuikeymodifierflags - * for more details - */ - keyModifierFlags?: number; -} - -export interface ClickOptions { - /** - * Uuid of the element to click. 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. - */ - elementId?: string; - /** - * Click X coordinate - */ - x?: number; - /** - * Click Y coordinate - */ - y?: number; - /** - * If set then the given key modifiers will be - * applied while click is performed. See - * https://developer.apple.com/documentation/xctest/xcuikeymodifierflags - * for more details - */ - keyModifierFlags?: number; -} - -export interface ScrollOptions { - /** - * Uuid of the element to be scrolled. 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. - */ - elementId?: string; - /** - * Scroll X coordinate - */ - x?: number; - /** - * Scroll Y coordinate - */ - y?: number; - /** - * Horizontal delta as float number - */ - deltaX?: number; - /** - * Vertical delta as float number - */ - deltaY?: number; - /** - * If set then the given key modifiers will be - * applied while scroll is performed. See - * https://developer.apple.com/documentation/xctest/xcuikeymodifierflags - * for more details - */ - keyModifierFlags?: number; -} - -export interface SwipeOptions { - /** - * Uuid of the element to be swiped. 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. - */ - elementId?: string; - /** - * Swipe X coordinate - */ - x?: number; - /** - * Swipe Y coordinate - */ - y?: number; - /** - * Swipe direction - */ - direction: 'up'|'down'|'left'|'right'; - /** - * The value is measured in pixels per second and same - * values could behave differently on different devices depending on their display - * density. Higher values make swipe gesture faster (which usually scrolls larger - * areas if we apply it to a list) and lower values slow it down. - * Only values greater than zero have effect. - */ - velocity?: number; - /** - * If set then the given key modifiers will be - * applied while scroll is performed. See - * https://developer.apple.com/documentation/xctest/xcuikeymodifierflags - * for more details - */ - keyModifierFlags?: number; -} - -export interface RightClickOptions { - /** - * Uuid of the element to click. 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. - */ - elementId?: string; - /** - * Click X coordinate - */ - x?: number; - /** - * Click Y coordinate - */ - y?: number; - /** - * If set then the given key modifiers will be - * applied while click is performed. See - * https://developer.apple.com/documentation/xctest/xcuikeymodifierflags - * for more details - */ - keyModifierFlags?: number; -} - -export interface HoverOptions { - /** - * Uuid of the element to hover. 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. - */ - elementId?: string; - /** - * Hover X coordinate - */ - x?: number; - /** - * Hover Y coordinate - */ - y?: number; - /** - * If set then the given key modifiers will be - * applied while hover is performed. See - * https://developer.apple.com/documentation/xctest/xcuikeymodifierflags - * for more details - */ - keyModifierFlags?: number; -} - -export interface DoubleClickOptions { - /** - * Uuid of the element to double click. 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. - */ - elementId?: string; - /** - * Click X coordinate - */ - x?: number; - /** - * Click Y coordinate - */ - y?: number; - /** - * If set then the given key modifiers will be - * applied while double click is performed. See - * https://developer.apple.com/documentation/xctest/xcuikeymodifierflags - * for more details - */ - keyModifierFlags?: number; -} - -export interface ClickAndDragOptions { - /** - * Uuid of the element to start the drag from. Either this property - * and `destinationElement` must be provided or `startX`, `startY`, `endX`, `endY` coordinates - * must be set. - */ - sourceElementId?: string; - /** - * Uuid of the element to end the drag on. Either this property - * and `sourceElement` must be provided or `startX`, `startY`, `endX`, `endY` coordinates - * must be set. - */ - destinationElementId?: string; - /** - * Starting X coordinate - */ - startX?: number; - /** - * Starting Y coordinate - */ - startY?: number; - /** - * Ending X coordinate - */ - endX?: number; - /** - * Ending Y coordinate - */ - endY?: number; - /** - * Long click duration in float seconds - */ - duration: number; - /** - * If set then the given key modifiers will be - * applied while drag is performed. See - * https://developer.apple.com/documentation/xctest/xcuikeymodifierflags - * for more details - */ - keyModifierFlags?: number; -} - -export interface ClickAndDragAndHoldOptions { - /** - * Uuid of the element to start the drag from. Either this property - * and `destinationElement` must be provided or `startX`, `startY`, `endX`, `endY` coordinates - * must be set. - */ - sourceElementId?: string; - /** - * Uuid of the element to end the drag on. Either this property - * and `sourceElement` must be provided or `startX`, `startY`, `endX`, `endY` coordinates - * must be set. - */ - destinationElementId?: string; - /** - * Starting X coordinate - */ - startX?: number; - /** - * Starting Y coordinate - */ - startY?: number; - /** - * Ending X coordinate - */ - endX?: number; - /** - * Ending Y coordinate - */ - endY?: number; - /** - * Long click duration in float seconds - */ - duration: number; - /** - * Touch hold duration in float seconds - */ - holdDuration: number; - /** - * Dragging velocity in pixels per second. - * If not provided then the default velocity is used. See - * https://developer.apple.com/documentation/xctest/xcuigesturevelocity - * for more details - */ - velocity?: number; - /** - * If set then the given key modifiers will be - * applied while drag is performed. See - * https://developer.apple.com/documentation/xctest/xcuikeymodifierflags - * for more details - */ - keyModifierFlags?: number; -} - export interface KeyOptions { /** * A string, that represents a key to type (see @@ -392,268 +15,6 @@ export interface KeyOptions { modifierFlags?: number; } -export interface KeysOptions { - /** - * Uuid of the element to send keys to. - * If the element is not provided then the keys will be sent to the current application. - */ - elementId?: string; - /** - * Array of keys to type. - * Each item could either be a string, that represents a key itself (see - * https://developer.apple.com/documentation/xctest/xcuielement/1500604-typekey?language=objc - * and https://developer.apple.com/documentation/xctest/xcuikeyboardkey?language=objc) - * or a dictionary, if the key should also be entered with modifiers. - */ - keys: (KeyOptions | string)[]; -} - -export interface PressOptions { - /** - * Uuid of the Touch Bar element to be pressed. 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 Touch Bar coordinates. - */ - elementId?: string; - /** Press X coordinate */ - x?: number; - /** Press Y coordinate */ - y?: number; - /** - * The number of float seconds to hold the mouse button - */ - duration: number; - /** - * If set then the given key modifiers will be - * applied while click is performed. See - * https://developer.apple.com/documentation/xctest/xcuikeymodifierflags - * for more details - */ - keyModifierFlags?: number; -} - -export interface TapOptions { - /** - * Uuid of the Touch Bar element to tap. 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 Touch Bar coordinates. - */ - elementId?: string; - /** - * Tap X coordinate - */ - x?: number; - /** - * Tap Y coordinate - */ - y?: number; - /** - * If set then the given key modifiers will be - * applied while click is performed. See - * https://developer.apple.com/documentation/xctest/xcuikeymodifierflags - * for more details - */ - keyModifierFlags?: number; -} - -export interface DoubleTapOptions { - /** - * Uuid of the Touch Bar element to tap. 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 Touch Bar coordinates. - */ - elementId?: string; - /** - * Tap X coordinate - */ - x?: number; - /** - * Tap Y coordinate - */ - y?: number; - /** - * If set then the given key modifiers will be - * applied while click is performed. See - * https://developer.apple.com/documentation/xctest/xcuikeymodifierflags - * for more details - */ - keyModifierFlags?: number; -} - -export interface PressAndDragOptions { - /** - * Uuid of a Touch Bar element to start the drag from. Either this property - * and `destinationElement` must be provided or `startX`, `startY`, `endX`, `endY` coordinates - * must be set. - */ - sourceElementId?: string; - /** - * Uuid of a Touch Bar element to end the drag on. Either this property - * and `sourceElement` must be provided or `startX`, `startY`, `endX`, `endY` coordinates - * must be set. - */ - destinationElementId?: string; - /** Starting X coordinate */ - startX?: number; - /** Starting Y coordinate */ - startY?: number; - /** Ending X coordinate */ - endX?: number; - /** Ending Y coordinate */ - endY?: number; - /** Long press duration in float seconds */ - duration: number; - /** - * If set then the given key modifiers will be - * applied while drag is performed. See - * https://developer.apple.com/documentation/xctest/xcuikeymodifierflags - * for more details - */ - keyModifierFlags?: number; -} - -export interface PressAndDragAndHoldOptions { - /** - * Uuid of a Touch Bar element to start the drag from. Either this property - * and `destinationElement` must be provided or `startX`, `startY`, `endX`, `endY` coordinates - * must be set. - */ - sourceElementId?: string; - /** - * Uuid of a Touch Bar element to end the drag on. Either this property - * and `sourceElement` must be provided or `startX`, `startY`, `endX`, `endY` coordinates - * must be set. - */ - destinationElementId?: string; - /** Starting X coordinate */ - startX?: number; - /** Starting Y coordinate */ - startY?: number; - /** Ending X coordinate */ - endX?: number; - /** Ending Y coordinate */ - endY?: number; - /** Long press duration in float seconds */ - duration: number; - /** Touch hold duration in float seconds */ - holdDuration: number; - /** - * Dragging velocity in pixels per second. - * If not provided then the default velocity is used. See - * https://developer.apple.com/documentation/xctest/xcuigesturevelocity - * for more details - */ - velocity?: number; - /** - * If set then the given key modifiers will be - * applied while drag is performed. See - * https://developer.apple.com/documentation/xctest/xcuikeymodifierflags - * for more details - */ - keyModifierFlags?: number; -} - -export interface DeepLinkOptions { - /** The URL to be opened. This parameter is manadatory. */ - url: string; - /** - * The bundle identifier of an application to open the given url with. - * If not provided then the default application for the given url scheme is going to be used. - */ - bundleId?: string; -} - -export interface StopRecordingOptions { - /** - * The path to the remote location, where the resulting video should be uploaded. - * The following protocols are supported: http/https, ftp. - * Null or empty string value (the default setting) means the content of resulting - * file should be encoded as Base64 and passed as the endpoint response value. - * An exception will be thrown if the generated media file is too big to - * fit into the available process memory. - */ - remotePath?: string; - /** - * The name of the user for the remote authentication. - */ - user?: string; - /** - * The password for the remote authentication. - */ - pass?: string; - /** - * The http multipart upload method name. The 'PUT' one is used by default. - */ - method?: string; - /** - * Additional headers mapping for multipart http(s) uploads - */ - headers?: StringRecord | [string, any][]; - /** - * [file] The name of the form field, where the file content BLOB should be stored for - * http(s) uploads - */ - fileFieldName?: string; - /** - * Additional form fields for multipart http(s) uploads - */ - formFields?: StringRecord | [string, string][]; -} - -export interface StartRecordingOptions { - /** - * The video filter spec to apply for ffmpeg. - * See https://trac.ffmpeg.org/wiki/FilteringGuide for more details on the possible values. - * Example: Set it to `scale=ifnot(gte(iw\,1024)\,iw\,1024):-2` in order to limit the video width - * to 1024px. The height will be adjusted automatically to match the actual ratio. - */ - videoFilter?: string; - /** - * The count of frames per second in the resulting video. - * The greater fps it has the bigger file size is. 15 by default. - */ - fps?: number | string; - /** - * One of the supported encoding presets. A preset is a collection of options that will provide a - * certain encoding speed to compression ratio. - * A slower preset will provide better compression (compression is quality per filesize). - * This means that, for example, if you target a certain file size or constant bit rate, you will achieve better - * quality with a slower preset. Read https://trac.ffmpeg.org/wiki/Encode/H.264 for more details. - * `veryfast` by default - */ - preset?: 'ultrafast'|'superfast'|'veryfast'|'faster'|'fast'|'medium'|'slow'|'slower'|'veryslow'; - /** - * Whether to capture the mouse cursor while recording - * the screen. false by default - */ - captureCursor?: boolean; - /** - * Whether to capture mouse clicks while recording the - * screen. False by default. - */ - captureClicks?: boolean; - /** - * Screen device index to use for the recording. - * The list of available devices could be retrieved using - * `ffmpeg -f avfoundation -list_devices true -i` command. - */ - deviceId: string | number; - /** - * The maximum recording time, in seconds. The default - * value is 600 seconds (10 minutes). - */ - timeLimit?: string | number; - /** - * Whether to ignore the call if a screen recording is currently running - * (`false`) or to start a new recording immediately and terminate the existing one if running (`true`). - * The default value is `true`. - */ - forceRestart?: boolean; -} - export interface ScreenshotInfo { /** Display identifier */ id: number; @@ -665,24 +26,3 @@ export interface ScreenshotInfo { /** A dictionary where each key contains a unique display identifier */ export type ScreenshotsInfo = StringRecord; - -export interface ScreenshotsOpts { - /** - * macOS 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; -} - -export interface SourceOptions { - /** - * The format of the application source to retrieve. - * Only two formats are supported: - * - xml: Returns the source formatted as XML document (the default setting) - * - description: Returns the source formatted as debugDescription output. - * See https://developer.apple.com/documentation/xctest/xcuielement/1500909-debugdescription?language=objc - * for more details. - */ - format?: 'xml'|'description'; -}