appium 3.2.2 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/build/lib/cli/args.d.ts +16 -12
  2. package/build/lib/cli/args.d.ts.map +1 -1
  3. package/build/lib/cli/args.js +15 -35
  4. package/build/lib/cli/args.js.map +1 -1
  5. package/build/lib/cli/driver-command.d.ts +51 -93
  6. package/build/lib/cli/driver-command.d.ts.map +1 -1
  7. package/build/lib/cli/driver-command.js +11 -66
  8. package/build/lib/cli/driver-command.js.map +1 -1
  9. package/build/lib/cli/extension-command.d.ts +211 -415
  10. package/build/lib/cli/extension-command.d.ts.map +1 -1
  11. package/build/lib/cli/extension-command.js +384 -653
  12. package/build/lib/cli/extension-command.js.map +1 -1
  13. package/build/lib/cli/extension.d.ts +11 -16
  14. package/build/lib/cli/extension.d.ts.map +1 -1
  15. package/build/lib/cli/extension.js +10 -28
  16. package/build/lib/cli/extension.js.map +1 -1
  17. package/build/lib/cli/parser.d.ts +40 -69
  18. package/build/lib/cli/parser.d.ts.map +1 -1
  19. package/build/lib/cli/parser.js +24 -59
  20. package/build/lib/cli/parser.js.map +1 -1
  21. package/build/lib/cli/plugin-command.d.ts +50 -90
  22. package/build/lib/cli/plugin-command.d.ts.map +1 -1
  23. package/build/lib/cli/plugin-command.js +11 -63
  24. package/build/lib/cli/plugin-command.js.map +1 -1
  25. package/build/lib/cli/setup-command.d.ts +21 -26
  26. package/build/lib/cli/setup-command.d.ts.map +1 -1
  27. package/build/lib/cli/setup-command.js +13 -55
  28. package/build/lib/cli/setup-command.js.map +1 -1
  29. package/build/lib/cli/utils.d.ts +27 -29
  30. package/build/lib/cli/utils.d.ts.map +1 -1
  31. package/build/lib/cli/utils.js +29 -31
  32. package/build/lib/cli/utils.js.map +1 -1
  33. package/build/lib/config-file.d.ts +24 -67
  34. package/build/lib/config-file.d.ts.map +1 -1
  35. package/build/lib/config-file.js +56 -115
  36. package/build/lib/config-file.js.map +1 -1
  37. package/build/lib/config.d.ts +42 -44
  38. package/build/lib/config.d.ts.map +1 -1
  39. package/build/lib/config.js +75 -107
  40. package/build/lib/config.js.map +1 -1
  41. package/build/lib/constants.d.ts +23 -23
  42. package/build/lib/constants.d.ts.map +1 -1
  43. package/build/lib/constants.js +10 -15
  44. package/build/lib/constants.js.map +1 -1
  45. package/build/lib/doctor/doctor.d.ts +40 -57
  46. package/build/lib/doctor/doctor.d.ts.map +1 -1
  47. package/build/lib/doctor/doctor.js +29 -60
  48. package/build/lib/doctor/doctor.js.map +1 -1
  49. package/build/lib/grid-register.d.ts +32 -7
  50. package/build/lib/grid-register.d.ts.map +1 -1
  51. package/build/lib/grid-register.js +84 -48
  52. package/build/lib/grid-register.js.map +1 -1
  53. package/build/lib/logsink.d.ts +13 -22
  54. package/build/lib/logsink.d.ts.map +1 -1
  55. package/build/lib/logsink.js +48 -103
  56. package/build/lib/logsink.js.map +1 -1
  57. package/build/lib/main.js +1 -1
  58. package/build/lib/main.js.map +1 -1
  59. package/build/lib/schema/arg-spec.d.ts +32 -107
  60. package/build/lib/schema/arg-spec.d.ts.map +1 -1
  61. package/build/lib/schema/arg-spec.js +11 -107
  62. package/build/lib/schema/arg-spec.js.map +1 -1
  63. package/build/lib/schema/cli-args.d.ts +3 -15
  64. package/build/lib/schema/cli-args.d.ts.map +1 -1
  65. package/build/lib/schema/cli-args.js +15 -105
  66. package/build/lib/schema/cli-args.js.map +1 -1
  67. package/build/lib/schema/cli-transformers.d.ts +15 -12
  68. package/build/lib/schema/cli-transformers.d.ts.map +1 -1
  69. package/build/lib/schema/cli-transformers.js +15 -45
  70. package/build/lib/schema/cli-transformers.js.map +1 -1
  71. package/build/lib/schema/index.d.ts +2 -2
  72. package/build/lib/schema/index.d.ts.map +1 -1
  73. package/build/lib/schema/index.js.map +1 -1
  74. package/build/lib/schema/keywords.d.ts +12 -20
  75. package/build/lib/schema/keywords.d.ts.map +1 -1
  76. package/build/lib/schema/keywords.js +6 -51
  77. package/build/lib/schema/keywords.js.map +1 -1
  78. package/build/lib/schema/schema.d.ts +106 -231
  79. package/build/lib/schema/schema.d.ts.map +1 -1
  80. package/build/lib/schema/schema.js +75 -345
  81. package/build/lib/schema/schema.js.map +1 -1
  82. package/build/lib/utils.d.ts +59 -238
  83. package/build/lib/utils.d.ts.map +1 -1
  84. package/build/lib/utils.js +55 -207
  85. package/build/lib/utils.js.map +1 -1
  86. package/lib/cli/{args.js → args.ts} +40 -51
  87. package/lib/cli/driver-command.ts +122 -0
  88. package/lib/cli/{extension-command.js → extension-command.ts} +610 -689
  89. package/lib/cli/extension.ts +65 -0
  90. package/lib/cli/{parser.js → parser.ts} +48 -71
  91. package/lib/cli/plugin-command.ts +117 -0
  92. package/lib/cli/{setup-command.js → setup-command.ts} +57 -72
  93. package/lib/cli/utils.ts +97 -0
  94. package/lib/config-file.ts +212 -0
  95. package/lib/{config.js → config.ts} +129 -141
  96. package/lib/{constants.js → constants.ts} +30 -41
  97. package/lib/doctor/{doctor.js → doctor.ts} +81 -91
  98. package/lib/grid-register.ts +250 -0
  99. package/lib/{logsink.js → logsink.ts} +91 -137
  100. package/lib/main.js +1 -1
  101. package/lib/schema/arg-spec.ts +131 -0
  102. package/lib/schema/cli-args.ts +171 -0
  103. package/lib/schema/cli-transformers.ts +83 -0
  104. package/lib/schema/keywords.ts +96 -0
  105. package/lib/schema/schema.ts +449 -0
  106. package/lib/utils.ts +404 -0
  107. package/package.json +16 -16
  108. package/lib/cli/driver-command.js +0 -174
  109. package/lib/cli/extension.js +0 -74
  110. package/lib/cli/plugin-command.js +0 -164
  111. package/lib/cli/utils.js +0 -91
  112. package/lib/config-file.js +0 -228
  113. package/lib/grid-register.js +0 -146
  114. package/lib/schema/arg-spec.js +0 -229
  115. package/lib/schema/cli-args.js +0 -254
  116. package/lib/schema/cli-transformers.js +0 -113
  117. package/lib/schema/keywords.js +0 -136
  118. package/lib/schema/schema.js +0 -725
  119. package/lib/utils.js +0 -512
  120. /package/lib/schema/{index.js → index.ts} +0 -0
@@ -1,74 +0,0 @@
1
- /* eslint-disable no-console */
2
- import {DRIVER_TYPE, PLUGIN_TYPE} from '../constants';
3
- import {isExtensionCommandArgs} from '../utils';
4
- import DriverCliCommand from './driver-command';
5
- import PluginCliCommand from './plugin-command';
6
- import {errAndQuit, JSON_SPACES} from './utils';
7
-
8
- export const commandClasses = Object.freeze(
9
- /** @type {const} */ ({
10
- [DRIVER_TYPE]: DriverCliCommand,
11
- [PLUGIN_TYPE]: PluginCliCommand,
12
- })
13
- );
14
-
15
- /**
16
- * Run a subcommand of the 'appium driver' type. Each subcommand has its own set of arguments which
17
- * can be represented as a JS object.
18
- *
19
- * @template {import('appium/types').CliExtensionCommand} Cmd
20
- * @template {import('appium/types').CliExtensionSubcommand} SubCmd
21
- * @param {import('appium/types').Args<Cmd, SubCmd>} args - JS object where the key is the parameter name (as defined in
22
- * driver-parser.js)
23
- * @param {import('../extension/extension-config').ExtensionConfig<Cmd>} config - Extension config object
24
- */
25
- async function runExtensionCommand(args, config) {
26
- // TODO driver config file should be locked while any of these commands are
27
- // running to prevent weird situations
28
- let jsonResult = null;
29
- const {extensionType: type} = config; // NOTE this is the same as `args.subcommand`
30
- if (!isExtensionCommandArgs(args)) {
31
- throw new TypeError(`Cannot call ${type} command without a subcommand like 'install'`);
32
- }
33
- let {json, suppressOutput} = args;
34
- json = Boolean(json);
35
- if (suppressOutput) {
36
- json = true;
37
- }
38
- const CommandClass = /** @type {ExtCommand<Cmd>} */ (commandClasses[type]);
39
- const cmd = new CommandClass({config, json});
40
- try {
41
- jsonResult = await cmd.execute(args);
42
- } catch (err) {
43
- // in the suppress output case, we are calling this function internally and should
44
- // just throw instead of printing an error and ending the process
45
- if (suppressOutput) {
46
- throw err;
47
- }
48
- errAndQuit(json, err);
49
- }
50
-
51
- if (json && !suppressOutput) {
52
- console.log(JSON.stringify(jsonResult, null, JSON_SPACES));
53
- }
54
-
55
- return jsonResult;
56
- }
57
-
58
- export {runExtensionCommand};
59
-
60
- /**
61
- * @template {ExtensionType} ExtType
62
- * @typedef {ExtType extends DriverType ? Class<DriverCliCommand> : ExtType extends PluginType ? Class<PluginCliCommand> : never} ExtCommand
63
- */
64
-
65
- /**
66
- * @typedef {import('@appium/types').ExtensionType} ExtensionType
67
- * @typedef {import('@appium/types').DriverType} DriverType
68
- * @typedef {import('@appium/types').PluginType} PluginType
69
- */
70
-
71
- /**
72
- * @template T
73
- * @typedef {import('@appium/types').Class<T>} Class
74
- */
@@ -1,164 +0,0 @@
1
- import _ from 'lodash';
2
- import ExtensionCliCommand from './extension-command';
3
- import {KNOWN_PLUGINS} from '../constants';
4
-
5
- const REQ_PLUGIN_FIELDS = ['pluginName', 'mainClass'];
6
-
7
- /**
8
- * @extends {ExtensionCliCommand<PluginType>}
9
- */
10
- export default class PluginCliCommand extends ExtensionCliCommand {
11
- /**
12
- *
13
- * @param {import('./extension-command').ExtensionCommandOptions<PluginType>} opts
14
- */
15
- constructor({config, json}) {
16
- super({config, json});
17
- this.knownExtensions = KNOWN_PLUGINS;
18
- }
19
-
20
- /**
21
- * Install a plugin
22
- *
23
- * @param {PluginInstallOpts} opts
24
- * @returns {Promise<ExtRecord<PluginType>>}
25
- */
26
- async install({plugin, installType, packageName}) {
27
- return await super._install({
28
- installSpec: plugin,
29
- installType,
30
- packageName,
31
- });
32
- }
33
-
34
- /**
35
- * Uninstall a plugin
36
- *
37
- * @param {PluginUninstallOpts} opts
38
- * @returns {Promise<ExtRecord<PluginType>>}
39
- */
40
- async uninstall({plugin}) {
41
- return await super._uninstall({installSpec: plugin});
42
- }
43
-
44
- /**
45
- * Update a plugin
46
- *
47
- * @param {PluginUpdateOpts} opts
48
- * @returns {Promise<import('./extension-command').ExtensionUpdateResult>}
49
- */
50
- async update({plugin, unsafe}) {
51
- return await super._update({installSpec: plugin, unsafe});
52
- }
53
-
54
- /**
55
- * Run a script from a plugin
56
- *
57
- * @param {PluginRunOptions} opts
58
- * @returns {Promise<import('./extension-command').RunOutput>}
59
- * @throws {Error} if the script fails to run
60
- */
61
- async run({plugin, scriptName, extraArgs}) {
62
- return await super._run({
63
- installSpec: plugin,
64
- scriptName,
65
- extraArgs,
66
- bufferOutput: this.isJsonOutput,
67
- });
68
- }
69
-
70
- /**
71
- * Runs doctor checks for the given plugin
72
- *
73
- * @param {PluginDoctorOptions} opts
74
- * @returns {Promise<number>} The amount of executed doctor checks.
75
- * @throws {Error} If any of the mandatory Doctor checks fails.
76
- */
77
- async doctor({plugin}) {
78
- return await super._doctor({
79
- installSpec: plugin,
80
- });
81
- }
82
-
83
- /**
84
- *
85
- * @param {import('./extension-command').ExtensionArgs} opts
86
- * @returns {string}
87
- */
88
- getPostInstallText({extName, extData}) {
89
- return `Plugin ${extName}@${extData.version} successfully installed`.green;
90
- }
91
-
92
- /**
93
- * Validates fields in `appium` field of `pluginMetadata`
94
- *
95
- * For any `package.json` fields which a plugin requires, validate the type of
96
- * those fields on the `package.json` data, throwing an error if anything is
97
- * amiss.
98
- * @param {import('appium/types').ExtMetadata<PluginType>} pluginMetadata
99
- * @param {string} installSpec
100
- * @returns {void}
101
- */
102
- validateExtensionFields(pluginMetadata, installSpec) {
103
- const missingFields = REQ_PLUGIN_FIELDS.reduce(
104
- (acc, field) => (pluginMetadata[field] ? acc : [...acc, field]),
105
- []
106
- );
107
-
108
- if (!_.isEmpty(missingFields)) {
109
- throw new Error(
110
- `Installed plugin "${installSpec}" did not expose correct fields for compatibility ` +
111
- `with Appium. Missing fields: ${JSON.stringify(missingFields)}`
112
- );
113
- }
114
- }
115
- }
116
-
117
- /**
118
- * @typedef {import('@appium/types').ExtensionType} ExtensionType
119
- * @typedef {import('@appium/types').PluginType} PluginType
120
- */
121
-
122
- /**
123
- * @template {ExtensionType} ExtType
124
- * @typedef {import('appium/types').ExtRecord<ExtType>} ExtRecord
125
- */
126
-
127
- /**
128
- * Options for {@linkcode PluginCliCommand.install}
129
- * @typedef PluginInstallOpts
130
- * @property {string} plugin - the name or spec of a plugin to install
131
- * @property {InstallType} installType - how to install this plugin. One of the INSTALL_TYPES
132
- * @property {string} [packageName] - for git/github installs, the plugin node package name
133
- */
134
-
135
- /**
136
- * @typedef {import('appium/types').InstallType} InstallType
137
- */
138
-
139
- /**
140
- * Options for {@linkcode PluginCliCommand.uninstall}
141
- * @typedef PluginUninstallOpts
142
- * @property {string} plugin - the name or spec of a plugin to uninstall
143
- */
144
-
145
- /**
146
- * Options for {@linkcode PluginCliCommand.update}
147
- * @typedef PluginUpdateOpts
148
- * @property {string} plugin - the name of the plugin to update
149
- * @property {boolean} unsafe - if true, will perform unsafe updates past major revision boundaries
150
- */
151
-
152
- /**
153
- * Options for {@linkcode PluginCliCommand.run}.
154
- * @typedef PluginRunOptions
155
- * @property {string} plugin - name of the plugin to run a script from
156
- * @property {string} scriptName - name of the script to run
157
- * @property {string[]} [extraArgs] - arguments to pass to the script
158
- */
159
-
160
- /**
161
- * Options for {@linkcode PluginCliCommand.doctor}.
162
- * @typedef PluginDoctorOptions
163
- * @property {string} plugin - name of the plugin to run doctor checks for
164
- */
package/lib/cli/utils.js DELETED
@@ -1,91 +0,0 @@
1
- /* eslint-disable no-console */
2
-
3
- import ora from 'ora';
4
-
5
- export const JSON_SPACES = 4;
6
-
7
- /***
8
- * Log an error to the console and exit the process.
9
- * @param {boolean} json - whether we should log json or text
10
- * @param {any} msg - error message, object, Error instance, etc.
11
- */
12
- export function errAndQuit(json, msg) {
13
- if (json) {
14
- console.log(JSON.stringify({error: `${msg}`}, null, JSON_SPACES));
15
- } else {
16
- console.error(`${msg}`.red);
17
- if (msg.stderr) {
18
- console.error(`${msg.stderr}`.red);
19
- }
20
- }
21
- process.exit(1);
22
- }
23
-
24
- /**
25
- * Conditionally log something to the console
26
- * @param {boolean} json - whether we are in json mode (and should therefore not log)
27
- * @param {string} msg - string to log
28
- */
29
- export function log(json, msg) {
30
- if (!json) {
31
- console.log(msg);
32
- }
33
- }
34
-
35
- /**
36
- * Start a spinner, execute an async function, and then stop the spinner
37
- * @param {boolean} json - whether we are in json mode (and should therefore not log)
38
- * @param {string} msg - string to log
39
- * @param {function} fn - function to wrap with spinning
40
- */
41
- export async function spinWith(json, msg, fn) {
42
- if (json) {
43
- return await fn();
44
- }
45
- const spinner = ora(msg).start();
46
- let res;
47
- try {
48
- res = await fn();
49
- spinner.succeed();
50
- return res;
51
- } catch (err) {
52
- spinner.fail();
53
- throw err;
54
- }
55
- }
56
-
57
- export class RingBuffer {
58
- constructor(size = 50) {
59
- this.size = size;
60
- this.buffer = [];
61
- }
62
-
63
- /**
64
- * Get the current buffer contents
65
- *
66
- * @returns {any[]}
67
- */
68
- getBuff() {
69
- return this.buffer;
70
- }
71
-
72
- /**
73
- * Remove the oldest item from the buffer
74
- *
75
- * @returns {void}
76
- */
77
- dequeue() {
78
- this.buffer.shift();
79
- }
80
- /**
81
- * Add an item to the buffer
82
- *
83
- * @param {any} item
84
- */
85
- enqueue(item) {
86
- if (this.buffer.length >= this.size) {
87
- this.dequeue();
88
- }
89
- this.buffer.push(item);
90
- }
91
- }
@@ -1,228 +0,0 @@
1
- import betterAjvErrors from '@sidvind/better-ajv-errors';
2
- import {lilconfig} from 'lilconfig';
3
- import _ from 'lodash';
4
- import * as yaml from 'yaml';
5
- import {getSchema, validate} from './schema/schema';
6
-
7
- /**
8
- * lilconfig loader to handle `.yaml` files
9
- * @type {import('lilconfig').LoaderSync}
10
- */
11
- function yamlLoader(filepath, content) {
12
- try {
13
- return yaml.parse(content);
14
- } catch (e) {
15
- throw new Error(`The YAML config at '${filepath}' cannot be loaded. Original error: ${e.message}`);
16
- }
17
- }
18
-
19
- /**
20
- * A cache of the raw config file (a JSON string) at a filepath.
21
- * This is used for better error reporting.
22
- * Note that config files needn't be JSON, but it helps if they are.
23
- * @type {Map<string,RawJson>}
24
- */
25
- const rawConfig = new Map();
26
-
27
- /**
28
- * Custom JSON loader that caches the raw config file (for use with `better-ajv-errors`).
29
- * If it weren't for this cache, this would be unnecessary.
30
- * @type {import('lilconfig').LoaderSync}
31
- */
32
- function jsonLoader(filepath, content) {
33
- rawConfig.set(filepath, content);
34
- try {
35
- return JSON.parse(content);
36
- } catch (e) {
37
- throw new Error(`The JSON config at '${filepath}' cannot be loaded. Original error: ${e.message}`);
38
- }
39
- }
40
-
41
- /**
42
- * Loads a config file from an explicit path
43
- * @param {LilconfigAsyncSearcher} lc - lilconfig instance
44
- * @param {string} filepath - Path to config file
45
- * @returns {Promise<import('lilconfig').LilconfigResult>}
46
- */
47
- async function loadConfigFile(lc, filepath) {
48
- try {
49
- // removing "await" will cause any rejection to _not_ be caught in this block!
50
- return await lc.load(filepath);
51
- } catch (/** @type {unknown} */ err) {
52
- if (/** @type {NodeJS.ErrnoException} */ (err).code === 'ENOENT') {
53
- /** @type {NodeJS.ErrnoException} */ (
54
- err
55
- ).message = `Config file not found at user-provided path: ${filepath}`;
56
- throw err;
57
- } else if (err instanceof SyntaxError) {
58
- // generally invalid JSON
59
- err.message = `Config file at user-provided path ${filepath} is invalid:\n${err.message}`;
60
- throw err;
61
- }
62
- throw err;
63
- }
64
- }
65
-
66
- /**
67
- * Searches for a config file
68
- * @param {LilconfigAsyncSearcher} lc - lilconfig instance
69
- * @returns {Promise<import('lilconfig').LilconfigResult>}
70
- */
71
- async function searchConfigFile(lc) {
72
- return await lc.search();
73
- }
74
-
75
- /**
76
- * Given an array of errors and the result of loading a config file, generate a
77
- * helpful string for the user.
78
- *
79
- * - If `opts` contains a `json` property, this should be the original JSON
80
- * _string_ of the config file. This is only applicable if the config file
81
- * was in JSON format. If present, it will associate line numbers with errors.
82
- * - If `errors` happens to be empty, this will throw.
83
- * @param {import('ajv').ErrorObject[]} errors - Non-empty array of errors. Required.
84
- * @param {ReadConfigFileResult['config']|any} [config] -
85
- * Configuration & metadata
86
- * @param {FormatConfigErrorsOptions} [opts]
87
- * @throws {TypeError} If `errors` is empty
88
- * @returns {string}
89
- */
90
- export function formatErrors(errors = [], config = {}, opts = {}) {
91
- if (errors && !errors.length) {
92
- throw new TypeError('Array of errors must be non-empty');
93
- }
94
- return betterAjvErrors(getSchema(opts.schemaId), config, errors, {
95
- json: opts.json,
96
- format: 'cli',
97
- });
98
- }
99
-
100
- /**
101
- * Given an optional path, read a config file. Validates the config file.
102
- *
103
- * Call {@link validate} if you already have a config object.
104
- * @param {string} [filepath] - Path to config file, if we have one
105
- * @param {ReadConfigFileOptions} [opts] - Options
106
- * @public
107
- * @returns {Promise<ReadConfigFileResult>} Contains config and filepath, if found, and any errors
108
- */
109
- export async function readConfigFile(filepath, opts = {}) {
110
- const lc = lilconfig('appium', {
111
- loaders: {
112
- '.yaml': yamlLoader,
113
- '.yml': yamlLoader,
114
- '.json': jsonLoader,
115
- noExt: jsonLoader,
116
- },
117
- packageProp: 'appiumConfig',
118
- });
119
-
120
- const result = filepath ? await loadConfigFile(lc, filepath) : await searchConfigFile(lc);
121
-
122
- if (result?.filepath && !result?.isEmpty) {
123
- const {pretty = true} = opts;
124
- try {
125
- let configResult;
126
- const errors = validate(result.config);
127
- if (_.isEmpty(errors)) {
128
- configResult = {...result, errors};
129
- } else {
130
- const reason = formatErrors(errors, result.config, {
131
- json: rawConfig.get(result.filepath),
132
- pretty,
133
- });
134
- configResult = reason ? {...result, errors, reason} : {...result, errors};
135
- }
136
-
137
- // normalize (to camel case) all top-level property names of the config file
138
- configResult.config = normalizeConfig(/** @type {AppiumConfig} */ (configResult.config));
139
-
140
- return configResult;
141
- } finally {
142
- // clean up the raw config file cache, which is only kept to better report errors.
143
- rawConfig.delete(result.filepath);
144
- }
145
- }
146
- return result ?? {};
147
- }
148
-
149
- /**
150
- * Convert schema property names to either a) the value of the `appiumCliDest` property, if any; or b) camel-case
151
- * @param {AppiumConfig} config - Configuration object
152
- * @returns {NormalizedAppiumConfig} New object with camel-cased keys (or `dest` keys).
153
- */
154
- export function normalizeConfig(config) {
155
- const schema = getSchema();
156
- /**
157
- * @param {AppiumConfig} config
158
- * @param {string} [section] - Keypath (lodash `_.get()` style) to section of config. If omitted, assume root Appium config schema
159
- * @todo Rewrite as a loop
160
- * @returns Normalized section of config
161
- */
162
- const normalize = (config, section) => {
163
- const obj = _.isUndefined(section) ? config : _.get(config, section, config);
164
-
165
- const mappedObj = _.mapKeys(obj, (__, prop) =>
166
- _.get(schema, `properties.server.properties[${prop}].appiumCliDest`, _.camelCase(prop))
167
- );
168
-
169
- return _.mapValues(mappedObj, (value, property) => {
170
- const nextSection = section ? `${section}.${property}` : property;
171
- return isSchemaTypeObject(schema.properties?.[property])
172
- ? normalize(config, nextSection)
173
- : value;
174
- });
175
- };
176
-
177
- /**
178
- * Returns `true` if the schema prop references an object, or if it's an object itself
179
- * @param {import('ajv').SchemaObject|object} schema - Referencing schema object
180
- */
181
- const isSchemaTypeObject = (schema) => Boolean(schema?.properties || schema?.type === 'object');
182
-
183
- return normalize(config);
184
- }
185
-
186
- /**
187
- * Result of calling {@link readConfigFile}.
188
- * @typedef ReadConfigFileResult
189
- * @property {import('ajv').ErrorObject[]} [errors] - Validation errors
190
- * @property {string} [filepath] - The path to the config file, if found
191
- * @property {boolean} [isEmpty] - If `true`, the config file exists but is empty
192
- * @property {NormalizedAppiumConfig} [config] - The parsed configuration
193
- * @property {string|import('@sidvind/better-ajv-errors').IOutputError[]} [reason] - Human-readable error messages and suggestions. If the `pretty` option is `true`, this will be a nice string to print.
194
- */
195
-
196
- /**
197
- * Options for {@link readConfigFile}.
198
- * @typedef ReadConfigFileOptions
199
- * @property {boolean} [pretty=true] If `false`, do not use color and fancy formatting in the `reason` property of the {@link ReadConfigFileResult}. The value of `reason` is then suitable for machine-reading.
200
- */
201
-
202
- /**
203
- * This is an `AsyncSearcher` which is inexplicably _not_ exported by the `lilconfig` type definition.
204
- * @typedef {ReturnType<import('lilconfig')["lilconfig"]>} LilconfigAsyncSearcher
205
- */
206
-
207
- /**
208
- * The contents of an Appium config file. Generated from schema
209
- * @typedef {import('@appium/types').AppiumConfig} AppiumConfig
210
- */
211
-
212
- /**
213
- * The contents of an Appium config file with camelcased property names (and using `appiumCliDest` value if present). Generated from {@link AppiumConfig}
214
- * @typedef {import('@appium/types').NormalizedAppiumConfig} NormalizedAppiumConfig
215
- */
216
-
217
- /**
218
- * The string should be a raw JSON string.
219
- * @typedef {string} RawJson
220
- */
221
-
222
- /**
223
- * Options for {@link formatErrors}.
224
- * @typedef FormatConfigErrorsOptions
225
- * @property {import('./config-file').RawJson} [json] - Raw JSON config (as string)
226
- * @property {boolean} [pretty=true] - Whether to format errors as a CLI-friendly string
227
- * @property {string} [schemaId] - Specific ID of a prop; otherwise entire schema
228
- */
@@ -1,146 +0,0 @@
1
- import axios from 'axios';
2
- import {fs} from '@appium/support';
3
- import logger from './logger';
4
- import _ from 'lodash';
5
-
6
- const hubUri = (config) => {
7
- const protocol = config.hubProtocol || 'http';
8
- return `${protocol}://${config.hubHost}:${config.hubPort}`;
9
- };
10
-
11
- /**
12
- * Registers a new node with a selenium grid
13
- * @param {string|object} data - Path or object representing selenium grid node config file. If a `string`, all subsequent arguments are required!
14
- * @param {string} [addr] - Bind to this address
15
- * @param {number} [port] - Bind to this port
16
- * @param {string} [basePath] - Base path for the grid
17
- */
18
- async function registerNode(data, addr, port, basePath) {
19
- let configFilePath;
20
- if (_.isString(data)) {
21
- configFilePath = data;
22
- try {
23
- data = await fs.readFile(data, 'utf-8');
24
- } catch (err) {
25
- logger.error(
26
- `Unable to load node configuration file ${configFilePath} to register with grid: ${err.message}`
27
- );
28
- return;
29
- }
30
- try {
31
- data = JSON.parse(data);
32
- } catch (err) {
33
- throw logger.errorWithException(
34
- `Syntax error in node configuration file ${configFilePath}: ${err.message}`
35
- );
36
- }
37
- }
38
-
39
- postRequest(data, addr, port, basePath);
40
- }
41
-
42
- async function registerToGrid(postOptions, configHolder) {
43
- try {
44
- const {status} = await axios(postOptions);
45
- if (status !== 200) {
46
- throw new Error(`Request failed with code ${status}`);
47
- }
48
- logger.debug(
49
- `Appium successfully registered with the the grid on ` + hubUri(configHolder.configuration)
50
- );
51
- } catch (err) {
52
- logger.error(`An attempt to register with the grid was unsuccessful: ${err.message}`);
53
- }
54
- }
55
-
56
- function postRequest(configHolder, addr, port, basePath) {
57
- // Move Selenium 3 configuration properties to configuration object
58
- if (!_.has(configHolder, 'configuration')) {
59
- let configuration = {};
60
- for (const property in /** @type {import('@appium/types').StringRecord} */ (configHolder)) {
61
- if (_.has(configHolder, property) && property !== 'capabilities') {
62
- configuration[property] = configHolder[property];
63
- delete configHolder[property];
64
- }
65
- }
66
- /** @type {import('@appium/types').StringRecord} */ (configHolder).configuration = configuration;
67
- }
68
-
69
- // if the node config does not have the appium/webdriver url, host, and port,
70
- // automatically add it based on how appium was initialized
71
- // otherwise, we will take whatever the user setup
72
- // because we will always set localhost/127.0.0.1. this won't work if your
73
- // node and grid aren't in the same place
74
- if (
75
- !configHolder.configuration.url ||
76
- !configHolder.configuration.host ||
77
- !configHolder.configuration.port
78
- ) {
79
- configHolder.configuration.url = `http://${addr}:${port}${basePath}`;
80
- configHolder.configuration.host = addr;
81
- configHolder.configuration.port = port;
82
- }
83
- // if the node config does not have id automatically add it
84
- if (!configHolder.configuration.id) {
85
- configHolder.configuration.id = `http://${configHolder.configuration.host}:${configHolder.configuration.port}`;
86
- }
87
-
88
- // the post options
89
- const regRequest = {
90
- url: `${hubUri(configHolder.configuration)}/grid/register`,
91
- method: 'POST',
92
- data: configHolder,
93
- };
94
-
95
- if (configHolder.configuration.register !== true) {
96
- logger.debug(`No registration sent (${configHolder.configuration.register} = false)`);
97
- return;
98
- }
99
-
100
- const registerCycleInterval = configHolder.configuration.registerCycle;
101
- if (isNaN(registerCycleInterval) || registerCycleInterval <= 0) {
102
- logger.warn(
103
- `'registerCycle' is not a valid positive number. ` +
104
- `No registration request will be sent to the grid.`
105
- );
106
- return;
107
- }
108
- // initiate a new Thread
109
- let first = true;
110
- logger.debug(
111
- `Starting auto register thread for the grid. ` +
112
- `Will try to register every ${registerCycleInterval} ms.`
113
- );
114
- setInterval(async function registerRetry() {
115
- if (first) {
116
- first = false;
117
- await registerToGrid(regRequest, configHolder);
118
- } else if (!(await isAlreadyRegistered(configHolder))) {
119
- // make the http POST to the grid for registration
120
- await registerToGrid(regRequest, configHolder);
121
- }
122
- }, registerCycleInterval);
123
- }
124
-
125
- async function isAlreadyRegistered(configHolder) {
126
- //check if node is already registered
127
- const id = configHolder.configuration.id;
128
- try {
129
- const {data, status} = await axios({
130
- url: `${hubUri(configHolder.configuration)}/grid/api/proxy?id=${id}`,
131
- timeout: 10000,
132
- });
133
- if (status !== 200) {
134
- throw new Error(`Request failed with code ${status}`);
135
- }
136
- if (!data.success) {
137
- // if register fail, print the debug msg
138
- logger.debug(`Grid registration error: ${data.msg}`);
139
- }
140
- return data.success;
141
- } catch (err) {
142
- logger.debug(`Hub down or not responding: ${err.message}`);
143
- }
144
- }
145
-
146
- export default registerNode;