appium 2.0.0-beta.24 → 2.0.0-beta.27

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 (124) hide show
  1. package/build/lib/appium-config.schema.json +278 -0
  2. package/build/lib/appium.js +45 -66
  3. package/build/lib/cli/args.js +19 -39
  4. package/build/lib/cli/driver-command.js +5 -9
  5. package/build/lib/cli/extension-command.js +73 -64
  6. package/build/lib/cli/extension.js +10 -23
  7. package/build/lib/cli/parser.js +9 -19
  8. package/build/lib/cli/plugin-command.js +5 -9
  9. package/build/lib/cli/utils.js +2 -4
  10. package/build/lib/config-file.js +5 -10
  11. package/build/lib/config.js +50 -20
  12. package/build/lib/constants.js +60 -0
  13. package/build/lib/extension/driver-config.js +190 -0
  14. package/build/lib/extension/extension-config.js +297 -0
  15. package/build/lib/extension/index.js +77 -0
  16. package/build/lib/extension/manifest.js +246 -0
  17. package/build/lib/extension/package-changed.js +68 -0
  18. package/build/lib/extension/plugin-config.js +87 -0
  19. package/build/lib/grid-register.js +2 -4
  20. package/build/lib/logger.js +2 -4
  21. package/build/lib/logsink.js +2 -4
  22. package/build/lib/main.js +42 -71
  23. package/build/lib/schema/appium-config-schema.js +2 -4
  24. package/build/lib/schema/arg-spec.js +11 -14
  25. package/build/lib/schema/cli-args.js +2 -4
  26. package/build/lib/schema/cli-transformers.js +2 -4
  27. package/build/lib/schema/index.js +2 -4
  28. package/build/lib/schema/keywords.js +2 -4
  29. package/build/lib/schema/schema.js +61 -37
  30. package/build/lib/utils.js +1 -32
  31. package/lib/appium.js +50 -68
  32. package/lib/cli/args.js +19 -23
  33. package/lib/cli/driver-command.js +10 -2
  34. package/lib/cli/extension-command.js +216 -135
  35. package/lib/cli/extension.js +7 -15
  36. package/lib/cli/parser.js +6 -14
  37. package/lib/cli/plugin-command.js +1 -2
  38. package/lib/config-file.js +12 -15
  39. package/lib/config.js +55 -24
  40. package/lib/constants.js +79 -0
  41. package/lib/extension/driver-config.js +230 -0
  42. package/lib/extension/extension-config.js +459 -0
  43. package/lib/extension/index.js +103 -0
  44. package/lib/extension/manifest.js +590 -0
  45. package/lib/extension/package-changed.js +64 -0
  46. package/lib/extension/plugin-config.js +111 -0
  47. package/lib/grid-register.js +4 -4
  48. package/lib/main.js +57 -93
  49. package/lib/schema/arg-spec.js +3 -3
  50. package/lib/schema/cli-args.js +1 -0
  51. package/lib/schema/keywords.js +1 -1
  52. package/lib/schema/schema.js +67 -29
  53. package/lib/utils.js +2 -32
  54. package/package.json +29 -21
  55. package/{postinstall.js → scripts/postinstall.js} +1 -1
  56. package/types/types.d.ts +70 -31
  57. package/bin/ios-webkit-debug-proxy-launcher.js +0 -71
  58. package/build/check-npm-pack-files.js +0 -23
  59. package/build/commands-yml/parse.js +0 -319
  60. package/build/commands-yml/validator.js +0 -130
  61. package/build/index.js +0 -19
  62. package/build/lib/cli/npm.js +0 -220
  63. package/build/lib/driver-config.js +0 -100
  64. package/build/lib/drivers.js +0 -100
  65. package/build/lib/ext-config-io.js +0 -165
  66. package/build/lib/extension-config.js +0 -320
  67. package/build/lib/plugin-config.js +0 -69
  68. package/build/lib/plugins.js +0 -18
  69. package/build/postinstall.js +0 -90
  70. package/build/test/cli/cli-e2e-specs.js +0 -221
  71. package/build/test/cli/cli-helpers.js +0 -86
  72. package/build/test/cli/cli-specs.js +0 -71
  73. package/build/test/cli/fixtures/test-driver/package.json +0 -27
  74. package/build/test/cli/schema-args-specs.js +0 -48
  75. package/build/test/cli/schema-e2e-specs.js +0 -47
  76. package/build/test/config-e2e-specs.js +0 -112
  77. package/build/test/config-file-e2e-specs.js +0 -209
  78. package/build/test/config-file-specs.js +0 -281
  79. package/build/test/config-specs.js +0 -246
  80. package/build/test/driver-e2e-specs.js +0 -435
  81. package/build/test/driver-specs.js +0 -386
  82. package/build/test/ext-config-io-specs.js +0 -181
  83. package/build/test/extension-config-specs.js +0 -365
  84. package/build/test/fixtures/allow-feat.txt +0 -5
  85. package/build/test/fixtures/caps.json +0 -3
  86. package/build/test/fixtures/config/allow-insecure.txt +0 -3
  87. package/build/test/fixtures/config/appium.config.bad-nodeconfig.json +0 -5
  88. package/build/test/fixtures/config/appium.config.bad.json +0 -32
  89. package/build/test/fixtures/config/appium.config.ext-good.json +0 -9
  90. package/build/test/fixtures/config/appium.config.ext-unknown-props.json +0 -10
  91. package/build/test/fixtures/config/appium.config.good.js +0 -40
  92. package/build/test/fixtures/config/appium.config.good.json +0 -33
  93. package/build/test/fixtures/config/appium.config.good.yaml +0 -30
  94. package/build/test/fixtures/config/appium.config.invalid.json +0 -31
  95. package/build/test/fixtures/config/appium.config.security-array.json +0 -5
  96. package/build/test/fixtures/config/appium.config.security-delimited.json +0 -5
  97. package/build/test/fixtures/config/appium.config.security-path.json +0 -5
  98. package/build/test/fixtures/config/driver-fake.config.json +0 -8
  99. package/build/test/fixtures/config/nodeconfig.json +0 -3
  100. package/build/test/fixtures/config/plugin-fake.config.json +0 -0
  101. package/build/test/fixtures/default-args.js +0 -35
  102. package/build/test/fixtures/deny-feat.txt +0 -5
  103. package/build/test/fixtures/driver.schema.js +0 -20
  104. package/build/test/fixtures/extensions.yaml +0 -27
  105. package/build/test/fixtures/flattened-schema.js +0 -532
  106. package/build/test/fixtures/plugin.schema.js +0 -20
  107. package/build/test/fixtures/schema-with-extensions.js +0 -28
  108. package/build/test/grid-register-specs.js +0 -74
  109. package/build/test/helpers.js +0 -75
  110. package/build/test/logger-specs.js +0 -76
  111. package/build/test/npm-specs.js +0 -20
  112. package/build/test/parser-specs.js +0 -319
  113. package/build/test/plugin-e2e-specs.js +0 -316
  114. package/build/test/schema/arg-spec-specs.js +0 -70
  115. package/build/test/schema/cli-args-specs.js +0 -408
  116. package/build/test/schema/schema-specs.js +0 -407
  117. package/build/test/utils-specs.js +0 -288
  118. package/lib/cli/npm.js +0 -251
  119. package/lib/driver-config.js +0 -101
  120. package/lib/drivers.js +0 -84
  121. package/lib/ext-config-io.js +0 -287
  122. package/lib/extension-config.js +0 -366
  123. package/lib/plugin-config.js +0 -63
  124. package/lib/plugins.js +0 -13
@@ -113,10 +113,9 @@ export async function readConfigFile (filepath, opts = {}) {
113
113
  ? await loadConfigFile(lc, filepath)
114
114
  : await searchConfigFile(lc);
115
115
 
116
- if (result && !result.isEmpty && result.filepath) {
117
- const {normalize = true, pretty = true} = opts;
116
+ if (result?.filepath && !result?.isEmpty) {
117
+ const {pretty = true} = opts;
118
118
  try {
119
- /** @type {ReadConfigFileResult} */
120
119
  let configResult;
121
120
  const errors = validate(result.config);
122
121
  if (_.isEmpty(errors)) {
@@ -131,12 +130,10 @@ export async function readConfigFile (filepath, opts = {}) {
131
130
  : {...result, errors};
132
131
  }
133
132
 
134
- if (normalize) {
135
- // normalize (to camel case) all top-level property names of the config file
136
- configResult.config = normalizeConfig(
137
- /** @type {AppiumConfig} */ (configResult.config),
138
- );
139
- }
133
+ // normalize (to camel case) all top-level property names of the config file
134
+ configResult.config = normalizeConfig(
135
+ /** @type {AppiumConfig} */ (configResult.config),
136
+ );
140
137
 
141
138
  return configResult;
142
139
  } finally {
@@ -152,11 +149,12 @@ export async function readConfigFile (filepath, opts = {}) {
152
149
  * @param {AppiumConfig} config - Configuration object
153
150
  * @returns {NormalizedAppiumConfig} New object with camel-cased keys (or `dest` keys).
154
151
  */
155
- function normalizeConfig (config) {
152
+ export function normalizeConfig (config) {
156
153
  const schema = getSchema();
157
154
  /**
158
155
  * @param {AppiumConfig} config
159
156
  * @param {string} [section] - Keypath (lodash `_.get()` style) to section of config. If omitted, assume root Appium config schema
157
+ * @todo Rewrite as a loop
160
158
  * @returns Normalized section of config
161
159
  */
162
160
  const normalize = (config, section) => {
@@ -183,19 +181,18 @@ function normalizeConfig (config) {
183
181
 
184
182
  /**
185
183
  * Result of calling {@link readConfigFile}.
186
- * @typedef {Object} ReadConfigFileResult
184
+ * @typedef ReadConfigFileResult
187
185
  * @property {import('ajv').ErrorObject[]} [errors] - Validation errors
188
186
  * @property {string} [filepath] - The path to the config file, if found
189
187
  * @property {boolean} [isEmpty] - If `true`, the config file exists but is empty
190
- * @property {AppiumConfig} [config] - The parsed configuration
188
+ * @property {NormalizedAppiumConfig} [config] - The parsed configuration
191
189
  * @property {string|betterAjvErrors.IOutputError[]} [reason] - Human-readable error messages and suggestions. If the `pretty` option is `true`, this will be a nice string to print.
192
190
  */
193
191
 
194
192
  /**
195
193
  * Options for {@link readConfigFile}.
196
- * @typedef {Object} ReadConfigFileOptions
194
+ * @typedef ReadConfigFileOptions
197
195
  * @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.
198
- * @property {boolean} [normalize=true] If `false`, do not normalize key names to camel case.
199
196
  */
200
197
 
201
198
  /**
@@ -220,7 +217,7 @@ function normalizeConfig (config) {
220
217
 
221
218
  /**
222
219
  * Options for {@link formatErrors}.
223
- * @typedef {Object} FormatConfigErrorsOptions
220
+ * @typedef FormatConfigErrorsOptions
224
221
  * @property {import('./config-file').RawJson} [json] - Raw JSON config (as string)
225
222
  * @property {boolean} [pretty=true] - Whether to format errors as a CLI-friendly string
226
223
  * @property {string} [schemaId] - Specific ID of a prop; otherwise entire schema
package/lib/config.js CHANGED
@@ -2,14 +2,13 @@
2
2
 
3
3
  /* eslint-disable no-console */
4
4
  import _ from 'lodash';
5
- import { mkdirp, system, fs } from '@appium/support';
5
+ import { system, fs } from '@appium/support';
6
6
  import axios from 'axios';
7
7
  import { exec } from 'teen_process';
8
- import { rootDir } from './utils';
9
8
  import logger from './logger';
10
9
  import semver from 'semver';
11
10
  import findUp from 'find-up';
12
- import { getDefaultsForSchema } from './schema/schema';
11
+ import { getDefaultsForSchema, getAllArgSpecs } from './schema/schema';
13
12
 
14
13
  const npmPackage = fs.readPackageJsonFrom(__dirname);
15
14
 
@@ -159,21 +158,40 @@ async function showBuildInfo () {
159
158
 
160
159
  /**
161
160
  * Returns k/v pairs of server arguments which are _not_ the defaults.
162
- * @param {ParsedArgs} args
161
+ * @param {ParsedArgs} parsedArgs
163
162
  * @returns {Partial<ParsedArgs>}
164
163
  */
165
- function getNonDefaultServerArgs (args) {
166
- // hopefully these function names are descriptive enough
164
+ function getNonDefaultServerArgs (parsedArgs) {
165
+ /**
166
+ * Flattens parsed args into a single level object for comparison with
167
+ * flattened defaults across server args and extension args.
168
+ * @param {ParsedArgs} args
169
+ * @returns {Record<string, { value: any, argSpec: import('./schema/arg-spec').ArgSpec }>}
170
+ */
171
+ const flatten = (args) => {
172
+ const argSpecs = getAllArgSpecs();
173
+ const flattened = _.reduce([...argSpecs.values()], (acc, argSpec) => {
174
+ if (_.has(args, argSpec.dest)) {
175
+ acc[argSpec.dest] = {value: _.get(args, argSpec.dest), argSpec};
176
+ }
177
+ return acc;
178
+ }, /** @type {Record<string, { value: any, argSpec: import('./schema/arg-spec').ArgSpec }>} */({}));
179
+
180
+ return flattened;
181
+ };
182
+
183
+ const args = flatten(parsedArgs);
167
184
 
168
- const typesDiffer = /** @param {string} dest */(dest) => typeof args[dest] !== typeof defaultsFromSchema[dest];
185
+ // hopefully these function names are descriptive enough
186
+ const typesDiffer = /** @param {string} dest */(dest) => typeof args[dest].value !== typeof defaultsFromSchema[dest];
169
187
 
170
188
  const defaultValueIsArray = /** @param {string} dest */(dest) => _.isArray(defaultsFromSchema[dest]);
171
189
 
172
- const argsValueIsArray = /** @param {string} dest */(dest) => _.isArray(args[dest]);
190
+ const argsValueIsArray = /** @param {string} dest */(dest) => _.isArray(args[dest].value);
173
191
 
174
- const arraysDiffer = /** @param {string} dest */(dest) => _.gt(_.size(_.difference(args[dest], defaultsFromSchema[dest])), 0);
192
+ const arraysDiffer = /** @param {string} dest */(dest) => _.gt(_.size(_.difference(args[dest].value, defaultsFromSchema[dest])), 0);
175
193
 
176
- const valuesDiffer = /** @param {string} dest */(dest) => args[dest] !== defaultsFromSchema[dest];
194
+ const valuesDiffer = /** @param {string} dest */(dest) => args[dest].value !== defaultsFromSchema[dest];
177
195
 
178
196
  const defaultIsDefined = /** @param {string} dest */(dest) => !_.isUndefined(defaultsFromSchema[dest]);
179
197
 
@@ -212,9 +230,13 @@ function getNonDefaultServerArgs (args) {
212
230
  ])
213
231
  ]);
214
232
 
215
- const defaultsFromSchema = getDefaultsForSchema();
233
+ const defaultsFromSchema = getDefaultsForSchema(true);
216
234
 
217
- return _.pickBy(args, (__, key) => isNotDefault(key));
235
+ return _.reduce(
236
+ _.pickBy(args, (__, key) => isNotDefault(key)),
237
+ // explodes the flattened object back into nested one
238
+ (acc, {value, argSpec}) => _.set(acc, argSpec.dest, value), /** @type {Partial<ParsedArgs>} */({})
239
+ );
218
240
  }
219
241
 
220
242
  /**
@@ -236,22 +258,29 @@ const compactConfig = _.partial(
236
258
  * The actual shape of `preConfigParsedArgs` and `defaults` does not matter for the purposes of this function,
237
259
  * but it's intended to be called with values of type {@link ParsedArgs} and `DefaultValues<true>`, respectively.
238
260
  *
239
- * @param {object} preConfigParsedArgs - Parsed CLI args (or param to `init()`) before config & defaults applied
240
- * @param {import('./config-file').ReadConfigFileResult} configResult - Result of attempting to load a config file
241
- * @param {object} defaults - Configuration defaults from schemas
261
+ * @param {Partial<ParsedArgs>} nonDefaultPreConfigParsedArgs - Parsed CLI args (or param to `init()`) before config & defaults applied
262
+ * @param {import('./config-file').ReadConfigFileResult} configResult - Result of attempting to load a config file. _Must_ be normalized
263
+ * @param {Partial<ParsedArgs>} defaults - Configuration defaults from schemas
264
+ * @param {ParsedArgs} parsedArgs - Entire parsed args object
242
265
  */
243
- function showConfig (preConfigParsedArgs, configResult, defaults) {
266
+ function showConfig (nonDefaultPreConfigParsedArgs, configResult, defaults, parsedArgs) {
244
267
  console.log('Appium Configuration\n');
268
+ console.log('from defaults:\n');
269
+ console.dir(compactConfig(defaults));
245
270
  if (configResult.config) {
246
- console.log(`via config file at ${configResult.filepath}:\n`);
271
+ console.log(`\nfrom config file at ${configResult.filepath}:\n`);
247
272
  console.dir(compactConfig(configResult.config));
248
273
  } else {
249
- console.log(`(no configuration file loaded)\n`);
274
+ console.log(`\n(no configuration file loaded)`);
250
275
  }
251
- console.log('via CLI or function call:\n');
252
- console.dir(compactConfig(preConfigParsedArgs));
253
- console.log('\nvia defaults:\n');
254
- console.dir(compactConfig(defaults));
276
+ if (_.isEmpty(nonDefaultPreConfigParsedArgs)) {
277
+ console.log(`\n(no CLI parameters provided)`);
278
+ } else {
279
+ console.log('\nvia CLI or function call:\n');
280
+ console.dir(compactConfig(nonDefaultPreConfigParsedArgs));
281
+ }
282
+ console.log('\nfinal configuration:\n');
283
+ console.dir(compactConfig(parsedArgs));
255
284
  }
256
285
 
257
286
  /**
@@ -259,17 +288,19 @@ function showConfig (preConfigParsedArgs, configResult, defaults) {
259
288
  */
260
289
  async function validateTmpDir (tmpDir) {
261
290
  try {
262
- await mkdirp(tmpDir);
291
+ await fs.mkdirp(tmpDir);
263
292
  } catch (e) {
264
293
  throw new Error(`We could not ensure that the temp dir you specified ` +
265
294
  `(${tmpDir}) exists. Please make sure it's writeable.`);
266
295
  }
267
296
  }
268
297
 
298
+ const rootDir = fs.findRoot(__dirname);
299
+
269
300
  export {
270
301
  getBuildInfo, checkNodeOk, showBuildInfo,
271
302
  warnNodeDeprecations, validateTmpDir, getNonDefaultServerArgs,
272
- getGitRev, APPIUM_VER, updateBuildInfo, showConfig
303
+ getGitRev, APPIUM_VER, updateBuildInfo, showConfig, rootDir
273
304
  };
274
305
 
275
306
  /**
@@ -0,0 +1,79 @@
1
+ // @ts-check
2
+
3
+ import path from 'path';
4
+
5
+ /**
6
+ * The name of the extension type for drivers
7
+ */
8
+ export const DRIVER_TYPE = 'driver';
9
+
10
+ /**
11
+ * The name of the extension type for plugins
12
+ */
13
+ export const PLUGIN_TYPE = 'plugin';
14
+
15
+ /**
16
+ * The `server` command of the `appium` CLI
17
+ */
18
+ export const SERVER_SUBCOMMAND = 'server';
19
+
20
+ /**
21
+ * The value of `--use-plugins` if _all_ plugins should be loaded
22
+ */
23
+ export const USE_ALL_PLUGINS = 'all';
24
+
25
+ // This is a map of plugin names to npm packages representing those plugins.
26
+ // The plugins in this list will be available to the CLI so users can just
27
+ // type 'appium plugin install 'name'', rather than having to specify the full
28
+ // npm package. I.e., these are the officially recognized plugins.
29
+ export const KNOWN_PLUGINS = Object.freeze(
30
+ /** @type {const} */ ({
31
+ images: '@appium/images-plugin',
32
+ 'execute-driver': '@appium/execute-driver-plugin',
33
+ 'relaxed-caps': '@appium/relaxed-caps-plugin',
34
+ }),
35
+ );
36
+
37
+ // This is a map of driver names to npm packages representing those drivers.
38
+ // The drivers in this list will be available to the CLI so users can just
39
+ // type 'appium driver install 'name'', rather than having to specify the full
40
+ // npm package. I.e., these are the officially recognized drivers.
41
+ export const KNOWN_DRIVERS = Object.freeze(
42
+ /** @type {const} */ ({
43
+ uiautomator2: 'appium-uiautomator2-driver',
44
+ xcuitest: 'appium-xcuitest-driver',
45
+ youiengine: 'appium-youiengine-driver',
46
+ windows: 'appium-windows-driver',
47
+ mac: 'appium-mac-driver',
48
+ mac2: 'appium-mac2-driver',
49
+ espresso: 'appium-espresso-driver',
50
+ tizen: 'appium-tizen-driver',
51
+ flutter: 'appium-flutter-driver',
52
+ safari: 'appium-safari-driver',
53
+ gecko: 'appium-geckodriver',
54
+ }),
55
+ );
56
+
57
+ /**
58
+ * Relative path to directory containing any Appium internal files
59
+ */
60
+ export const CACHE_DIR_RELATIVE_PATH = path.join(
61
+ 'node_modules',
62
+ '.cache',
63
+ 'appium',
64
+ );
65
+
66
+ /**
67
+ * Relative path to hashfile (from `APPIUM_HOME`) of consuming project's `package.json` (if it exists)
68
+ */
69
+ export const PKG_HASHFILE_RELATIVE_PATH = path.join(
70
+ CACHE_DIR_RELATIVE_PATH,
71
+ 'package.hash',
72
+ );
73
+
74
+
75
+ export const EXT_SUBCOMMAND_LIST = 'list';
76
+ export const EXT_SUBCOMMAND_INSTALL = 'install';
77
+ export const EXT_SUBCOMMAND_UNINSTALL = 'uninstall';
78
+ export const EXT_SUBCOMMAND_UPDATE = 'update';
79
+ export const EXT_SUBCOMMAND_RUN = 'run';
@@ -0,0 +1,230 @@
1
+ // @ts-check
2
+
3
+ import _ from 'lodash';
4
+ import { DRIVER_TYPE } from '../constants';
5
+ import log from '../logger';
6
+ import { ExtensionConfig } from './extension-config';
7
+
8
+ /**
9
+ * @extends {ExtensionConfig<DriverType>}
10
+ */
11
+ export class DriverConfig extends ExtensionConfig {
12
+
13
+ /**
14
+ * A set of unique automation names used by drivers.
15
+ * @type {Set<string>}
16
+ */
17
+ knownAutomationNames;
18
+
19
+ /**
20
+ * A mapping of {@link Manifest} instances to {@link DriverConfig} instances.
21
+ *
22
+ * `Manifest` and `ExtensionConfig` have a one-to-many relationship; each `Manifest` should be associated with a `DriverConfig` and a `PluginConfig`; no more, no less.
23
+ *
24
+ * This variable tracks the `Manifest`-to-`DriverConfig` portion.
25
+ *
26
+ * @type {WeakMap<Manifest,DriverConfig>}
27
+ * @private
28
+ */
29
+ static _instances = new WeakMap();
30
+
31
+ /**
32
+ * Call {@link DriverConfig.create} instead.
33
+ * @private
34
+ * @param {import('./manifest').Manifest} manifest - Manifest instance
35
+ * @param {DriverConfigOptions} [opts]
36
+ */
37
+ constructor (manifest, {logFn, extData} = {}) {
38
+ super(DRIVER_TYPE, manifest, logFn);
39
+
40
+ this.knownAutomationNames = new Set();
41
+
42
+ if (extData) {
43
+ this.validate(extData);
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Creates a new {@link DriverConfig} instance for a {@link Manifest} instance.
49
+ *
50
+ * @param {Manifest} manifest
51
+ * @param {DriverConfigOptions} [opts]
52
+ * @throws If `manifest` already associated with a `DriverConfig`
53
+ * @returns {DriverConfig}
54
+ */
55
+ static create (manifest, {extData, logFn} = {}) {
56
+ const instance = new DriverConfig(manifest, {logFn, extData});
57
+ if (DriverConfig.getInstance(manifest)) {
58
+ throw new Error(`Manifest with APPIUM_HOME ${manifest.appiumHome} already has a DriverConfig; use DriverConfig.getInstance() to retrieve it.`);
59
+ }
60
+ DriverConfig._instances.set(manifest, instance);
61
+ return instance;
62
+ }
63
+
64
+ /**
65
+ * Returns a DriverConfig associated with a Manifest
66
+ * @param {Manifest} manifest
67
+ * @returns {DriverConfig|undefined}
68
+ */
69
+ static getInstance (manifest) {
70
+ return DriverConfig._instances.get(manifest);
71
+ }
72
+
73
+ /**
74
+ * Checks extensions for problems
75
+ * @param {ExtRecord<DriverType>} exts
76
+ */
77
+ validate (exts) {
78
+ this.knownAutomationNames.clear();
79
+ return super.validate(exts);
80
+ }
81
+
82
+ /**
83
+ * @param {ManifestDriverData} extData
84
+ * @returns {import('./extension-config').Problem[]}
85
+ */
86
+ getConfigProblems (extData) {
87
+ const problems = [];
88
+ const {platformNames, automationName} = extData;
89
+
90
+ if (!_.isArray(platformNames)) {
91
+ problems.push({
92
+ err: 'Missing or incorrect supported platformNames list.',
93
+ val: platformNames
94
+ });
95
+ } else {
96
+ if (_.isEmpty(platformNames)) {
97
+ problems.push({
98
+ err: 'Empty platformNames list.',
99
+ val: platformNames
100
+ });
101
+ } else {
102
+ for (const pName of platformNames) {
103
+ if (!_.isString(pName)) {
104
+ problems.push({err: 'Incorrectly formatted platformName.', val: pName});
105
+ }
106
+ }
107
+ }
108
+ }
109
+
110
+ if (!_.isString(automationName)) {
111
+ problems.push({err: 'Missing or incorrect automationName', val: automationName});
112
+ }
113
+
114
+ if (this.knownAutomationNames.has(automationName)) {
115
+ problems.push({
116
+ err: 'Multiple drivers claim support for the same automationName',
117
+ val: automationName
118
+ });
119
+ }
120
+
121
+ // should we retain the name at the end of this function, once we've checked there are no problems?
122
+ this.knownAutomationNames.add(automationName);
123
+
124
+ return problems;
125
+ }
126
+
127
+ /**
128
+ * @param {ExtName<DriverType>} driverName
129
+ * @param {ManifestDriverData} extData
130
+ * @returns {string}
131
+ */
132
+ extensionDesc (driverName, {version, automationName}) {
133
+ return `${driverName}@${version} (automationName '${automationName}')`;
134
+ }
135
+
136
+ /**
137
+ * Given capabilities, find a matching driver within the config. Load its class and return it along with version and driver name.
138
+ * @param { { automationName: string, platformName: string } } caps
139
+ * @returns { { driver: import('./manifest').DriverClass, version: string, driverName: string } }
140
+ */
141
+ findMatchingDriver ({automationName, platformName}) {
142
+ if (!_.isString(platformName)) {
143
+ throw new Error('You must include a platformName capability');
144
+ }
145
+
146
+ if (!_.isString(automationName)) {
147
+ throw new Error('You must include an automationName capability');
148
+ }
149
+
150
+ log.info(`Attempting to find matching driver for automationName ` +
151
+ `'${automationName}' and platformName '${platformName}'`);
152
+
153
+ try {
154
+ const {
155
+ driverName,
156
+ mainClass,
157
+ version,
158
+ } = this._getDriverBySupport(automationName, platformName);
159
+ log.info(`The '${driverName}' driver was installed and matched caps.`);
160
+ log.info(`Will require it at ${this.getInstallPath(driverName)}`);
161
+ const driver = this.require(driverName);
162
+ if (!driver) {
163
+ throw new Error(`Driver '${driverName}' did not export a class with name '${mainClass}'. Contact the author of the driver!`);
164
+ }
165
+ return {driver, version, driverName};
166
+ } catch (err) {
167
+ const msg = `Could not find a driver for automationName ` +
168
+ `'${automationName}' and platformName ${platformName}'. ` +
169
+ `Have you installed a driver that supports those ` +
170
+ `capabilities? Run 'appium driver list --installed' to see. ` +
171
+ `(Lower-level error: ${err.message})`;
172
+ throw new Error(msg);
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Given an automation name and platform name, find a suitable driver and return its extension data.
178
+ * @param {string} matchAutomationName
179
+ * @param {string} matchPlatformName
180
+ * @returns {ManifestDriverData & { driverName: string } }
181
+ */
182
+ _getDriverBySupport (matchAutomationName, matchPlatformName) {
183
+ const drivers = this.installedExtensions;
184
+ for (const [driverName, driverData] of _.toPairs(drivers)) {
185
+ const {automationName, platformNames} = driverData;
186
+ const aNameMatches = automationName.toLowerCase() === matchAutomationName.toLowerCase();
187
+ const pNameMatches = _.includes(platformNames.map(_.toLower),
188
+ matchPlatformName.toLowerCase());
189
+
190
+ if (aNameMatches && pNameMatches) {
191
+ return {driverName, ...driverData};
192
+ }
193
+
194
+ if (aNameMatches) {
195
+ throw new Error(`Driver '${driverName}' supports automationName ` +
196
+ `'${automationName}', but Appium could not find ` +
197
+ `support for platformName '${matchPlatformName}'. Supported ` +
198
+ `platformNames are: ` +
199
+ JSON.stringify(platformNames));
200
+ }
201
+ }
202
+
203
+ throw new Error(`Could not find installed driver to support given caps`);
204
+ }
205
+ }
206
+
207
+ /**
208
+ * @typedef DriverConfigOptions
209
+ * @property {import('./extension-config').ExtensionLogFn} [logFn] - Optional logging function
210
+ * @property {ManifestData['drivers']} [extData] - Extension data
211
+ */
212
+
213
+ /**
214
+ * @typedef {import('./manifest').ExternalData<DriverType>} ExternalDriverData
215
+ * @typedef {import('./manifest').ManifestDriverData} ManifestDriverData
216
+ * @typedef {import('./manifest').ManifestData} ManifestData
217
+ * @typedef {import('./manifest').DriverType} DriverType
218
+ * @typedef {import('./manifest').Manifest} Manifest
219
+ */
220
+
221
+ /**
222
+ * @template T
223
+ * @typedef {import('./extension-config').ExtRecord<T>} ExtRecord
224
+ */
225
+
226
+ /**
227
+ * @template T
228
+ * @typedef {import('./extension-config').ExtName<T>} ExtName
229
+ */
230
+