refactor: add type for driver-command,plugin-command,doctor,docutils,execute-child (#17962)

This commit is contained in:
pacozaa
2023-01-07 23:11:04 +07:00
committed by GitHub
parent da230ca243
commit 8f7171c42a
16 changed files with 311 additions and 32 deletions
+56 -5
View File
@@ -8,6 +8,7 @@ const REQ_DRIVER_FIELDS = ['driverName', 'automationName', 'platformNames', 'mai
/**
* @extends {ExtensionCommand<DriverType>}
*/
export default class DriverCommand extends ExtensionCommand {
/**
* @param {import('./extension-command').ExtensionCommandOptions<DriverType>} opts
@@ -21,6 +22,7 @@ export default class DriverCommand extends ExtensionCommand {
* Install a driver
*
* @param {DriverInstallOpts} opts
* @return {Promise<ExtRecord<DriverType>>}
*/
async install({driver, installType, packageName}) {
return await super._install({
@@ -30,18 +32,40 @@ export default class DriverCommand extends ExtensionCommand {
});
}
/**
* Uninstall a driver
*
* @param {DriverUninstallOpts} opts
* @return {Promise<ExtRecord<DriverType>>}
*/
async uninstall({driver}) {
return await super._uninstall({installSpec: driver});
}
/**
* Update a driver
*
* @param {DriverUpdateOpts} opts
* @return {Promise<import('./extension-command').ExtensionUpdateResult>}
*/
async update({driver, unsafe}) {
return await super._update({installSpec: driver, unsafe});
}
/**
*
* @param {DriverRunOptions} opts
* @return {Promise<import('./extension-command').RunOutput>}
*/
async run({driver, scriptName, extraArgs}) {
return await super._run({installSpec: driver, scriptName, extraArgs});
}
/**
*
* @param {import('./extension-command').ExtensionArgs} opts
* @returns {string}
*/
getPostInstallText({extName, extData}) {
return (
`Driver ${extName}@${extData.version} successfully installed\n`.green +
@@ -74,6 +98,16 @@ export default class DriverCommand extends ExtensionCommand {
}
}
/**
* @typedef {import('@appium/types').ExtensionType} ExtensionType
* @typedef {import('@appium/types').DriverType} DriverType
*/
/**
* @template {ExtensionType} ExtType
* @typedef {import('appium/types').ExtRecord<ExtType>} ExtRecord
*/
/**
* @typedef DriverCommandOptions
* @property {import('../extension/extension-config').ExtensionConfig<DriverType>} config
@@ -81,11 +115,7 @@ export default class DriverCommand extends ExtensionCommand {
*/
/**
* @typedef {import('@appium/types').DriverType} DriverType
*/
/**
* Options for {@linkcode ExtensionCommand._install}
* Options for {@linkcode DriverCommand.install}
* @typedef DriverInstallOpts
* @property {string} driver - the name or spec of a driver to install
* @property {InstallType} installType - how to install this driver. One of the INSTALL_TYPES
@@ -95,3 +125,24 @@ export default class DriverCommand extends ExtensionCommand {
/**
* @typedef {import('appium/types').InstallType} InstallType
*/
/**
* Options for {@linkcode DriverCommand.uninstall}
* @typedef DriverUninstallOpts
* @property {string} driver - the name or spec of a driver to uninstall
*/
/**
* Options for {@linkcode DriverCommand.update}
* @typedef DriverUpdateOpts
* @property {string} driver - the name of the driver to update
* @property {boolean} unsafe - if true, will perform unsafe updates past major revision boundaries
*/
/**
* Options for {@linkcode DriverCommand.run}.
* @typedef DriverRunOptions
* @property {string} driver - name of the driver to run a script from
* @property {string} scriptName - name of the script to run
* @property {string[]} [extraArgs] - arguments to pass to the script
*/
+1 -1
View File
@@ -75,7 +75,7 @@ class ExtensionCommand {
* nor is something like `@returns {never}` which does not imply a thrown exception.
* @param {string} message
* @protected
* @returns {Error}
* @throws {Error}
*/
_createFatalError(message) {
return new Error(this.log.decorate(message, 'error'));
+69 -2
View File
@@ -17,6 +17,12 @@ export default class PluginCommand extends ExtensionCommand {
this.knownExtensions = KNOWN_PLUGINS;
}
/**
* Install a plugin
*
* @param {PluginInstallOpts} opts
* @returns {Promise<ExtRecord<PluginType>>}
*/
async install({plugin, installType, packageName}) {
return await super._install({
installSpec: plugin,
@@ -25,26 +31,48 @@ export default class PluginCommand extends ExtensionCommand {
});
}
/**
* Uninstall a plugin
*
* @param {PluginUninstallOpts} opts
* @returns {Promise<ExtRecord<PluginType>>}
*/
async uninstall({plugin}) {
return await super._uninstall({installSpec: plugin});
}
/**
* Update a plugin
*
* @param {PluginUpdateOpts} opts
* @returns {Promise<import('./extension-command').ExtensionUpdateResult>}
*/
async update({plugin, unsafe}) {
return await super._update({installSpec: plugin, unsafe});
}
/**
*
* @param {PluginRunOptions} opts
* @returns {Promise<import('./extension-command').RunOutput>}
*/
async run({plugin, scriptName, extraArgs}) {
return await super._run({installSpec: plugin, scriptName, extraArgs});
}
/**
*
* @param {import('./extension-command').ExtensionArgs} opts
* @returns {string}
*/
getPostInstallText({extName, extData}) {
return `Plugin ${extName}@${extData.version} successfully installed`.green;
}
/**
* Validates fields in `appium` field of `driverMetadata`
* Validates fields in `appium` field of `pluginMetadata`
*
* For any `package.json` fields which a driver requires, validate the type of
* For any `package.json` fields which a plugin requires, validate the type of
* those fields on the `package.json` data, throwing an error if anything is
* amiss.
* @param {import('appium/types').ExtMetadata<PluginType>} pluginMetadata
@@ -67,5 +95,44 @@ export default class PluginCommand extends ExtensionCommand {
}
/**
* @typedef {import('@appium/types').ExtensionType} ExtensionType
* @typedef {import('@appium/types').PluginType} PluginType
*/
/**
* @template {ExtensionType} ExtType
* @typedef {import('appium/types').ExtRecord<ExtType>} ExtRecord
*/
/**
* Options for {@linkcode PluginCommand.install}
* @typedef PluginInstallOpts
* @property {string} plugin - the name or spec of a plugin to install
* @property {InstallType} installType - how to install this plugin. One of the INSTALL_TYPES
* @property {string} [packageName] - for git/github installs, the plugin node package name
*/
/**
* @typedef {import('appium/types').InstallType} InstallType
*/
/**
* Options for {@linkcode PluginCommand.uninstall}
* @typedef PluginUninstallOpts
* @property {string} plugin - the name or spec of a plugin to uninstall
*/
/**
* Options for {@linkcode PluginCommand.update}
* @typedef PluginUpdateOpts
* @property {string} plugin - the name of the plugin to update
* @property {boolean} unsafe - if true, will perform unsafe updates past major revision boundaries
*/
/**
* Options for {@linkcode PluginCommand.run}.
* @typedef PluginRunOptions
* @property {string} plugin - name of the plugin to run a script from
* @property {string} scriptName - name of the script to run
* @property {string[]} [extraArgs] - arguments to pass to the script
*/
+3
View File
@@ -7,6 +7,9 @@ import '@colors/colors';
import {getAndroidBinaryPath, getSdkRootFromEnv} from 'appium-adb';
import log from './logger';
/**
* @type {import('./factory').DoctorCheckList}
*/
let checks = [];
const javaHome = system.isWindows() ? '%JAVA_HOME%' : '$JAVA_HOME';
+3
View File
@@ -7,6 +7,9 @@ import {DoctorCheck, FixSkippedError} from './doctor';
import log from './logger';
import {fixIt} from './prompt';
/**
* @type {import('./factory').DoctorCheckList}
*/
let checks = [];
class DirCheck extends DoctorCheck {
+3
View File
@@ -4,6 +4,9 @@ import {fs, system} from '@appium/support';
import path from 'path';
import '@colors/colors';
/**
* @type {import('./factory').DoctorCheckList}
*/
let checks = [];
// Check PATH binaries
+30
View File
@@ -7,15 +7,29 @@ const {version} = fs.readPackageJsonFrom(__dirname);
class FixSkippedError extends Error {}
/**
* Create interface for other Doctors
*/
class DoctorCheck {
/**
* @param {DoctorOpts} opts
*/
constructor(opts = {}) {
this.autofix = !!opts.autofix;
}
/**
* Every doctor diagnose the symptoms
* @throws {Error}
*/
diagnose() {
throw new Error('Not Implemented!');
}
/**
* Every doctor suggest the solutions to fix the sickness
* @throws {Error}
*/
fix() {
// return string for manual fixes.
throw new Error('Not Implemented!');
@@ -24,17 +38,28 @@ class DoctorCheck {
class Doctor {
constructor() {
/**
* All the sub check goes here after register
* @type {DoctorCheck[]}
*/
this.checks = [];
this.checkOptionals = [];
this.toFix = [];
this.toFixOptionals = [];
}
/**
* Register all the sub check and combine them together
* @param {DoctorCheck[] | DoctorCheck} checks
*/
register(checks) {
checks = Array.isArray(checks) ? checks : [checks];
this.checks = this.checks.concat(checks);
}
/**
* The doctor shows the report
*/
async diagnose() {
log.info(`### Diagnostic for ${'necessary'.green} dependencies starting ###`);
this.toFix = [];
@@ -211,3 +236,8 @@ class Doctor {
}
export {Doctor, DoctorCheck, FixSkippedError};
/**
* @typedef DoctorOpts
* @property {boolean?} autofix
*/
+16
View File
@@ -6,6 +6,9 @@ import androidChecks from './android';
import devChecks from './dev';
import demoChecks from './demo';
/**
* @type {DoctorGroup}
*/
let checks = {generalChecks, iosChecks, androidChecks, devChecks, demoChecks};
let newDoctor = (opts) => {
@@ -19,3 +22,16 @@ let newDoctor = (opts) => {
};
export default newDoctor;
/**
* @typedef {import('./doctor').DoctorCheck[]} DoctorCheckList
*/
/**
* @typedef DoctorGroup - Contain a group of Doctors
* @property {DoctorCheckList} generalChecks - Check AppiumHome, NodeBinary, NodeVersion, ffmpeg, mjpeg-consumer
* @property {DoctorCheckList} iosChecks - Check if iOS toolchains are installed
* @property {DoctorCheckList} androidChecks - Check if Android toolchains are installed
* @property {DoctorCheckList} devChecks - Check Path Binary and Android SDKs
* @property {DoctorCheckList} demoChecks - Check /tmp/appium-doctor/demo/*
*/
+20 -2
View File
@@ -6,9 +6,16 @@ import {util, env} from '@appium/support';
import {EOL} from 'os';
import '@colors/colors';
/**
* @type {import('./factory').DoctorCheckList}
*/
let checks = [];
class AppiumHomeCheck extends DoctorCheck {
/**
*
* @returns {UtilsResult}
*/
async diagnose() {
return ok(`APPIUM_HOME is ${await env.resolveAppiumHome()}`);
}
@@ -59,6 +66,10 @@ class NodeVersionCheck extends DoctorCheck {
checks.push(new NodeVersionCheck());
class OptionalFfmpegCommandCheck extends DoctorCheck {
/**
*
* @returns {Promise<UtilsResult>}
*/
async diagnose() {
const ffmpegPath = await resolveExecutablePath('ffmpeg');
return ffmpegPath
@@ -69,8 +80,11 @@ class OptionalFfmpegCommandCheck extends DoctorCheck {
)
: nokOptional('ffmpeg cannot be found');
}
// eslint-disable-next-line require-await
async fix() {
/**
*
* @returns {string}
*/
fix() {
return `${
'ffmpeg'.bold
} is needed to record screen features. Please read https://www.ffmpeg.org/ to install it`;
@@ -107,3 +121,7 @@ export {
OptionalMjpegConsumerCommandCheck,
};
export default checks;
/**
* @typedef {import('./utils').UtilsResult} UtilsResult
*/
+3
View File
@@ -7,6 +7,9 @@ import {fixIt} from './prompt';
import EnvVarAndPathCheck from './env';
import '@colors/colors';
/**
* @type {import('./factory').DoctorCheckList}
*/
let checks = [];
let fixes = {};
+8
View File
@@ -1,5 +1,8 @@
import {inquirer} from './utils';
/**
* @type {string}
*/
let persistentResponse;
const fixItQuestion = {
@@ -7,6 +10,11 @@ const fixItQuestion = {
name: 'confirmation',
message: 'Fix it:',
choices: ['yes', 'no', 'always', 'never'],
/**
*
* @param {string} val
* @returns {string}
*/
filter(val) {
return val.toLowerCase();
},
+26
View File
@@ -5,15 +5,34 @@ import {fs, system} from '@appium/support';
import {exec} from 'teen_process';
import {isFunction} from 'lodash';
/**
* @param {string} message
* @returns {UtilsResult}
*/
function ok(message) {
return {ok: true, optional: false, message};
}
/**
* @param {string} message
* @returns {UtilsResult}
*/
function nok(message) {
return {ok: false, optional: false, message};
}
/**
* @param {string} message
* @returns {UtilsResult}
*/
function okOptional(message) {
return {ok: true, optional: true, message};
}
/**
* @param {string} message
* @returns {UtilsResult}
*/
function nokOptional(message) {
return {ok: false, optional: true, message};
}
@@ -122,3 +141,10 @@ export {
getNpmPackageInfo,
resetLog,
};
/**
* @typedef UtilsResult
* @property {boolean} ok
* @property {boolean} optional
* @property {string} message
*/
+9 -9
View File
@@ -6,11 +6,11 @@ const DEFAULT_BRANCH = 'gh-pages';
const MIKE_VER_STRING = 'mike 1.';
export class Mike {
/** @type string */ remote;
/** @type string */ branch;
/** @type string? */ prefix;
/** @type string */ configFile;
/** @type boolean */ _mikeVerified = false;
/** @type {string} */ remote;
/** @type {string} */ branch;
/** @type {string?} */ prefix;
/** @type {string} */ configFile;
/** @type {boolean} */ _mikeVerified = false;
constructor(/** @type MikeOpts */ opts) {
this.remote = opts.remote || DEFAULT_REMOTE;
@@ -22,7 +22,7 @@ export class Mike {
/**
* Throw an error if the 'mike' binary cannot be found
*
* @throws Error
* @throws {Error}
*/
async verifyMike() {
if (this._mikeVerified) {
@@ -46,7 +46,7 @@ export class Mike {
* @param {string} cmdName - the name of the mike command to run
* @param {string[]} cmdArgs - an array of command-specific arguments
*
* @returns string[]
* @returns {string[]}
*/
getMikeArgs(cmdName, cmdArgs) {
return [
@@ -70,7 +70,7 @@ export class Mike {
* @param {string[]} [mikeArgs=[]] - the arguments to pass to the mike command
* @param {boolean?} [verify=true] - whether to verify mike exists first
*
* @returns Promise<import('teen_process').ExecResult<string>>
* @returns {Promise<import('teen_process').ExecResult<string>>}
*/
async exec(mikeCmd, mikeArgs = [], verify = true) {
if (verify) {
@@ -84,7 +84,7 @@ export class Mike {
/**
* Return the list of mike deploys
*
* @returns string[]
* @returns {string[]}
*/
async list() {
const {stdout} = await this.exec('list');
+4 -4
View File
@@ -22,9 +22,9 @@ export async function verifyMkdocs() {
/**
* Run 'mkdocs build' on a project
*
* @param {string} configPath path to mkdocs config yml
* @param {string} outputDir directory mkdocs should build into
* @param {?string} theme theme name
* @param {string} configPath - path to mkdocs config yml
* @param {string} outputDir - directory mkdocs should build into
* @param {string?} theme - theme name
*/
export async function mkdocsBuild(configPath, outputDir, theme = 'mkdocs') {
await verifyMkdocs();
@@ -34,7 +34,7 @@ export async function mkdocsBuild(configPath, outputDir, theme = 'mkdocs') {
/**
* Run 'mkdocs serve' on a project
*
* @param {string} configPath path to mkdocs config yml
* @param {string} configPath - path to mkdocs config yml
*/
export async function mkdocsServe(configPath) {
await verifyMkdocs();
@@ -5,6 +5,9 @@ import {logger, util} from 'appium/support';
import {attach} from 'webdriverio';
const log = logger.getLogger('ExecuteDriver Child');
/**
* @type {Promise<void>}
*/
let send;
// duplicate defining these keys here so we don't need to re-load a huge appium
@@ -12,12 +15,22 @@ let send;
export const W3C_ELEMENT_KEY = util.W3C_WEB_ELEMENT_IDENTIFIER;
export const MJSONWP_ELEMENT_KEY = 'ELEMENT';
async function runScript(driverOpts, script, timeoutMs) {
/**
* Run the script in a VM.
* @param {DriverScriptMessageEvent} eventParams
* @returns {Promise<RunScriptResult>}
* @throws {TypeError}
*/
async function runScript(eventParams) {
const {driverOpts, script, timeoutMs} = eventParams;
if (!_.isNumber(timeoutMs)) {
throw new TypeError('Timeout parameter must be a number');
}
// set up fake logger
/**
* set up fake logger
* @type {string[]}
*/
const logLevels = ['error', 'warn', 'log'];
const logs = {};
const consoleFns = {};
@@ -122,11 +135,22 @@ function coerceScriptResult(obj) {
return obj;
}
async function main(driverOpts, script, timeoutMs) {
/**
* Entry point to runScript
* @param {DriverScriptMessageEvent} eventParams
*/
async function main(eventParams) {
/**
* keep the response of runScript
* @type {ScriptResult}
*/
let res;
log.info('Parameters received from parent process');
try {
res = {success: await runScript(driverOpts, script, timeoutMs)};
res = {success: await runScript(eventParams)};
log.info('runScript success');
} catch (error) {
log.info('runScript error');
res = {error: {message: error.message, stack: error.stack}};
}
await send(res);
@@ -136,8 +160,34 @@ async function main(driverOpts, script, timeoutMs) {
if (require.main === module && _.isFunction(process.send)) {
send = B.promisify(process.send, {context: process});
log.info('Running driver execution in child process');
process.on('message', ({driverOpts, script, timeoutMs}) => {
log.info('Parameters received from parent process');
main(driverOpts, script, timeoutMs);
});
process.on('message', main);
}
/**
* @typedef {import('webdriverio').AttachOptions} AttachOptions
*/
/**
* @typedef DriverScriptMessageEvent
* @property {AttachOptions} driverOpts - the driver options
* @property {string} script - the javascript to execute
* @property {number} timeoutMs - script timeout in milliseconds
*/
/**
* @typedef ScriptResult
* @property {any} success
* @property {ScriptResultError} error
*/
/**
* @typedef ScriptResultError
* @property {any} message
* @property {any} stack
*/
/**
* @typedef RunScriptResult
* @property {any} result
* @property {Object} logs
*/
+2 -1
View File
@@ -24,7 +24,7 @@ export default class ExecuteDriverPlugin extends BasePlugin {
* already been attached to the currently running session.
*
* @param {function} next - standard behaviour for executeDriverScript
* @param {BaseDriver} driver - Appium driver handling this command
* @param {import('@appium/types').ExternalDriver} driver - Appium driver handling this command
* @param {string} script - the string representing the driver script to run
* @param {string} [scriptType='webdriverio'] - the name of the driver script
* library (currently only webdriverio is supported)
@@ -32,6 +32,7 @@ export default class ExecuteDriverPlugin extends BasePlugin {
*
* @returns {Object} - a JSONifiable object representing the return value of
* the script
* @throws {Error}
*/
async executeDriverScript(
next,