appium 2.0.0-beta.43 → 2.0.0-beta.45

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 (76) hide show
  1. package/build/lib/appium.d.ts +137 -15
  2. package/build/lib/appium.d.ts.map +1 -1
  3. package/build/lib/appium.js +5 -5
  4. package/build/lib/appium.js.map +1 -0
  5. package/build/lib/cli/args.js +3 -3
  6. package/build/lib/cli/args.js.map +1 -0
  7. package/build/lib/cli/driver-command.js +3 -3
  8. package/build/lib/cli/driver-command.js.map +1 -0
  9. package/build/lib/cli/extension-command.d.ts.map +1 -1
  10. package/build/lib/cli/extension-command.js +4 -4
  11. package/build/lib/cli/extension-command.js.map +1 -0
  12. package/build/lib/cli/extension.js +3 -3
  13. package/build/lib/cli/extension.js.map +1 -0
  14. package/build/lib/cli/parser.js +3 -3
  15. package/build/lib/cli/parser.js.map +1 -0
  16. package/build/lib/cli/plugin-command.js +3 -3
  17. package/build/lib/cli/plugin-command.js.map +1 -0
  18. package/build/lib/cli/utils.js +3 -3
  19. package/build/lib/cli/utils.js.map +1 -0
  20. package/build/lib/config-file.js +3 -3
  21. package/build/lib/config-file.js.map +1 -0
  22. package/build/lib/config.js +3 -3
  23. package/build/lib/config.js.map +1 -0
  24. package/build/lib/constants.js +3 -3
  25. package/build/lib/constants.js.map +1 -0
  26. package/build/lib/extension/driver-config.d.ts +3 -2
  27. package/build/lib/extension/driver-config.d.ts.map +1 -1
  28. package/build/lib/extension/driver-config.js +3 -3
  29. package/build/lib/extension/driver-config.js.map +1 -0
  30. package/build/lib/extension/extension-config.js +3 -3
  31. package/build/lib/extension/extension-config.js.map +1 -0
  32. package/build/lib/extension/index.js +3 -3
  33. package/build/lib/extension/index.js.map +1 -0
  34. package/build/lib/extension/manifest.d.ts +1 -2
  35. package/build/lib/extension/manifest.d.ts.map +1 -1
  36. package/build/lib/extension/manifest.js +35 -30
  37. package/build/lib/extension/manifest.js.map +1 -0
  38. package/build/lib/extension/package-changed.js +3 -3
  39. package/build/lib/extension/package-changed.js.map +1 -0
  40. package/build/lib/extension/plugin-config.js +3 -3
  41. package/build/lib/extension/plugin-config.js.map +1 -0
  42. package/build/lib/grid-register.js +3 -3
  43. package/build/lib/grid-register.js.map +1 -0
  44. package/build/lib/logger.js.map +1 -0
  45. package/build/lib/logsink.js +3 -3
  46. package/build/lib/logsink.js.map +1 -0
  47. package/build/lib/main.d.ts.map +1 -1
  48. package/build/lib/main.js +4 -3
  49. package/build/lib/main.js.map +1 -0
  50. package/build/lib/schema/arg-spec.js +3 -3
  51. package/build/lib/schema/arg-spec.js.map +1 -0
  52. package/build/lib/schema/cli-args.js +3 -3
  53. package/build/lib/schema/cli-args.js.map +1 -0
  54. package/build/lib/schema/cli-transformers.d.ts +1 -1
  55. package/build/lib/schema/cli-transformers.d.ts.map +1 -1
  56. package/build/lib/schema/cli-transformers.js +23 -18
  57. package/build/lib/schema/cli-transformers.js.map +1 -0
  58. package/build/lib/schema/index.js.map +1 -0
  59. package/build/lib/schema/keywords.js.map +1 -0
  60. package/build/lib/schema/schema.js +3 -3
  61. package/build/lib/schema/schema.js.map +1 -0
  62. package/build/lib/utils.d.ts +296 -27
  63. package/build/lib/utils.d.ts.map +1 -1
  64. package/build/lib/utils.js +60 -27
  65. package/build/lib/utils.js.map +1 -0
  66. package/build/tsconfig.tsbuildinfo +1 -1
  67. package/lib/appium.js +32 -18
  68. package/lib/cli/extension-command.js +2 -1
  69. package/lib/extension/driver-config.js +2 -1
  70. package/lib/extension/manifest.js +49 -32
  71. package/lib/main.js +3 -1
  72. package/lib/schema/cli-transformers.js +27 -23
  73. package/lib/utils.js +143 -62
  74. package/package.json +17 -18
  75. package/test.d.ts +0 -7
  76. package/test.js +0 -13
package/lib/appium.js CHANGED
@@ -14,13 +14,9 @@ import AsyncLock from 'async-lock';
14
14
  import {parseCapsForInnerDriver, pullSettings} from './utils';
15
15
  import {util, node, logger} from '@appium/support';
16
16
  import {getDefaultsForExtension} from './schema';
17
- import {DRIVER_TYPE, PLUGIN_TYPE} from './constants';
17
+ import {DRIVER_TYPE} from './constants';
18
18
 
19
- /**
20
- * Invariant set of base constraints
21
- * @type {Readonly<Constraints>}
22
- */
23
- const desiredCapabilityConstraints = Object.freeze({
19
+ const desiredCapabilityConstraints = /** @type {const} */ ({
24
20
  automationName: {
25
21
  presence: true,
26
22
  isString: true,
@@ -30,6 +26,9 @@ const desiredCapabilityConstraints = Object.freeze({
30
26
  isString: true,
31
27
  },
32
28
  });
29
+ /**
30
+ * @typedef {typeof desiredCapabilityConstraints} AppiumDriverConstraints
31
+ */
33
32
 
34
33
  const sessionsListGuard = new AsyncLock();
35
34
  const pendingDriversGuard = new AsyncLock();
@@ -181,10 +180,10 @@ class AppiumDriver extends DriverCore {
181
180
  /**
182
181
  * Retrieves all CLI arguments for a specific plugin.
183
182
  * @param {string} extName - Plugin name
184
- * @returns {Record<string,unknown>} Arguments object. If none, an empty object.
183
+ * @returns {StringRecord} Arguments object. If none, an empty object.
185
184
  */
186
185
  getCliArgsForPlugin(extName) {
187
- return /** @type {Record<string,unknown>} */ (this.args.plugin?.[extName] ?? {});
186
+ return /** @type {StringRecord} */ (this.args.plugin?.[extName] ?? {});
188
187
  }
189
188
 
190
189
  /**
@@ -194,12 +193,10 @@ class AppiumDriver extends DriverCore {
194
193
  *
195
194
  * _Note that this behavior currently (May 18 2022) differs from how plugins are handled_ (see {@linkcode AppiumDriver.getCliArgsForPlugin}).
196
195
  * @param {string} extName - Driver name
197
- * @returns {Record<string,unknown>|undefined} Arguments object. If none, `undefined`
196
+ * @returns {StringRecord|undefined} Arguments object. If none, `undefined`
198
197
  */
199
198
  getCliArgsForDriver(extName) {
200
- const allCliArgsForExt = /** @type {Record<string,unknown>|undefined} */ (
201
- this.args.driver?.[extName]
202
- );
199
+ const allCliArgsForExt = /** @type {StringRecord|undefined} */ (this.args.driver?.[extName]);
203
200
 
204
201
  if (!_.isEmpty(allCliArgsForExt)) {
205
202
  const defaults = getDefaultsForExtension(DRIVER_TYPE, extName);
@@ -214,9 +211,9 @@ class AppiumDriver extends DriverCore {
214
211
 
215
212
  /**
216
213
  * Create a new session
217
- * @param {W3CCapabilities} jsonwpCaps JSONWP formatted desired capabilities
218
- * @param {W3CCapabilities} reqCaps Required capabilities (JSONWP standard)
219
- * @param {W3CCapabilities} w3cCapabilities W3C capabilities
214
+ * @param {W3CCapabilities<AppiumDriverConstraints>} jsonwpCaps JSONWP formatted desired capabilities
215
+ * @param {W3CCapabilities<AppiumDriverConstraints>} reqCaps Required capabilities (JSONWP standard)
216
+ * @param {W3CCapabilities<AppiumDriverConstraints>} w3cCapabilities W3C capabilities
220
217
  * @param {DriverData[]} [driverData]
221
218
  */
222
219
  async createSession(jsonwpCaps, reqCaps, w3cCapabilities, driverData) {
@@ -249,9 +246,11 @@ class AppiumDriver extends DriverCore {
249
246
  );
250
247
 
251
248
  const {desiredCaps, processedJsonwpCapabilities, processedW3CCapabilities} =
252
- /** @type {import('./utils').ParsedDriverCaps} */ (parsedCaps);
249
+ /** @type {import('./utils').ParsedDriverCaps<AppiumDriverConstraints>} */ (parsedCaps);
253
250
  protocol = parsedCaps.protocol;
254
- const error = /** @type {import('./utils').InvalidCaps} */ (parsedCaps).error;
251
+ const error = /** @type {import('./utils').InvalidCaps<AppiumDriverConstraints>} */ (
252
+ parsedCaps
253
+ ).error;
255
254
  // If the parsing of the caps produced an error, throw it in here
256
255
  if (error) {
257
256
  throw error;
@@ -783,6 +782,11 @@ class AppiumDriver extends DriverCore {
783
782
  return dstSession && _.isFunction(dstSession.proxyActive) && dstSession.proxyActive(sessionId);
784
783
  }
785
784
 
785
+ /**
786
+ *
787
+ * @param {string} sessionId
788
+ * @returns {import('@appium/types').RouteMatcher[]}
789
+ */
786
790
  getProxyAvoidList(sessionId) {
787
791
  const dstSession = this.sessions[sessionId];
788
792
  return dstSession ? dstSession.getProxyAvoidList() : [];
@@ -826,7 +830,6 @@ export {AppiumDriver};
826
830
  * @typedef {import('@appium/types').ExternalDriver} ExternalDriver
827
831
  * @typedef {import('@appium/types').Driver} Driver
828
832
  * @typedef {import('@appium/types').DriverClass} DriverClass
829
- * @typedef {import('@appium/types').W3CCapabilities} W3CCapabilities
830
833
  * @typedef {import('@appium/types').DriverData} DriverData
831
834
  * @typedef {import('@appium/types').ServerArgs} DriverOpts
832
835
  * @typedef {import('@appium/types').Constraints} Constraints
@@ -837,6 +840,7 @@ export {AppiumDriver};
837
840
  * @typedef {import('@appium/types').PluginClass} PluginClass
838
841
  * @typedef {import('@appium/types').PluginType} PluginType
839
842
  * @typedef {import('@appium/types').DriverType} DriverType
843
+ * @typedef {import('@appium/types').StringRecord} StringRecord
840
844
  * @typedef {import('@appium/types').SessionHandler<SessionHandlerResult<any[]>,SessionHandlerResult<void>>} SessionHandler
841
845
  */
842
846
 
@@ -849,3 +853,13 @@ export {AppiumDriver};
849
853
  * @property {Error} [error]
850
854
  * @property {string} [protocol]
851
855
  */
856
+
857
+ /**
858
+ * @template {Constraints} C
859
+ * @typedef {import('@appium/types').W3CCapabilities<C>} W3CCapabilities
860
+ */
861
+
862
+ /**
863
+ * @template {Constraints} C
864
+ * @typedef {import('@appium/types').Capabilities<C>} Capabilities
865
+ */
@@ -576,13 +576,14 @@ class ExtensionCommand {
576
576
  // this is a helper method, 'ext' is assumed to already be installed here, and of the npm
577
577
  // install type
578
578
  const {version, pkgName} = this.config.installedExtensions[ext];
579
+ /** @type {string?} */
579
580
  let unsafeUpdate = await npm.getLatestVersion(this.config.appiumHome, pkgName);
580
581
  let safeUpdate = await npm.getLatestSafeUpgradeVersion(
581
582
  this.config.appiumHome,
582
583
  pkgName,
583
584
  version
584
585
  );
585
- if (!util.compareVersions(unsafeUpdate, '>', version)) {
586
+ if (unsafeUpdate !== null && !util.compareVersions(unsafeUpdate, '>', version)) {
586
587
  // the latest version is not greater than the current version, so there's no possible update
587
588
  unsafeUpdate = null;
588
589
  safeUpdate = null;
@@ -133,7 +133,8 @@ export class DriverConfig extends ExtensionConfig {
133
133
 
134
134
  /**
135
135
  * Given capabilities, find a matching driver within the config. Load its class and return it along with version and driver name.
136
- * @param {Capabilities} caps
136
+ * @template {import('@appium/types').StringRecord} C
137
+ * @param {C} caps
137
138
  * @returns {MatchedDriver}
138
139
  */
139
140
  findMatchingDriver({automationName, platformName}) {
@@ -2,6 +2,8 @@
2
2
  * Module containing {@link Manifest} which handles reading & writing of extension config files.
3
3
  */
4
4
 
5
+ import B from 'bluebird';
6
+ import glob from 'glob';
5
7
  import {env, fs} from '@appium/support';
6
8
  import _ from 'lodash';
7
9
  import path from 'path';
@@ -11,23 +13,6 @@ import log from '../logger';
11
13
  import {INSTALL_TYPE_NPM} from './extension-config';
12
14
  import {packageDidChange} from './package-changed';
13
15
 
14
- /**
15
- * Default depth to search in directory tree for whatever it is we're looking for.
16
- *
17
- * It's 4 because smaller numbers didn't work.
18
- */
19
- const DEFAULT_SEARCH_DEPTH = 4;
20
-
21
- /**
22
- * Default options for {@link findExtensions}.
23
- * @type {Readonly<import('klaw').Options>}
24
- */
25
- const DEFAULT_FIND_EXTENSIONS_OPTS = Object.freeze({
26
- depthLimit: DEFAULT_SEARCH_DEPTH,
27
- /* istanbul ignore next */
28
- filter: (filepath) => !path.basename(filepath).startsWith('.'),
29
- });
30
-
31
16
  /**
32
17
  * Current configuration schema revision!
33
18
  */
@@ -175,26 +160,58 @@ export class Manifest {
175
160
 
176
161
  /**
177
162
  * Searches `APPIUM_HOME` for installed extensions and adds them to the manifest.
178
- * @param {SyncWithInstalledExtensionsOpts} opts
179
163
  * @returns {Promise<boolean>} `true` if any extensions were added, `false` otherwise.
180
164
  */
181
- async syncWithInstalledExtensions({depthLimit = DEFAULT_SEARCH_DEPTH} = {}) {
182
- const walkOpts = _.defaults({depthLimit}, DEFAULT_FIND_EXTENSIONS_OPTS);
165
+ async syncWithInstalledExtensions() {
183
166
  // this could be parallelized, but we can't use fs.walk as an async iterator
184
167
  let didChange = false;
185
- for await (const {stats, path: filepath} of fs.walk(this._appiumHome, walkOpts)) {
186
- if (filepath !== this._appiumHome && stats.isDirectory()) {
187
- try {
188
- const pkg = await env.readPackageInDir(filepath);
189
- if (pkg && isExtension(pkg)) {
190
- // it's possible that this extension already exists in the manifest,
191
- // so only update `didChange` if it's new.
192
- const added = this.addExtensionFromPackage(pkg, path.join(filepath, 'package.json'));
193
- didChange = didChange || added;
168
+
169
+ /**
170
+ * Listener for the `match` event of a `glob` instance
171
+ * @param {string} filepath - Path to a `package.json`
172
+ * @returns {Promise<void>}
173
+ */
174
+ const onMatch = async (filepath) => {
175
+ try {
176
+ const pkg = JSON.parse(await fs.readFile(filepath, 'utf8'));
177
+ if (isDriver(pkg) || isPlugin(pkg)) {
178
+ const changed = this.addExtensionFromPackage(pkg, filepath);
179
+ didChange = didChange || changed;
180
+ }
181
+ } catch {}
182
+ };
183
+
184
+ /**
185
+ * A list of `Promise`s which read `package.json` files looking for Appium extensions.
186
+ * @type {Promise<void>[]}
187
+ */
188
+ const queue = [
189
+ // look at `package.json` in `APPIUM_HOME` only
190
+ onMatch(path.join(this._appiumHome, 'package.json')),
191
+ ];
192
+
193
+ // add dependencies to the queue
194
+ await new B((resolve, reject) => {
195
+ glob(
196
+ 'node_modules/{*,@*/*}/package.json',
197
+ {cwd: this._appiumHome, silent: true, absolute: true},
198
+ // eslint-disable-next-line promise/prefer-await-to-callbacks
199
+ (err) => {
200
+ if (err) {
201
+ reject(err);
194
202
  }
195
- } catch {}
196
- }
197
- }
203
+ resolve();
204
+ }
205
+ )
206
+ .on('error', reject)
207
+ .on('match', (filepath) => {
208
+ queue.push(onMatch(filepath));
209
+ });
210
+ });
211
+
212
+ // wait for everything to finish
213
+ await B.all(queue);
214
+
198
215
  return didChange;
199
216
  }
200
217
 
package/lib/main.js CHANGED
@@ -26,7 +26,7 @@ import {loadExtensions, getActivePlugins, getActiveDrivers} from './extension';
26
26
  import {DRIVER_TYPE, PLUGIN_TYPE, SERVER_SUBCOMMAND} from './constants';
27
27
  import registerNode from './grid-register';
28
28
  import {getDefaultsForSchema, validate} from './schema/schema';
29
- import {inspect} from './utils';
29
+ import {inspect, adjustNodePath} from './utils';
30
30
 
31
31
  const {resolveAppiumHome} = env;
32
32
 
@@ -172,6 +172,8 @@ function areServerCommandArgs(args) {
172
172
  async function init(args) {
173
173
  const appiumHome = args?.appiumHome ?? (await resolveAppiumHome());
174
174
 
175
+ adjustNodePath();
176
+
175
177
  const {driverConfig, pluginConfig} = await loadExtensions(appiumHome);
176
178
 
177
179
  const parser = getParser();
@@ -1,5 +1,5 @@
1
1
  import {ArgumentTypeError} from 'argparse';
2
- import {readFileSync} from 'fs';
2
+ import {readFileSync, existsSync} from 'fs';
3
3
  import _ from 'lodash';
4
4
 
5
5
  /**
@@ -55,25 +55,30 @@ export const transformers = {
55
55
  /**
56
56
  * Given a CSV-style string or pathname, parse it into an array.
57
57
  * The file can also be split on newlines.
58
- * @param {string} value
58
+ * @param {string} csvOrPath
59
59
  * @returns {string[]}
60
60
  */
61
- csv: (value) => {
62
- let body;
61
+ csv: (csvOrPath) => {
62
+ let csv = csvOrPath;
63
+ let loadedFromFile = false;
63
64
  // since this value could be a single string (no commas) _or_ a pathname, we will need
64
65
  // to attempt to parse it as a file _first_.
65
- try {
66
- body = readFileSync(value, 'utf8');
67
- } catch (err) {
68
- if (err.code !== 'ENOENT') {
69
- throw new ArgumentTypeError(`Could not read file ${body}: ${err.message}`);
66
+ if (existsSync(csvOrPath)) {
67
+ try {
68
+ csv = readFileSync(csvOrPath, 'utf8');
69
+ } catch (err) {
70
+ throw new ArgumentTypeError(`Could not read file '${csvOrPath}': ${err.message}`);
70
71
  }
72
+ loadedFromFile = true;
71
73
  }
72
74
 
73
75
  try {
74
- return body ? parseCsvFile(body) : parseCsvLine(value);
76
+ return loadedFromFile ? parseCsvFile(csv) : parseCsvLine(csv);
75
77
  } catch (err) {
76
- throw new ArgumentTypeError('Must be a comma-delimited string, e.g., "foo,bar,baz"');
78
+ const msg = loadedFromFile
79
+ ? `The provided value of '${csvOrPath}' must be a valid CSV`
80
+ : `Must be a comma-delimited string, e.g., "foo,bar,baz"`;
81
+ throw new TypeError(`${msg}. Original error: ${err.message}`);
77
82
  }
78
83
  },
79
84
 
@@ -85,19 +90,18 @@ export const transformers = {
85
90
  json: (jsonOrPath) => {
86
91
  let json = jsonOrPath;
87
92
  let loadedFromFile = false;
88
- try {
89
- // use synchronous file access, as `argparse` provides no way of either
90
- // awaiting or using callbacks. This step happens in startup, in what is
91
- // effectively command-line code, so nothing is blocked in terms of
92
- // sessions, so holding up the event loop does not incur the usual
93
- // drawbacks.
94
- json = readFileSync(jsonOrPath, 'utf8');
95
- loadedFromFile = true;
96
- } catch (err) {
97
- // unreadable files don't count... but other problems do.
98
- if (err.code !== 'ENOENT') {
99
- throw err;
93
+ if (existsSync(jsonOrPath)) {
94
+ try {
95
+ // use synchronous file access, as `argparse` provides no way of either
96
+ // awaiting or using callbacks. This step happens in startup, in what is
97
+ // effectively command-line code, so nothing is blocked in terms of
98
+ // sessions, so holding up the event loop does not incur the usual
99
+ // drawbacks.
100
+ json = readFileSync(jsonOrPath, 'utf8');
101
+ } catch (err) {
102
+ throw new ArgumentTypeError(`Could not read file '${jsonOrPath}': ${err.message}`);
100
103
  }
104
+ loadedFromFile = true;
101
105
  }
102
106
  try {
103
107
  const result = JSON.parse(json);
package/lib/utils.js CHANGED
@@ -1,9 +1,12 @@
1
1
  import _ from 'lodash';
2
2
  import logger from './logger';
3
- import {processCapabilities, PROTOCOLS} from '@appium/base-driver';
3
+ import {processCapabilities, PROTOCOLS, STANDARD_CAPS} from '@appium/base-driver';
4
4
  import {inspect as dump} from 'util';
5
+ import {node} from '@appium/support';
6
+ import path from 'path';
5
7
 
6
8
  const W3C_APPIUM_PREFIX = 'appium';
9
+ const STANDARD_CAPS_LOWERCASE = new Set([...STANDARD_CAPS].map((cap) => cap.toLowerCase()));
7
10
 
8
11
  /**
9
12
  *
@@ -33,16 +36,18 @@ const inspect = _.flow(
33
36
  * Takes the caps that were provided in the request and translates them
34
37
  * into caps that can be used by the inner drivers.
35
38
  *
36
- * @param {any} jsonwpCapabilities
37
- * @param {W3CCapabilities} w3cCapabilities
38
- * @param {import('@appium/types').Constraints} constraints
39
- * @param {import('@appium/types').DefaultCapabilitiesConfig} [defaultCapabilities]
40
- * @returns {ParsedDriverCaps|InvalidCaps}
39
+ * @template {Constraints} C
40
+ * @template [J=any]
41
+ * @param {J} jsonwpCapabilities
42
+ * @param {W3CCapabilities<C>} w3cCapabilities
43
+ * @param {C} constraints
44
+ * @param {NSCapabilities<C>} [defaultCapabilities]
45
+ * @returns {ParsedDriverCaps<C,J>|InvalidCaps<C,J>}
41
46
  */
42
47
  function parseCapsForInnerDriver(
43
48
  jsonwpCapabilities,
44
49
  w3cCapabilities,
45
- constraints = {},
50
+ constraints = /** @type {C} */ ({}),
46
51
  defaultCapabilities = {}
47
52
  ) {
48
53
  // Check if the caller sent JSONWP caps, W3C caps, or both
@@ -50,14 +55,14 @@ function parseCapsForInnerDriver(
50
55
  _.isPlainObject(w3cCapabilities) &&
51
56
  (_.has(w3cCapabilities, 'alwaysMatch') || _.has(w3cCapabilities, 'firstMatch'));
52
57
  const hasJSONWPCaps = _.isPlainObject(jsonwpCapabilities);
53
- let desiredCaps = /** @type {ParsedDriverCaps['desiredCaps']} */ ({});
54
- /** @type {ParsedDriverCaps['processedW3CCapabilities']} */
58
+ let desiredCaps = /** @type {ParsedDriverCaps<C>['desiredCaps']} */ ({});
59
+ /** @type {ParsedDriverCaps<C>['processedW3CCapabilities']} */
55
60
  let processedW3CCapabilities;
56
- /** @type {ParsedDriverCaps['processedJsonwpCapabilities']} */
61
+ /** @type {ParsedDriverCaps<C>['processedJsonwpCapabilities']} */
57
62
  let processedJsonwpCapabilities;
58
63
 
59
64
  if (!hasW3CCaps) {
60
- return /** @type {InvalidCaps} */ ({
65
+ return /** @type {InvalidCaps<C>} */ ({
61
66
  protocol: PROTOCOLS.W3C,
62
67
  error: new Error('W3C capabilities should be provided'),
63
68
  });
@@ -76,7 +81,7 @@ function parseCapsForInnerDriver(
76
81
  for (const [defaultCapKey, defaultCapValue] of _.toPairs(defaultCapabilities)) {
77
82
  let isCapAlreadySet = false;
78
83
  // Check if the key is already present in firstMatch entries
79
- for (const firstMatchEntry of w3cCapabilities.firstMatch || []) {
84
+ for (const firstMatchEntry of w3cCapabilities.firstMatch ?? []) {
80
85
  if (
81
86
  _.isPlainObject(firstMatchEntry) &&
82
87
  _.has(removeAppiumPrefixes(firstMatchEntry), removeAppiumPrefix(defaultCapKey))
@@ -100,7 +105,9 @@ function parseCapsForInnerDriver(
100
105
 
101
106
  // Only add the default capability if it is not overridden
102
107
  if (_.isEmpty(w3cCapabilities.firstMatch)) {
103
- w3cCapabilities.firstMatch = [{[defaultCapKey]: defaultCapValue}];
108
+ w3cCapabilities.firstMatch = /** @type {W3CCapabilities<C>['firstMatch']} */ ([
109
+ {[defaultCapKey]: defaultCapValue},
110
+ ]);
104
111
  } else {
105
112
  w3cCapabilities.firstMatch[0][defaultCapKey] = defaultCapValue;
106
113
  }
@@ -127,7 +134,7 @@ function parseCapsForInnerDriver(
127
134
  desiredCaps = processCapabilities(w3cCapabilities, constraints, true);
128
135
  } catch (error) {
129
136
  logger.info(`Could not parse W3C capabilities: ${error.message}`);
130
- return /** @type {InvalidCaps} */ ({
137
+ return /** @type {InvalidCaps<C,J>} */ ({
131
138
  desiredCaps,
132
139
  processedJsonwpCapabilities,
133
140
  processedW3CCapabilities,
@@ -143,7 +150,7 @@ function parseCapsForInnerDriver(
143
150
  };
144
151
  }
145
152
 
146
- return /** @type {ParsedDriverCaps} */ ({
153
+ return /** @type {ParsedDriverCaps<C,J>} */ ({
147
154
  desiredCaps,
148
155
  processedJsonwpCapabilities,
149
156
  processedW3CCapabilities,
@@ -153,62 +160,101 @@ function parseCapsForInnerDriver(
153
160
 
154
161
  /**
155
162
  * Takes a capabilities objects and prefixes capabilities with `appium:`
156
- * @param {Capabilities} caps Desired capabilities object
157
- * @returns {AppiumW3CCapabilities}
163
+ * @template {Constraints} [C={}]
164
+ * @param {Capabilities<C>} caps - Desired capabilities object
165
+ * @returns {NSCapabilities<C>}
158
166
  */
159
167
  function insertAppiumPrefixes(caps) {
160
- // Standard, non-prefixed capabilities (see https://www.w3.org/TR/webdriver/#dfn-table-of-standard-capabilities)
161
- const STANDARD_CAPS = [
162
- 'browserName',
163
- 'browserVersion',
164
- 'platformName',
165
- 'acceptInsecureCerts',
166
- 'pageLoadStrategy',
167
- 'proxy',
168
- 'setWindowRect',
169
- 'timeouts',
170
- 'unhandledPromptBehavior',
171
- ];
172
-
173
- let prefixedCaps = {};
174
- for (let [name, value] of _.toPairs(caps)) {
175
- if (STANDARD_CAPS.includes(name) || name.includes(':')) {
176
- prefixedCaps[name] = value;
177
- } else {
178
- prefixedCaps[`${W3C_APPIUM_PREFIX}:${name}`] = value;
179
- }
180
- }
181
- return prefixedCaps;
168
+ return /** @type {NSCapabilities<C>} */ (
169
+ _.mapKeys(caps, (_, key) =>
170
+ STANDARD_CAPS_LOWERCASE.has(key.toLowerCase()) || key.includes(':')
171
+ ? key
172
+ : `${W3C_APPIUM_PREFIX}:${key}`
173
+ )
174
+ );
182
175
  }
183
176
 
184
177
  /**
185
- *
186
- * @param {AppiumW3CCapabilities} caps
187
- * @returns {Capabilities}
178
+ * @template {Constraints} [C={}]
179
+ * @param {NSCapabilities<C>} caps
180
+ * @returns {Capabilities<C>}
188
181
  */
189
182
  function removeAppiumPrefixes(caps) {
190
- if (!_.isPlainObject(caps)) {
191
- return caps;
192
- }
193
-
194
- /** @type {Capabilities} */
195
- const fixedCaps = {};
196
- for (let [name, value] of _.toPairs(caps)) {
197
- fixedCaps[removeAppiumPrefix(name)] = value;
198
- }
199
- return fixedCaps;
183
+ return /** @type {Capabilities<C>} */ (_.mapKeys(caps, (_, key) => removeAppiumPrefix(key)));
200
184
  }
201
185
 
186
+ /**
187
+ * @param {string} key
188
+ * @returns {string}
189
+ */
202
190
  function removeAppiumPrefix(key) {
203
191
  const prefix = `${W3C_APPIUM_PREFIX}:`;
204
192
  return _.startsWith(key, prefix) ? key.substring(prefix.length) : key;
205
193
  }
206
194
 
195
+ /**
196
+ *
197
+ * @param {string} pkgName
198
+ * @returns {string|undefined}
199
+ */
207
200
  function getPackageVersion(pkgName) {
208
201
  const pkgInfo = require(`${pkgName}/package.json`) || {};
209
202
  return pkgInfo.version;
210
203
  }
211
204
 
205
+ /**
206
+ * Adjusts NODE_PATH environment variable,
207
+ * so drivers and plugins could load their peer dependencies.
208
+ * Read https://nodejs.org/api/modules.html#loading-from-the-global-folders
209
+ * for more details.
210
+ * @returns {void}
211
+ */
212
+ function adjustNodePath() {
213
+ const selfRoot = node.getModuleRootSync('appium', __filename);
214
+ if (!selfRoot || path.dirname(selfRoot).length >= selfRoot.length) {
215
+ return;
216
+ }
217
+ const nodeModulesRoot = path.dirname(selfRoot);
218
+
219
+ const refreshRequirePaths = () => {
220
+ try {
221
+ // ! This hack allows us to avoid modification of import
222
+ // ! statements in client modules. It uses a private API though,
223
+ // ! so it could break (maybe, eventually).
224
+ // See https://gist.github.com/branneman/8048520#7-the-hack
225
+ // @ts-ignore
226
+ require('module').Module._initPaths();
227
+ return true;
228
+ } catch (e) {
229
+ return false;
230
+ }
231
+ };
232
+
233
+ if (!process.env.NODE_PATH) {
234
+ process.env.NODE_PATH = nodeModulesRoot;
235
+ if (refreshRequirePaths()) {
236
+ process.env.APPIUM_OMIT_PEER_DEPS = '1';
237
+ } else {
238
+ delete process.env.NODE_PATH;
239
+ }
240
+ return;
241
+ }
242
+
243
+ const nodePathParts = process.env.NODE_PATH.split(path.delimiter);
244
+ if (nodePathParts.includes(nodeModulesRoot)) {
245
+ process.env.APPIUM_OMIT_PEER_DEPS = '1';
246
+ return;
247
+ }
248
+
249
+ nodePathParts.push(nodeModulesRoot);
250
+ process.env.NODE_PATH = nodePathParts.join(path.delimiter);
251
+ if (refreshRequirePaths()) {
252
+ process.env.APPIUM_OMIT_PEER_DEPS = '1';
253
+ } else {
254
+ process.env.NODE_PATH = _.without(nodePathParts, nodeModulesRoot).join(path.delimiter);
255
+ }
256
+ }
257
+
212
258
  /**
213
259
  * Pulls the initial values of Appium settings from the given capabilities argument.
214
260
  * Each setting item must satisfy the following format:
@@ -250,29 +296,64 @@ export {
250
296
  getPackageVersion,
251
297
  pullSettings,
252
298
  removeAppiumPrefixes,
299
+ adjustNodePath,
253
300
  };
254
301
 
255
302
  /**
256
- * @todo protocol is more specific
303
+ * @typedef {import('@appium/types').StringRecord} StringRecord
304
+ * @typedef {import('@appium/types').BaseDriverCapConstraints} BaseDriverCapConstraints
305
+ */
306
+
307
+ /**
308
+ * @template {Constraints} [C=BaseDriverCapConstraints]
309
+ * @template [J=any]
257
310
  * @typedef ParsedDriverCaps
258
- * @property {Capabilities} desiredCaps
311
+ * @property {Capabilities<C>} desiredCaps
259
312
  * @property {string} protocol
260
- * @property {any} [processedJsonwpCapabilities]
261
- * @property {W3CCapabilities} [processedW3CCapabilities]
313
+ * @property {J} [processedJsonwpCapabilities]
314
+ * @property {W3CCapabilities<C>} [processedW3CCapabilities]
262
315
  */
263
316
 
264
317
  /**
265
318
  * @todo protocol is more specific
319
+ * @template {Constraints} [C=BaseDriverCapConstraints]
320
+ * @template [J=any]
266
321
  * @typedef InvalidCaps
267
322
  * @property {Error} error
268
323
  * @property {string} protocol
269
- * @property {Capabilities} [desiredCaps]
270
- * @property {any} [processedJsonwpCapabilities]
271
- * @property {W3CCapabilities} [processedW3CCapabilities]
324
+ * @property {Capabilities<C>} [desiredCaps]
325
+ * @property {J} [processedJsonwpCapabilities]
326
+ * @property {W3CCapabilities<C>} [processedW3CCapabilities]
327
+ */
328
+
329
+ /**
330
+ * @template {Constraints} [C=BaseDriverCapConstraints]
331
+ * @template {StringRecord|void} [Extra=void]
332
+ * @typedef {import('@appium/types').Capabilities<C, Extra>} Capabilities
333
+ */
334
+
335
+ /**
336
+ * @template {Constraints} [C=BaseDriverCapConstraints]
337
+ * @template {StringRecord|void} [Extra=void]
338
+ * @typedef {import('@appium/types').W3CCapabilities<C, Extra>} W3CCapabilities
339
+ */
340
+
341
+ /**
342
+ * @template {Constraints} [C=BaseDriverCapConstraints]
343
+ * @template {StringRecord|void} [Extra=void]
344
+ * @typedef {import('@appium/types').NSCapabilities<C, Extra>} NSCapabilities
345
+ */
346
+
347
+ /**
348
+ * @template {Constraints} C
349
+ * @typedef {import('@appium/types').ConstraintsToCaps<C>} ConstraintsToCaps
350
+ */
351
+
352
+ /**
353
+ * @template T
354
+ * @typedef {import('type-fest').StringKeyOf<T>} StringKeyOf
272
355
  */
273
356
 
274
357
  /**
275
- * @typedef {import('@appium/types').W3CCapabilities} W3CCapabilities
276
- * @typedef {import('@appium/types').Capabilities} Capabilities
277
- * @typedef {import('@appium/types').AppiumW3CCapabilities} AppiumW3CCapabilities
358
+ * @typedef {import('@appium/types').Constraints} Constraints
278
359
  */