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: Improve the typing system #298

Merged
merged 1 commit into from
Apr 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
75 changes: 23 additions & 52 deletions lib/commands/app-management.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,13 @@
const commands = {};

/**
* @typedef {Object} LaunchAppOptions
* @property {string} [bundleId] Bundle identifier of the app to be launched
* or activated. Either this property or `path` must be provided
* @property {string} [path] Full path to the app bundle. Either this property
* or `bundleId` must be provided
* @property {string[]} arguments the list of command line arguments
* for the app to be be launched with. This parameter is ignored if the app
* is already running.
* @property {Object} environment environment variables mapping. Custom
* variables are added to the default process environment.
*/

/**
* Start an app with given bundle identifier or activates it
* if the app is already running. An exception is thrown if the
* app with the given identifier cannot be found.
*
* @param {LaunchAppOptions} opts
* @this {Mac2Driver}
* @param {import('../types').LaunchAppOptions} [opts={}]
*/
commands.macosLaunchApp = async function macosLaunchApp (opts) {
const { bundleId, environment, path } = opts ?? {};
export async function macosLaunchApp (opts = {}) {
const { bundleId, environment, path } = opts;
return await this.wda.proxy.command('/wda/apps/launch', 'POST', {
arguments: opts.arguments,
environment,
Expand All @@ -30,66 +16,51 @@ commands.macosLaunchApp = async function macosLaunchApp (opts) {
});
};

/**
* @typedef {Object} ActivateAppOptions
* @property {string} [bundleId] Bundle identifier of the app to be activated.
* Either this property or `path` must be provided
* @property {string} [path] Full path to the app bundle. Either this property
* or `bundleId` must be provided
*/

/**
* Activate an app with given bundle identifier. An exception is thrown if the
* app cannot be found or is not running.
*
* @param {ActivateAppOptions} opts
* @this {Mac2Driver}
* @param {import('../types').ActivateAppOptions} [opts={}]
*/
commands.macosActivateApp = async function macosActivateApp (opts) {
const { bundleId, path } = opts ?? {};
export async function macosActivateApp (opts = {}) {
const { bundleId, path } = opts;
return await this.wda.proxy.command('/wda/apps/activate', 'POST', { bundleId, path });
};

/**
* @typedef {Object} TerminateAppOptions
* @property {string} [bundleId] Bundle identifier of the app to be terminated.
* Either this property or `path` must be provided
* @property {string} [path] Full path to the app bundle. Either this property
* or `bundleId` must be provided
*/

/**
* Terminate an app with given bundle identifier. An exception is thrown if the
* app cannot be found.
*
* @param {TerminateAppOptions} opts
* @this {Mac2Driver}
* @param {import('../types').TerminateAppOptions} opts
* @returns {Promise<boolean>} `true` if the app was running and has been successfully terminated.
* `false` if the app was not running before.
*/
commands.macosTerminateApp = async function macosTerminateApp (opts) {
export async function macosTerminateApp (opts) {
const { bundleId, path } = opts ?? {};
return await this.wda.proxy.command('/wda/apps/terminate', 'POST', { bundleId, path });
return /** @type {boolean} */ (
await this.wda.proxy.command('/wda/apps/terminate', 'POST', { bundleId, path })
);
};

/**
* @typedef {Object} QueryAppStateOptions
* @property {string} [bundleId] Bundle identifier of the app whose state should be queried.
* Either this property or `path` must be provided
* @property {string} [path] Full path to the app bundle. Either this property
* or `bundleId` must be provided
*/

/**
* Query an app state with given bundle identifier. An exception is thrown if the
* app cannot be found.
*
* @param {QueryAppStateOptions} opts
* @this {Mac2Driver}
* @param {import('../types').QueryAppStateOptions} opts
* @returns {Promise<number>} The application state code. See
* https://developer.apple.com/documentation/xctest/xcuiapplicationstate?language=objc
* for more details
*/
commands.macosQueryAppState = async function macosQueryAppState (opts) {
export async function macosQueryAppState (opts) {
const { bundleId, path } = opts ?? {};
return await this.wda.proxy.command('/wda/apps/state', 'POST', { bundleId, path });
return /** @type {number} */ (
await this.wda.proxy.command('/wda/apps/state', 'POST', { bundleId, path })
);
};

export default commands;
/**
* @typedef {import('../driver').Mac2Driver} Mac2Driver
*/
37 changes: 12 additions & 25 deletions lib/commands/applescript.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,10 @@
import { fs, tempDir, util } from 'appium/support';
import { exec } from 'teen_process';
import log from '../logger';
import path from 'path';

const OSASCRIPT = 'osascript';
const APPLE_SCRIPT_FEATURE = 'apple_script';

const commands = {};

/**
* @typedef {Object} ExecAppleScriptOptions
* @property {string} script A valid AppleScript to execute
* @property {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.
* @property {string} command A valid AppleScript as a single command (no line breaks) to execute
* @property {number} timeout [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.
* @property {string} cwd The path to an existing folder, which is going to be set as the
* working directory for the command/script being executed.
*/

/**
* Executes the given AppleScript command or a whole script based on the
* given options. Either of these options must be provided. If both are provided
Expand All @@ -31,12 +16,13 @@ const commands = {};
* and no permissions to do it are given to the parent (for example, Appium or Terminal)
* process in System Preferences -> Privacy list.
*
* @param {ExecAppleScriptOptions} opts
* @this {Mac2Driver}
* @param {import('../types').ExecAppleScriptOptions} opts
* @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.
*/
commands.macosExecAppleScript = async function macosExecAppleScript (opts) {
export async function macosExecAppleScript (opts = {}) {
this.ensureFeatureEnabled(APPLE_SCRIPT_FEATURE);

const {
Expand All @@ -45,12 +31,12 @@ commands.macosExecAppleScript = async function macosExecAppleScript (opts) {
command,
cwd,
timeout,
} = opts ?? {};
} = opts;
if (!script && !command) {
log.errorAndThrow('AppleScript script/command must not be empty');
this.log.errorAndThrow('AppleScript script/command must not be empty');
}
if (/\n/.test(command)) {
log.errorAndThrow('AppleScript commands cannot contain line breaks');
if (/\n/.test(/** @type {string} */(command))) {
this.log.errorAndThrow('AppleScript commands cannot contain line breaks');
}
// 'command' has priority over 'script'
const shouldRunScript = !command;
Expand All @@ -64,12 +50,12 @@ commands.macosExecAppleScript = async function macosExecAppleScript (opts) {
if (shouldRunScript) {
tmpRoot = await tempDir.openDir();
const tmpScriptPath = path.resolve(tmpRoot, 'appium_script.scpt');
await fs.writeFile(tmpScriptPath, script, 'utf8');
await fs.writeFile(tmpScriptPath, /** @type {string} */(script), 'utf8');
args.push(tmpScriptPath);
} else {
args.push('-e', command);
}
log.info(`Running ${OSASCRIPT} with arguments: ${util.quote(args)}`);
this.log.info(`Running ${OSASCRIPT} with arguments: ${util.quote(args)}`);
try {
const {stdout} = await exec(OSASCRIPT, args, {cwd, timeout});
return stdout;
Expand All @@ -83,5 +69,6 @@ commands.macosExecAppleScript = async function macosExecAppleScript (opts) {
}
};

export { commands };
export default commands;
/**
* @typedef {import('../driver').Mac2Driver} Mac2Driver
*/
27 changes: 20 additions & 7 deletions lib/commands/execute.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import _ from 'lodash';
import { errors } from 'appium/driver';
import log from '../logger';

const commands = {};

const EXTENSION_COMMANDS_MAPPING = {
setValue: 'macosSetValue',
Expand Down Expand Up @@ -39,21 +36,37 @@ const EXTENSION_COMMANDS_MAPPING = {
deepLink: 'macosDeepLink',
};

commands.execute = async function execute (script, args) {
/**
*
* @this {Mac2Driver}
* @param {string} script
* @param {any[]|import('@appium/types').StringRecord} [args]
* @returns {Promise<any>}
*/
export async function execute (script, args) {
if (script.match(/^macos:/)) {
log.info(`Executing extension command '${script}'`);
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();
};

commands.executeMacosCommand = async function executeMacosCommand (command, opts = {}) {
/**
*
* @this {Mac2Driver}
* @param {string} command
* @param {import('@appium/types').StringRecord} [opts={}]
* @returns {Promise<any>}
*/
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[EXTENSION_COMMANDS_MAPPING[command]](opts);
};

export default commands;
/**
* @typedef {import('../driver').Mac2Driver} Mac2Driver
*/
26 changes: 16 additions & 10 deletions lib/commands/find.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import { util } from 'appium/support';


const commands = {};

// This is needed to make lookup by image working
commands.findElOrEls = async function findElOrEls (strategy, selector, mult, context) {
context = util.unwrapElement(context);
const endpoint = `/element${context ? `/${context}/element` : ''}${mult ? 's' : ''}`;
/**
* This is needed to make lookup by image working
*
* @this {Mac2Driver}
* @param {string} strategy
* @param {string} selector
* @param {boolean} mult
* @param {string} [context]
* @returns {Promise<any>}
*/
export async function findElOrEls (strategy, selector, mult, context) {
const contextId = context ? util.unwrapElement(context) : context;
const endpoint = `/element${contextId ? `/${contextId}/element` : ''}${mult ? 's' : ''}`;

if (strategy === '-ios predicate string') {
strategy = 'predicate string';
Expand All @@ -20,6 +26,6 @@ commands.findElOrEls = async function findElOrEls (strategy, selector, mult, con
});
};


export { commands };
export default commands;
/**
* @typedef {import('../driver').Mac2Driver} Mac2Driver
*/
Loading
Loading