appium 2.0.0-beta.4 → 2.0.0-beta.40

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 (151) hide show
  1. package/README.md +10 -11
  2. package/build/lib/appium.d.ts +204 -0
  3. package/build/lib/appium.d.ts.map +1 -0
  4. package/build/lib/appium.js +257 -131
  5. package/build/lib/cli/args.d.ts +20 -0
  6. package/build/lib/cli/args.d.ts.map +1 -0
  7. package/build/lib/cli/args.js +96 -282
  8. package/build/lib/cli/driver-command.d.ts +36 -0
  9. package/build/lib/cli/driver-command.d.ts.map +1 -0
  10. package/build/lib/cli/driver-command.js +25 -18
  11. package/build/lib/cli/extension-command.d.ts +372 -0
  12. package/build/lib/cli/extension-command.d.ts.map +1 -0
  13. package/build/lib/cli/extension-command.js +286 -156
  14. package/build/lib/cli/extension.d.ts +18 -0
  15. package/build/lib/cli/extension.d.ts.map +1 -0
  16. package/build/lib/cli/extension.js +30 -17
  17. package/build/lib/cli/parser.d.ts +80 -0
  18. package/build/lib/cli/parser.d.ts.map +1 -0
  19. package/build/lib/cli/parser.js +152 -95
  20. package/build/lib/cli/plugin-command.d.ts +33 -0
  21. package/build/lib/cli/plugin-command.d.ts.map +1 -0
  22. package/build/lib/cli/plugin-command.js +24 -19
  23. package/build/lib/cli/utils.d.ts +29 -0
  24. package/build/lib/cli/utils.d.ts.map +1 -0
  25. package/build/lib/cli/utils.js +27 -3
  26. package/build/lib/config-file.d.ts +100 -0
  27. package/build/lib/config-file.d.ts.map +1 -0
  28. package/build/lib/config-file.js +136 -0
  29. package/build/lib/config.d.ts +41 -0
  30. package/build/lib/config.d.ts.map +1 -0
  31. package/build/lib/config.js +92 -67
  32. package/build/lib/constants.d.ts +48 -0
  33. package/build/lib/constants.d.ts.map +1 -0
  34. package/build/lib/constants.js +60 -0
  35. package/build/lib/extension/driver-config.d.ts +81 -0
  36. package/build/lib/extension/driver-config.d.ts.map +1 -0
  37. package/build/lib/extension/driver-config.js +177 -0
  38. package/build/lib/extension/extension-config.d.ts +242 -0
  39. package/build/lib/extension/extension-config.d.ts.map +1 -0
  40. package/build/lib/extension/extension-config.js +436 -0
  41. package/build/lib/extension/index.d.ts +48 -0
  42. package/build/lib/extension/index.d.ts.map +1 -0
  43. package/build/lib/extension/index.js +74 -0
  44. package/build/lib/extension/manifest.d.ts +174 -0
  45. package/build/lib/extension/manifest.d.ts.map +1 -0
  46. package/build/lib/extension/manifest.js +256 -0
  47. package/build/lib/extension/package-changed.d.ts +11 -0
  48. package/build/lib/extension/package-changed.d.ts.map +1 -0
  49. package/build/lib/extension/package-changed.js +68 -0
  50. package/build/lib/extension/plugin-config.d.ts +57 -0
  51. package/build/lib/extension/plugin-config.d.ts.map +1 -0
  52. package/build/lib/extension/plugin-config.js +78 -0
  53. package/build/lib/grid-register.d.ts +10 -0
  54. package/build/lib/grid-register.d.ts.map +1 -0
  55. package/build/lib/grid-register.js +21 -25
  56. package/build/lib/logger.d.ts +3 -0
  57. package/build/lib/logger.d.ts.map +1 -0
  58. package/build/lib/logger.js +4 -6
  59. package/build/lib/logsink.d.ts +4 -0
  60. package/build/lib/logsink.d.ts.map +1 -0
  61. package/build/lib/logsink.js +14 -17
  62. package/build/lib/main.d.ts +55 -0
  63. package/build/lib/main.d.ts.map +1 -0
  64. package/build/lib/main.js +189 -90
  65. package/build/lib/schema/arg-spec.d.ts +143 -0
  66. package/build/lib/schema/arg-spec.d.ts.map +1 -0
  67. package/build/lib/schema/arg-spec.js +119 -0
  68. package/build/lib/schema/cli-args.d.ts +19 -0
  69. package/build/lib/schema/cli-args.d.ts.map +1 -0
  70. package/build/lib/schema/cli-args.js +180 -0
  71. package/build/lib/schema/cli-transformers.d.ts +5 -0
  72. package/build/lib/schema/cli-transformers.d.ts.map +1 -0
  73. package/build/lib/schema/cli-transformers.js +74 -0
  74. package/build/lib/schema/index.d.ts +3 -0
  75. package/build/lib/schema/index.d.ts.map +1 -0
  76. package/build/lib/schema/index.js +34 -0
  77. package/build/lib/schema/keywords.d.ts +24 -0
  78. package/build/lib/schema/keywords.d.ts.map +1 -0
  79. package/build/lib/schema/keywords.js +70 -0
  80. package/build/lib/schema/schema.d.ts +259 -0
  81. package/build/lib/schema/schema.d.ts.map +1 -0
  82. package/build/lib/schema/schema.js +452 -0
  83. package/build/lib/utils.d.ts +66 -0
  84. package/build/lib/utils.d.ts.map +1 -0
  85. package/build/lib/utils.js +35 -139
  86. package/build/tsconfig.tsbuildinfo +1 -0
  87. package/build/types/appium-manifest.d.ts +59 -0
  88. package/build/types/appium-manifest.d.ts.map +1 -0
  89. package/build/types/cli.d.ts +112 -0
  90. package/build/types/cli.d.ts.map +1 -0
  91. package/build/types/extension-manifest.d.ts +55 -0
  92. package/build/types/extension-manifest.d.ts.map +1 -0
  93. package/build/types/index.d.ts +16 -0
  94. package/build/types/index.d.ts.map +1 -0
  95. package/driver.d.ts +1 -0
  96. package/driver.js +14 -0
  97. package/index.js +11 -0
  98. package/lib/appium.js +517 -186
  99. package/lib/cli/args.js +269 -422
  100. package/lib/cli/driver-command.js +58 -23
  101. package/lib/cli/extension-command.js +612 -260
  102. package/lib/cli/extension.js +34 -16
  103. package/lib/cli/parser.js +241 -83
  104. package/lib/cli/plugin-command.js +48 -20
  105. package/lib/cli/utils.js +24 -10
  106. package/lib/config-file.js +219 -0
  107. package/lib/config.js +210 -91
  108. package/lib/constants.js +69 -0
  109. package/lib/extension/driver-config.js +249 -0
  110. package/lib/extension/extension-config.js +679 -0
  111. package/lib/extension/index.js +116 -0
  112. package/lib/extension/manifest.js +475 -0
  113. package/lib/extension/package-changed.js +64 -0
  114. package/lib/extension/plugin-config.js +113 -0
  115. package/lib/grid-register.js +49 -35
  116. package/lib/logger.js +1 -2
  117. package/lib/logsink.js +38 -33
  118. package/lib/main.js +303 -100
  119. package/lib/schema/arg-spec.js +229 -0
  120. package/lib/schema/cli-args.js +238 -0
  121. package/lib/schema/cli-transformers.js +115 -0
  122. package/lib/schema/index.js +2 -0
  123. package/lib/schema/keywords.js +136 -0
  124. package/lib/schema/schema.js +717 -0
  125. package/lib/utils.js +121 -140
  126. package/package.json +75 -85
  127. package/plugin.d.ts +1 -0
  128. package/plugin.js +13 -0
  129. package/scripts/autoinstall-extensions.js +177 -0
  130. package/support.d.ts +1 -0
  131. package/support.js +13 -0
  132. package/types/appium-manifest.ts +73 -0
  133. package/types/cli.ts +146 -0
  134. package/types/extension-manifest.ts +64 -0
  135. package/types/index.ts +21 -0
  136. package/CHANGELOG.md +0 -3515
  137. package/bin/ios-webkit-debug-proxy-launcher.js +0 -71
  138. package/build/lib/cli/npm.js +0 -206
  139. package/build/lib/cli/parser-helpers.js +0 -82
  140. package/build/lib/driver-config.js +0 -77
  141. package/build/lib/drivers.js +0 -96
  142. package/build/lib/extension-config.js +0 -253
  143. package/build/lib/plugin-config.js +0 -59
  144. package/build/lib/plugins.js +0 -14
  145. package/lib/cli/npm.js +0 -183
  146. package/lib/cli/parser-helpers.js +0 -79
  147. package/lib/driver-config.js +0 -46
  148. package/lib/drivers.js +0 -81
  149. package/lib/extension-config.js +0 -209
  150. package/lib/plugin-config.js +0 -34
  151. package/lib/plugins.js +0 -10
package/lib/appium.js CHANGED
@@ -1,14 +1,26 @@
1
+ /* eslint-disable no-unused-vars */
1
2
  import _ from 'lodash';
2
- import log from './logger';
3
- import { getBuildInfo, updateBuildInfo, APPIUM_VER } from './config';
4
- import { findMatchingDriver } from './drivers';
5
- import { BaseDriver, errors, isSessionCommand } from 'appium-base-driver';
6
- import B from 'bluebird';
3
+ import {getBuildInfo, updateBuildInfo, APPIUM_VER} from './config';
4
+ import {
5
+ BaseDriver,
6
+ DriverCore,
7
+ errors,
8
+ isSessionCommand,
9
+ CREATE_SESSION_COMMAND,
10
+ DELETE_SESSION_COMMAND,
11
+ GET_STATUS_COMMAND,
12
+ } from '@appium/base-driver';
7
13
  import AsyncLock from 'async-lock';
8
- import { parseCapsForInnerDriver, pullSettings } from './utils';
9
- import { util } from 'appium-support';
10
-
11
- const desiredCapabilityConstraints = {
14
+ import {parseCapsForInnerDriver, pullSettings} from './utils';
15
+ import {util, node, logger} from '@appium/support';
16
+ import {getDefaultsForExtension} from './schema';
17
+ import {DRIVER_TYPE, PLUGIN_TYPE} from './constants';
18
+
19
+ /**
20
+ * Invariant set of base constraints
21
+ * @type {Readonly<Constraints>}
22
+ */
23
+ const desiredCapabilityConstraints = Object.freeze({
12
24
  automationName: {
13
25
  presence: true,
14
26
  isString: true,
@@ -17,109 +29,208 @@ const desiredCapabilityConstraints = {
17
29
  presence: true,
18
30
  isString: true,
19
31
  },
20
- };
32
+ });
21
33
 
22
34
  const sessionsListGuard = new AsyncLock();
23
35
  const pendingDriversGuard = new AsyncLock();
24
36
 
25
- class AppiumDriver extends BaseDriver {
26
- constructor (args) {
27
- // It is necessary to set `--tmp` here since it should be set to
28
- // process.env.APPIUM_TMP_DIR once at an initial point in the Appium lifecycle.
29
- // The process argument will be referenced by BaseDriver.
30
- // Please call appium-support.tempDir module to apply this benefit.
31
- if (args.tmpDir) {
32
- process.env.APPIUM_TMP_DIR = args.tmpDir;
33
- }
37
+ /**
38
+ * @implements {SessionHandler}
39
+ */
40
+ class AppiumDriver extends DriverCore {
41
+ /**
42
+ * Access to sessions list must be guarded with a Semaphore, because
43
+ * it might be changed by other async calls at any time
44
+ * It is not recommended to access this property directly from the outside
45
+ * @type {Record<string,ExternalDriver>}
46
+ */
47
+ sessions = {};
34
48
 
35
- super(args);
49
+ /**
50
+ * Access to pending drivers list must be guarded with a Semaphore, because
51
+ * it might be changed by other async calls at any time
52
+ * It is not recommended to access this property directly from the outside
53
+ * @type {Record<string,ExternalDriver[]>}
54
+ */
55
+ pendingDrivers = {};
36
56
 
37
- this.desiredCapConstraints = desiredCapabilityConstraints;
57
+ /**
58
+ * Note that {@linkcode AppiumDriver} has no `newCommandTimeout` method.
59
+ * `AppiumDriver` does not set and observe its own timeouts; individual
60
+ * sessions (managed drivers) do instead.
61
+ */
62
+ newCommandTimeoutMs = 0;
38
63
 
39
- // the main Appium Driver has no new command timeout
40
- this.newCommandTimeoutMs = 0;
64
+ /**
65
+ * List of active plugins
66
+ * @type {Map<PluginClass,string>}
67
+ */
68
+ pluginClasses;
41
69
 
42
- this.args = Object.assign({}, args);
70
+ /**
71
+ * map of sessions to actual plugin instances per session
72
+ * @type {Record<string,InstanceType<PluginClass>[]>}
73
+ */
74
+ sessionPlugins = {};
43
75
 
44
- // Access to sessions list must be guarded with a Semaphore, because
45
- // it might be changed by other async calls at any time
46
- // It is not recommended to access this property directly from the outside
47
- this.sessions = {};
76
+ /**
77
+ * some commands are sessionless, so we need a set of plugins for them
78
+ * @type {InstanceType<PluginClass>[]}
79
+ */
80
+ sessionlessPlugins = [];
81
+
82
+ /** @type {DriverConfig} */
83
+ driverConfig;
84
+
85
+ /** @type {AppiumServer} */
86
+ server;
87
+
88
+ desiredCapConstraints = desiredCapabilityConstraints;
89
+
90
+ /** @type {DriverOpts} */
91
+ args;
92
+
93
+ /**
94
+ * @param {DriverOpts} opts
95
+ */
96
+ constructor(opts) {
97
+ // It is necessary to set `--tmp` here since it should be set to
98
+ // process.env.APPIUM_TMP_DIR once at an initial point in the Appium lifecycle.
99
+ // The process argument will be referenced by BaseDriver.
100
+ // Please call @appium/support.tempDir module to apply this benefit.
101
+ if (opts.tmpDir) {
102
+ process.env.APPIUM_TMP_DIR = opts.tmpDir;
103
+ }
48
104
 
49
- // Access to pending drivers list must be guarded with a Semaphore, because
50
- // it might be changed by other async calls at any time
51
- // It is not recommended to access this property directly from the outside
52
- this.pendingDrivers = {};
105
+ super(opts);
53
106
 
54
- this.plugins = [];
107
+ this.args = {...opts};
55
108
 
56
109
  // allow this to happen in the background, so no `await`
57
- updateBuildInfo();
110
+ // catch this to avoid an unhandled rejection
111
+ // eslint-disable-next-line promise/prefer-await-to-then,promise/prefer-await-to-callbacks
112
+ updateBuildInfo().catch((err) => {
113
+ this.log.debug(err);
114
+ });
115
+ }
116
+
117
+ /**
118
+ * Retrieves logger instance for the current umbrella driver instance
119
+ */
120
+ get log() {
121
+ if (!this._log) {
122
+ const instanceName = `${this.constructor.name}@${node.getObjectId(this).substring(0, 4)}`;
123
+ this._log = logger.getLogger(instanceName);
124
+ }
125
+ return this._log;
58
126
  }
59
127
 
60
128
  /**
61
129
  * Cancel commands queueing for the umbrella Appium driver
62
130
  */
63
- get isCommandsQueueEnabled () {
131
+ get isCommandsQueueEnabled() {
64
132
  return false;
65
133
  }
66
134
 
67
- sessionExists (sessionId) {
135
+ sessionExists(sessionId) {
68
136
  const dstSession = this.sessions[sessionId];
69
137
  return dstSession && dstSession.sessionId !== null;
70
138
  }
71
139
 
72
- driverForSession (sessionId) {
140
+ driverForSession(sessionId) {
73
141
  return this.sessions[sessionId];
74
142
  }
75
143
 
76
- async getStatus () { // eslint-disable-line require-await
144
+ // eslint-disable-next-line require-await
145
+ async getStatus() {
77
146
  return {
78
147
  build: _.clone(getBuildInfo()),
79
148
  };
80
149
  }
81
150
 
82
- async getSessions () {
83
- const sessions = await sessionsListGuard.acquire(AppiumDriver.name, () => this.sessions);
84
- return _.toPairs(sessions)
85
- .map(([id, driver]) => ({id, capabilities: driver.caps}));
151
+ // eslint-disable-next-line require-await
152
+ async getSessions() {
153
+ return _.toPairs(this.sessions).map(([id, driver]) => ({
154
+ id,
155
+ capabilities: driver.caps,
156
+ }));
157
+ }
158
+
159
+ printNewSessionAnnouncement(driverName, driverVersion, driverBaseVersion) {
160
+ this.log.info(
161
+ driverVersion
162
+ ? `Appium v${APPIUM_VER} creating new ${driverName} (v${driverVersion}) session`
163
+ : `Appium v${APPIUM_VER} creating new ${driverName} session`
164
+ );
165
+ this.log.info(`Checking BaseDriver versions for Appium and ${driverName}`);
166
+ this.log.info(
167
+ AppiumDriver.baseVersion
168
+ ? `Appium's BaseDriver version is ${AppiumDriver.baseVersion}`
169
+ : `Could not determine Appium's BaseDriver version`
170
+ );
171
+ this.log.info(
172
+ driverBaseVersion
173
+ ? `${driverName}'s BaseDriver version is ${driverBaseVersion}`
174
+ : `Could not determine ${driverName}'s BaseDriver version`
175
+ );
86
176
  }
87
177
 
88
- printNewSessionAnnouncement (driverName, driverVersion) {
89
- const introString = driverVersion
90
- ? `Appium v${APPIUM_VER} creating new ${driverName} (v${driverVersion}) session`
91
- : `Appium v${APPIUM_VER} creating new ${driverName} session`;
92
- log.info(introString);
178
+ /**
179
+ * Retrieves all CLI arguments for a specific plugin.
180
+ * @param {string} extName - Plugin name
181
+ * @returns {Record<string,unknown>} Arguments object. If none, an empty object.
182
+ */
183
+ getCliArgsForPlugin(extName) {
184
+ return /** @type {Record<string,unknown>} */ (this.args.plugin?.[extName] ?? {});
93
185
  }
94
186
 
95
187
  /**
96
- * This is just an alias for driver.js's method, which is necessary for
97
- * mocking in the test suite
188
+ * Retrieves CLI args for a specific driver.
189
+ *
190
+ * _Any arg which is equal to its default value will not be present in the returned object._
191
+ *
192
+ * _Note that this behavior currently (May 18 2022) differs from how plugins are handled_ (see {@linkcode AppiumDriver.getCliArgsForPlugin}).
193
+ * @param {string} extName - Driver name
194
+ * @returns {Record<string,unknown>|undefined} Arguments object. If none, `undefined`
98
195
  */
99
- _findMatchingDriver (...args) {
100
- return findMatchingDriver(...args);
196
+ getCliArgsForDriver(extName) {
197
+ const allCliArgsForExt = /** @type {Record<string,unknown>|undefined} */ (
198
+ this.args.driver?.[extName]
199
+ );
200
+
201
+ if (!_.isEmpty(allCliArgsForExt)) {
202
+ const defaults = getDefaultsForExtension(DRIVER_TYPE, extName);
203
+ const cliArgs = _.isEmpty(defaults)
204
+ ? allCliArgsForExt
205
+ : _.omitBy(allCliArgsForExt, (value, key) => _.isEqual(defaults[key], value));
206
+ if (!_.isEmpty(cliArgs)) {
207
+ return cliArgs;
208
+ }
209
+ }
101
210
  }
102
211
 
103
212
  /**
104
213
  * Create a new session
105
- * @param {Object} jsonwpCaps JSONWP formatted desired capabilities
106
- * @param {Object} reqCaps Required capabilities (JSONWP standard)
107
- * @param {Object} w3cCapabilities W3C capabilities
108
- * @return {Array} Unique session ID and capabilities
214
+ * @param {W3CCapabilities} jsonwpCaps JSONWP formatted desired capabilities
215
+ * @param {W3CCapabilities} reqCaps Required capabilities (JSONWP standard)
216
+ * @param {W3CCapabilities} w3cCapabilities W3C capabilities
217
+ * @param {DriverData[]} [driverData]
109
218
  */
110
- async createSession (jsonwpCaps, reqCaps, w3cCapabilities) {
219
+ async createSession(jsonwpCaps, reqCaps, w3cCapabilities, driverData) {
111
220
  const defaultCapabilities = _.cloneDeep(this.args.defaultCapabilities);
112
221
  const defaultSettings = pullSettings(defaultCapabilities);
113
222
  jsonwpCaps = _.cloneDeep(jsonwpCaps);
114
- const jwpSettings = Object.assign({}, defaultSettings, pullSettings(jsonwpCaps));
223
+ const jwpSettings = {...defaultSettings, ...pullSettings(jsonwpCaps)};
115
224
  w3cCapabilities = _.cloneDeep(w3cCapabilities);
116
225
  // It is possible that the client only provides caps using JSONWP standard,
117
226
  // although firstMatch/alwaysMatch properties are still present.
118
227
  // In such case we assume the client understands W3C protocol and merge the given
119
228
  // JSONWP caps to W3C caps
120
- const w3cSettings = Object.assign({}, jwpSettings);
121
- Object.assign(w3cSettings, pullSettings((w3cCapabilities || {}).alwaysMatch || {}));
122
- for (const firstMatchEntry of ((w3cCapabilities || {}).firstMatch || [])) {
229
+ const w3cSettings = {
230
+ ...jwpSettings,
231
+ ...pullSettings((w3cCapabilities ?? {}).alwaysMatch ?? {}),
232
+ };
233
+ for (const firstMatchEntry of (w3cCapabilities ?? {}).firstMatch ?? []) {
123
234
  Object.assign(w3cSettings, pullSettings(firstMatchEntry));
124
235
  }
125
236
 
@@ -134,9 +245,10 @@ class AppiumDriver extends BaseDriver {
134
245
  defaultCapabilities
135
246
  );
136
247
 
137
- const {desiredCaps, processedJsonwpCapabilities, processedW3CCapabilities, error} = parsedCaps;
248
+ const {desiredCaps, processedJsonwpCapabilities, processedW3CCapabilities} =
249
+ /** @type {import('./utils').ParsedDriverCaps} */ (parsedCaps);
138
250
  protocol = parsedCaps.protocol;
139
-
251
+ const error = /** @type {import('./utils').InvalidCaps} */ (parsedCaps).error;
140
252
  // If the parsing of the caps produced an error, throw it in here
141
253
  if (error) {
142
254
  throw error;
@@ -144,87 +256,118 @@ class AppiumDriver extends BaseDriver {
144
256
 
145
257
  const {
146
258
  driver: InnerDriver,
147
- version: driverVersion
148
- } = this._findMatchingDriver(this.driverConfig, desiredCaps);
149
- this.printNewSessionAnnouncement(InnerDriver.name, driverVersion);
259
+ version: driverVersion,
260
+ driverName,
261
+ } = this.driverConfig.findMatchingDriver(desiredCaps);
262
+ this.printNewSessionAnnouncement(InnerDriver.name, driverVersion, InnerDriver.baseVersion);
150
263
 
151
264
  if (this.args.sessionOverride) {
152
265
  await this.deleteAllSessions();
153
266
  }
154
267
 
155
- let runningDriversData, otherPendingDriversData;
156
- const d = new InnerDriver(this.args);
268
+ /**
269
+ * @type {DriverData[]}
270
+ */
271
+ let runningDriversData = [];
272
+ /**
273
+ * @type {DriverData[]}
274
+ */
275
+ let otherPendingDriversData = [];
276
+
277
+ const driverInstance = new InnerDriver(this.args, true);
157
278
 
158
279
  // We want to assign security values directly on the driver. The driver
159
280
  // should not read security values from `this.opts` because those values
160
281
  // could have been set by a malicious user via capabilities, whereas we
161
282
  // want a guarantee the values were set by the appium server admin
162
283
  if (this.args.relaxedSecurityEnabled) {
163
- log.info(`Applying relaxed security to '${InnerDriver.name}' as per ` +
164
- `server command line argument. All insecure features will be ` +
165
- `enabled unless explicitly disabled by --deny-insecure`);
166
- d.relaxedSecurityEnabled = true;
284
+ this.log.info(
285
+ `Applying relaxed security to '${InnerDriver.name}' as per ` +
286
+ `server command line argument. All insecure features will be ` +
287
+ `enabled unless explicitly disabled by --deny-insecure`
288
+ );
289
+ driverInstance.relaxedSecurityEnabled = true;
167
290
  }
168
291
 
169
292
  if (!_.isEmpty(this.args.denyInsecure)) {
170
- log.info('Explicitly preventing use of insecure features:');
171
- this.args.denyInsecure.map((a) => log.info(` ${a}`));
172
- d.denyInsecure = this.args.denyInsecure;
293
+ this.log.info('Explicitly preventing use of insecure features:');
294
+ this.args.denyInsecure.map((a) => this.log.info(` ${a}`));
295
+ driverInstance.denyInsecure = this.args.denyInsecure;
173
296
  }
174
297
 
175
298
  if (!_.isEmpty(this.args.allowInsecure)) {
176
- log.info('Explicitly enabling use of insecure features:');
177
- this.args.allowInsecure.map((a) => log.info(` ${a}`));
178
- d.allowInsecure = this.args.allowInsecure;
299
+ this.log.info('Explicitly enabling use of insecure features:');
300
+ this.args.allowInsecure.map((a) => this.log.info(` ${a}`));
301
+ driverInstance.allowInsecure = this.args.allowInsecure;
302
+ }
303
+
304
+ // Likewise, any driver-specific CLI args that were passed in should be assigned directly to
305
+ // the driver so that they cannot be mimicked by a malicious user sending in capabilities
306
+ const cliArgs = this.getCliArgsForDriver(driverName);
307
+ if (!_.isEmpty(cliArgs)) {
308
+ driverInstance.cliArgs = cliArgs;
179
309
  }
180
310
 
181
311
  // This assignment is required for correct web sockets functionality inside the driver
182
- d.server = this.server;
312
+ // Drivers/plugins might also want to know where they are hosted
313
+
314
+ // XXX: temporary hack to work around #16747
315
+ driverInstance.server = this.server;
316
+ driverInstance.serverHost = this.args.address;
317
+ driverInstance.serverPort = this.args.port;
318
+ driverInstance.serverPath = this.args.basePath;
319
+
183
320
  try {
184
- runningDriversData = await this.curSessionDataForDriver(InnerDriver);
321
+ runningDriversData = (await this.curSessionDataForDriver(InnerDriver)) ?? [];
185
322
  } catch (e) {
186
323
  throw new errors.SessionNotCreatedError(e.message);
187
324
  }
188
325
  await pendingDriversGuard.acquire(AppiumDriver.name, () => {
189
326
  this.pendingDrivers[InnerDriver.name] = this.pendingDrivers[InnerDriver.name] || [];
190
- otherPendingDriversData = this.pendingDrivers[InnerDriver.name].map((drv) => drv.driverData);
191
- this.pendingDrivers[InnerDriver.name].push(d);
327
+ otherPendingDriversData = _.compact(
328
+ this.pendingDrivers[InnerDriver.name].map((drv) => drv.driverData)
329
+ );
330
+ this.pendingDrivers[InnerDriver.name].push(driverInstance);
192
331
  });
193
332
 
194
333
  try {
195
- [innerSessionId, dCaps] = await d.createSession(
334
+ [innerSessionId, dCaps] = await driverInstance.createSession(
196
335
  processedJsonwpCapabilities,
197
336
  reqCaps,
198
337
  processedW3CCapabilities,
199
338
  [...runningDriversData, ...otherPendingDriversData]
200
339
  );
201
- protocol = d.protocol;
202
- await sessionsListGuard.acquire(AppiumDriver.name, () => {
203
- this.sessions[innerSessionId] = d;
204
- });
340
+ protocol = driverInstance.protocol;
341
+ this.sessions[innerSessionId] = driverInstance;
205
342
  } finally {
206
343
  await pendingDriversGuard.acquire(AppiumDriver.name, () => {
207
- _.pull(this.pendingDrivers[InnerDriver.name], d);
344
+ _.pull(this.pendingDrivers[InnerDriver.name], driverInstance);
208
345
  });
209
346
  }
210
347
 
211
- this.attachUnexpectedShutdownHandler(d, innerSessionId);
348
+ this.attachUnexpectedShutdownHandler(driverInstance, innerSessionId);
212
349
 
213
- log.info(`New ${InnerDriver.name} session created successfully, session ` +
214
- `${innerSessionId} added to master session list`);
350
+ this.log.info(
351
+ `New ${InnerDriver.name} session created successfully, session ` +
352
+ `${innerSessionId} added to master session list`
353
+ );
215
354
 
216
355
  // set the New Command Timeout for the inner driver
217
- d.startNewCommandTimeout();
356
+ driverInstance.startNewCommandTimeout();
218
357
 
219
358
  // apply initial values to Appium settings (if provided)
220
- if (d.isW3CProtocol() && !_.isEmpty(w3cSettings)) {
221
- log.info(`Applying the initial values to Appium settings parsed from W3C caps: ` +
222
- JSON.stringify(w3cSettings));
223
- await d.updateSettings(w3cSettings);
224
- } else if (d.isMjsonwpProtocol() && !_.isEmpty(jwpSettings)) {
225
- log.info(`Applying the initial values to Appium settings parsed from MJSONWP caps: ` +
226
- JSON.stringify(jwpSettings));
227
- await d.updateSettings(jwpSettings);
359
+ if (driverInstance.isW3CProtocol() && !_.isEmpty(w3cSettings)) {
360
+ this.log.info(
361
+ `Applying the initial values to Appium settings parsed from W3C caps: ` +
362
+ JSON.stringify(w3cSettings)
363
+ );
364
+ await driverInstance.updateSettings(w3cSettings);
365
+ } else if (driverInstance.isMjsonwpProtocol() && !_.isEmpty(jwpSettings)) {
366
+ this.log.info(
367
+ `Applying the initial values to Appium settings parsed from MJSONWP caps: ` +
368
+ JSON.stringify(jwpSettings)
369
+ );
370
+ await driverInstance.updateSettings(jwpSettings);
228
371
  }
229
372
  } catch (error) {
230
373
  return {
@@ -235,86 +378,114 @@ class AppiumDriver extends BaseDriver {
235
378
 
236
379
  return {
237
380
  protocol,
238
- value: [innerSessionId, dCaps, protocol]
381
+ value: [innerSessionId, dCaps, protocol],
239
382
  };
240
383
  }
241
384
 
242
- attachUnexpectedShutdownHandler (driver, innerSessionId) {
243
- const removeSessionFromMasterList = (cause = new Error('Unknown error')) => {
244
- log.warn(`Closing session, cause was '${cause.message}'`);
245
- log.info(`Removing session '${innerSessionId}' from our master session list`);
385
+ /**
386
+ *
387
+ * @param {Driver} driver
388
+ * @param {string} innerSessionId
389
+ */
390
+ attachUnexpectedShutdownHandler(driver, innerSessionId) {
391
+ const onShutdown = (cause = new Error('Unknown error')) => {
392
+ this.log.warn(`Ending session, cause was '${cause.message}'`);
393
+
394
+ if (this.sessionPlugins[innerSessionId]) {
395
+ for (const plugin of this.sessionPlugins[innerSessionId]) {
396
+ if (_.isFunction(plugin.onUnexpectedShutdown)) {
397
+ this.log.debug(
398
+ `Plugin ${plugin.name} defines an unexpected shutdown handler; calling it now`
399
+ );
400
+ try {
401
+ plugin.onUnexpectedShutdown(driver, cause);
402
+ } catch (e) {
403
+ this.log.warn(
404
+ `Got an error when running plugin ${plugin.name} shutdown handler: ${e}`
405
+ );
406
+ }
407
+ } else {
408
+ this.log.debug(`Plugin ${plugin.name} does not define an unexpected shutdown handler`);
409
+ }
410
+ }
411
+ }
412
+
413
+ this.log.info(`Removing session '${innerSessionId}' from our master session list`);
246
414
  delete this.sessions[innerSessionId];
415
+ delete this.sessionPlugins[innerSessionId];
247
416
  };
248
417
 
249
- // eslint-disable-next-line promise/prefer-await-to-then
250
- if (_.isFunction((driver.onUnexpectedShutdown || {}).then)) {
251
- // TODO: Remove this block after all the drivers use base driver above v 5.0.0
252
- // Remove the session on unexpected shutdown, so that we are in a position
253
- // to open another session later on.
254
- driver.onUnexpectedShutdown
255
- // eslint-disable-next-line promise/prefer-await-to-then
256
- .then(() => {
257
- // if we get here, we've had an unexpected shutdown, so error
258
- throw new Error('Unexpected shutdown');
259
- })
260
- .catch((e) => {
261
- // if we cancelled the unexpected shutdown promise, that means we
262
- // no longer care about it, and can safely ignore it
263
- if (!(e instanceof B.CancellationError)) {
264
- removeSessionFromMasterList(e);
265
- }
266
- }); // this is a cancellable promise
267
- } else if (_.isFunction(driver.onUnexpectedShutdown)) {
268
- // since base driver v 5.0.0
269
- driver.onUnexpectedShutdown(removeSessionFromMasterList);
418
+ if (_.isFunction(driver.onUnexpectedShutdown)) {
419
+ driver.onUnexpectedShutdown(onShutdown);
270
420
  } else {
271
- log.warn(`Failed to attach the unexpected shutdown listener. ` +
272
- `Is 'onUnexpectedShutdown' method available for '${driver.constructor.name}'?`);
421
+ this.log.warn(
422
+ `Failed to attach the unexpected shutdown listener. ` +
423
+ `Is 'onUnexpectedShutdown' method available for '${driver.constructor.name}'?`
424
+ );
273
425
  }
274
426
  }
275
427
 
276
- async curSessionDataForDriver (InnerDriver) {
277
- const sessions = await sessionsListGuard.acquire(AppiumDriver.name, () => this.sessions);
278
- const data = _.values(sessions)
279
- .filter((s) => s.constructor.name === InnerDriver.name)
280
- .map((s) => s.driverData);
281
- for (let datum of data) {
428
+ /**
429
+ *
430
+ * @param {DriverClass} InnerDriver
431
+ * @returns {Promise<DriverData[]>}}
432
+ */
433
+ // eslint-disable-next-line require-await
434
+ async curSessionDataForDriver(InnerDriver) {
435
+ const data = _.compact(
436
+ _.values(this.sessions)
437
+ .filter((s) => s.constructor.name === InnerDriver.name)
438
+ .map((s) => s.driverData)
439
+ );
440
+ for (const datum of data) {
282
441
  if (!datum) {
283
- throw new Error(`Problem getting session data for driver type ` +
284
- `${InnerDriver.name}; does it implement 'get ` +
285
- `driverData'?`);
442
+ throw new Error(
443
+ `Problem getting session data for driver type ` +
444
+ `${InnerDriver.name}; does it implement 'get driverData'?`
445
+ );
286
446
  }
287
447
  }
288
448
  return data;
289
449
  }
290
450
 
291
- async deleteSession (sessionId) {
451
+ /**
452
+ * @param {string} sessionId
453
+ */
454
+ async deleteSession(sessionId) {
292
455
  let protocol;
293
456
  try {
294
- let otherSessionsData = null;
295
- let dstSession = null;
296
- await sessionsListGuard.acquire(AppiumDriver.name, () => {
457
+ let otherSessionsData;
458
+ const dstSession = await sessionsListGuard.acquire(AppiumDriver.name, () => {
297
459
  if (!this.sessions[sessionId]) {
298
460
  return;
299
461
  }
300
462
  const curConstructorName = this.sessions[sessionId].constructor.name;
301
463
  otherSessionsData = _.toPairs(this.sessions)
302
- .filter(([key, value]) => value.constructor.name === curConstructorName && key !== sessionId)
303
- .map(([, value]) => value.driverData);
304
- dstSession = this.sessions[sessionId];
464
+ .filter(
465
+ ([key, value]) => value.constructor.name === curConstructorName && key !== sessionId
466
+ )
467
+ .map(([, value]) => value.driverData);
468
+ const dstSession = this.sessions[sessionId];
305
469
  protocol = dstSession.protocol;
306
- log.info(`Removing session ${sessionId} from our master session list`);
470
+ this.log.info(`Removing session ${sessionId} from our master session list`);
307
471
  // regardless of whether the deleteSession completes successfully or not
308
472
  // make the session unavailable, because who knows what state it might
309
473
  // be in otherwise
310
474
  delete this.sessions[sessionId];
475
+ delete this.sessionPlugins[sessionId];
476
+ return dstSession;
311
477
  });
478
+ // this may not be correct, but if `dstSession` was falsy, the call to `deleteSession()` would
479
+ // throw anyway.
480
+ if (!dstSession) {
481
+ throw new Error('Session not found');
482
+ }
312
483
  return {
313
484
  protocol,
314
485
  value: await dstSession.deleteSession(sessionId, otherSessionsData),
315
486
  };
316
487
  } catch (e) {
317
- log.error(`Had trouble ending session ${sessionId}: ${e.message}`);
488
+ this.log.error(`Had trouble ending session ${sessionId}: ${e.message}`);
318
489
  return {
319
490
  protocol,
320
491
  error: e,
@@ -322,38 +493,89 @@ class AppiumDriver extends BaseDriver {
322
493
  }
323
494
  }
324
495
 
325
- async deleteAllSessions (opts = {}) {
496
+ async deleteAllSessions(opts = {}) {
326
497
  const sessionsCount = _.size(this.sessions);
327
498
  if (0 === sessionsCount) {
328
- log.debug('There are no active sessions for cleanup');
499
+ this.log.debug('There are no active sessions for cleanup');
329
500
  return;
330
501
  }
331
502
 
332
- const {
333
- force = false,
334
- reason,
335
- } = opts;
336
- log.debug(`Cleaning up ${util.pluralize('active session', sessionsCount, true)}`);
503
+ const {force = false, reason} = opts;
504
+ this.log.debug(`Cleaning up ${util.pluralize('active session', sessionsCount, true)}`);
337
505
  const cleanupPromises = force
338
- ? _.values(this.sessions).map((drv) => drv.startUnexpectedShutdown(reason && new Error(reason)))
506
+ ? _.values(this.sessions).map((drv) =>
507
+ drv.startUnexpectedShutdown(reason && new Error(reason))
508
+ )
339
509
  : _.keys(this.sessions).map((id) => this.deleteSession(id));
340
510
  for (const cleanupPromise of cleanupPromises) {
341
511
  try {
342
512
  await cleanupPromise;
343
513
  } catch (e) {
344
- log.debug(e);
514
+ this.log.debug(e);
515
+ }
516
+ }
517
+ }
518
+
519
+ /**
520
+ * Get the appropriate plugins for a session (or sessionless plugins)
521
+ *
522
+ * @param {?string} sessionId - the sessionId (or null) to use to find plugins
523
+ * @returns {Array} - array of plugin instances
524
+ */
525
+ pluginsForSession(sessionId = null) {
526
+ if (sessionId) {
527
+ if (!this.sessionPlugins[sessionId]) {
528
+ this.sessionPlugins[sessionId] = this.createPluginInstances();
345
529
  }
530
+ return this.sessionPlugins[sessionId];
531
+ }
532
+
533
+ if (_.isEmpty(this.sessionlessPlugins)) {
534
+ this.sessionlessPlugins = this.createPluginInstances();
346
535
  }
536
+ return this.sessionlessPlugins;
347
537
  }
348
538
 
349
- pluginsToHandleCmd (cmd) {
350
- return this.plugins.filter((p) =>
351
- p.commands === true ||
352
- (_.isArray(p.commands) && _.includes(p.commands, cmd))
539
+ /**
540
+ * To get plugins for a command, we either get the plugin instances associated with the
541
+ * particular command's session, or in the case of sessionless plugins, pull from the set of
542
+ * plugin instances reserved for sessionless commands (and we lazily create plugin instances on
543
+ * first use)
544
+ *
545
+ * @param {string} cmd - the name of the command to find a plugin to handle
546
+ * @param {?string} sessionId - the particular session for which to find a plugin, or null if
547
+ * sessionless
548
+ */
549
+ pluginsToHandleCmd(cmd, sessionId = null) {
550
+ // to handle a given command, a plugin should either implement that command as a plugin
551
+ // instance method or it should implement a generic 'handle' method
552
+ return this.pluginsForSession(sessionId).filter(
553
+ (p) => _.isFunction(p[cmd]) || _.isFunction(p.handle)
353
554
  );
354
555
  }
355
556
 
356
- async executeCommand (cmd, ...args) {
557
+ /**
558
+ * Creates instances of all of the enabled Plugin classes
559
+ * @returns {Plugin[]}
560
+ */
561
+ createPluginInstances() {
562
+ /** @type {Plugin[]} */
563
+ const pluginInstances = [];
564
+ for (const [PluginClass, name] of this.pluginClasses.entries()) {
565
+ const cliArgs = this.getCliArgsForPlugin(name);
566
+ const plugin = new PluginClass(name, cliArgs);
567
+ pluginInstances.push(plugin);
568
+ }
569
+ return pluginInstances;
570
+ }
571
+
572
+ /**
573
+ *
574
+ * @param {string} cmd
575
+ * @param {...any} args
576
+ * @returns {Promise<{value: any, error?: Error, protocol: string} | import('type-fest').AsyncReturnType<Driver['executeCommand']>>}
577
+ */
578
+ async executeCommand(cmd, ...args) {
357
579
  // We have basically three cases for how to handle commands:
358
580
  // 1. handle getStatus (we do this as a special out of band case so it doesn't get added to an
359
581
  // execution queue, and can be called while e.g. createSession is in progress)
@@ -363,28 +585,41 @@ class AppiumDriver extends BaseDriver {
363
585
  // The tricky part is that because we support command plugins, we need to wrap any of these
364
586
  // cases with plugin handling.
365
587
 
366
- const isGetStatus = cmd === 'getStatus';
367
- const isUmbrellaCmd = !isGetStatus && isAppiumDriverCommand(cmd);
368
- const isSessionCmd = !isGetStatus && !isUmbrellaCmd;
588
+ const isGetStatus = cmd === GET_STATUS_COMMAND;
589
+ const isUmbrellaCmd = isAppiumDriverCommand(cmd);
590
+ const isSessionCmd = isSessionCommand(cmd);
369
591
 
370
- // get any plugins which are registered as handling this command
371
- const plugins = this.pluginsToHandleCmd(cmd);
592
+ // if a plugin override proxying for this command and that is why we are here instead of just
593
+ // letting the protocol proxy the command entirely, determine that, get the request object for
594
+ // use later on, then clean up the args
595
+ const reqForProxy = _.last(args)?.reqForProxy;
596
+ if (reqForProxy) {
597
+ args.pop();
598
+ }
372
599
 
373
600
  // first do some error checking. If we're requesting a session command execution, then make
374
601
  // sure that session actually exists on the session driver, and set the session driver itself
375
602
  let sessionId = null;
376
603
  let dstSession = null;
377
604
  let protocol = null;
605
+ /** @type {this | ExternalDriver} */
606
+ let driver = this;
378
607
  if (isSessionCmd) {
379
608
  sessionId = _.last(args);
380
- dstSession = await sessionsListGuard.acquire(AppiumDriver.name, () => this.sessions[sessionId]);
609
+ dstSession = this.sessions[sessionId];
381
610
  if (!dstSession) {
382
611
  throw new Error(`The session with id '${sessionId}' does not exist`);
383
612
  }
384
613
  // now save the response protocol given that the session driver's protocol might differ
385
614
  protocol = dstSession.protocol;
615
+ if (!isUmbrellaCmd) {
616
+ driver = dstSession;
617
+ }
386
618
  }
387
619
 
620
+ // get any plugins which are registered as handling this command
621
+ const plugins = this.pluginsToHandleCmd(cmd, sessionId);
622
+
388
623
  // now we define a 'cmdHandledBy' object which will keep track of which plugins have handled this
389
624
  // command. we care about this because (a) multiple plugins can handle the same command, and
390
625
  // (b) there's no guarantee that a plugin will actually call the next() method which runs the
@@ -401,11 +636,26 @@ class AppiumDriver extends BaseDriver {
401
636
  // if we're running with plugins, make sure we log that the default behavior is actually
402
637
  // happening so we can tell when the plugin call chain is unwrapping to the default behavior
403
638
  // if that's what happens
404
- plugins.length && log.info(`Executing default handling behavior for command '${cmd}'`);
639
+ plugins.length && this.log.info(`Executing default handling behavior for command '${cmd}'`);
405
640
 
406
641
  // if we make it here, we know that the default behavior is handled
407
642
  cmdHandledBy.default = true;
408
643
 
644
+ if (reqForProxy) {
645
+ // we would have proxied this command had a plugin not handled it, so the default behavior
646
+ // is to do the proxy and retrieve the result internally so it can be passed to the plugin
647
+ // in case it calls 'await next()'. This requires that the driver have defined
648
+ // 'proxyCommand' and not just 'proxyReqRes'.
649
+ if (!dstSession.proxyCommand) {
650
+ throw new NoDriverProxyCommandError();
651
+ }
652
+ return await dstSession.proxyCommand(
653
+ reqForProxy.originalUrl,
654
+ reqForProxy.method,
655
+ reqForProxy.body
656
+ );
657
+ }
658
+
409
659
  if (isGetStatus) {
410
660
  return await this.getStatus();
411
661
  }
@@ -413,7 +663,7 @@ class AppiumDriver extends BaseDriver {
413
663
  if (isUmbrellaCmd) {
414
664
  // some commands, like deleteSession, we want to make sure to handle on *this* driver,
415
665
  // not the platform driver
416
- return await super.executeCommand(cmd, ...args);
666
+ return await BaseDriver.prototype.executeCommand.call(this, cmd, ...args);
417
667
  }
418
668
 
419
669
  // here we know that we are executing a session command, and have a valid session driver
@@ -421,18 +671,39 @@ class AppiumDriver extends BaseDriver {
421
671
  };
422
672
 
423
673
  // now take our default behavior, wrap it with any number of plugin behaviors, and run it
424
- const wrappedCmd = this.wrapCommandWithPlugins({cmd, args, plugins, cmdHandledBy, next: defaultBehavior});
674
+ const wrappedCmd = this.wrapCommandWithPlugins({
675
+ driver,
676
+ cmd,
677
+ args,
678
+ plugins,
679
+ cmdHandledBy,
680
+ next: defaultBehavior,
681
+ });
425
682
  const res = await this.executeWrappedCommand({wrappedCmd, protocol});
426
683
 
427
684
  // if we had plugins, make sure to log out the helpful report about which plugins ended up
428
685
  // handling the command and which didn't
429
- plugins.length && this.logPluginHandlerReport({cmd, cmdHandledBy});
686
+ this.logPluginHandlerReport(plugins, {cmd, cmdHandledBy});
687
+
688
+ // And finally, if the command was createSession, we want to migrate any plugins which were
689
+ // previously sessionless to use the new sessionId, so that plugins can share state between
690
+ // their createSession method and other instance methods
691
+ if (cmd === CREATE_SESSION_COMMAND && this.sessionlessPlugins.length && !res.error) {
692
+ const sessionId = _.first(res.value);
693
+ this.log.info(
694
+ `Promoting ${this.sessionlessPlugins.length} sessionless plugins to be attached ` +
695
+ `to session ID ${sessionId}`
696
+ );
697
+ this.sessionPlugins[sessionId] = this.sessionlessPlugins;
698
+ this.sessionlessPlugins = [];
699
+ }
430
700
 
431
701
  return res;
432
702
  }
433
703
 
434
- wrapCommandWithPlugins ({cmd, args, next, cmdHandledBy, plugins}) {
435
- plugins.length && log.info(`Plugins which can handle cmd '${cmd}': ${plugins.map((p) => p.name)}`);
704
+ wrapCommandWithPlugins({driver, cmd, args, next, cmdHandledBy, plugins}) {
705
+ plugins.length &&
706
+ this.log.info(`Plugins which can handle cmd '${cmd}': ${plugins.map((p) => p.name)}`);
436
707
 
437
708
  // now we can go through each plugin and wrap `next` around its own handler, passing the *old*
438
709
  // next in so that it can call it if it wants to
@@ -442,16 +713,25 @@ class AppiumDriver extends BaseDriver {
442
713
  // evaluated, otherwise we end up with infinite recursion of the last `next` to be defined.
443
714
  cmdHandledBy[plugin.name] = false; // we see a new plugin, so add it to the 'cmdHandledBy' object
444
715
  next = ((_next) => async () => {
445
- log.info(`Plugin ${plugin.name} is now handling cmd '${cmd}'`);
716
+ this.log.info(`Plugin ${plugin.name} is now handling cmd '${cmd}'`);
446
717
  cmdHandledBy[plugin.name] = true; // if we make it here, this plugin has attempted to handle cmd
447
- return await plugin.handle(_next, this, cmd, ...args);
718
+ // first attempt to handle the command via a command-specific handler on the plugin
719
+ if (plugin[cmd]) {
720
+ return await plugin[cmd](_next, driver, ...args);
721
+ }
722
+ // otherwise, call the generic 'handle' method
723
+ return await plugin.handle(_next, driver, cmd, ...args);
448
724
  })(next);
449
725
  }
450
726
 
451
727
  return next;
452
728
  }
453
729
 
454
- logPluginHandlerReport ({cmd, cmdHandledBy}) {
730
+ logPluginHandlerReport(plugins, {cmd, cmdHandledBy}) {
731
+ if (!plugins.length) {
732
+ return;
733
+ }
734
+
455
735
  // at the end of the day, we have an object representing which plugins ended up getting
456
736
  // their code run as part of handling this command. Because plugins can choose *not* to
457
737
  // pass control to other plugins or to the default driver behavior, this is information
@@ -461,14 +741,18 @@ class AppiumDriver extends BaseDriver {
461
741
  const didHandle = Object.keys(cmdHandledBy).filter((k) => cmdHandledBy[k]);
462
742
  const didntHandle = Object.keys(cmdHandledBy).filter((k) => !cmdHandledBy[k]);
463
743
  if (didntHandle.length > 0) {
464
- log.info(`Command '${cmd}' was not handled by the following beahviors or plugins, even ` +
465
- `though they were registered to handle it: ${JSON.stringify(didntHandle)}. The ` +
466
- `command *was* handled by these: ${JSON.stringify(didHandle)}.`);
744
+ this.log.info(
745
+ `Command '${cmd}' was *not* handled by the following behaviours or plugins, even ` +
746
+ `though they were registered to handle it: ${JSON.stringify(didntHandle)}. The ` +
747
+ `command *was* handled by these: ${JSON.stringify(didHandle)}.`
748
+ );
467
749
  }
468
750
  }
469
751
 
470
- async executeWrappedCommand ({wrappedCmd, protocol}) {
471
- let cmdRes, cmdErr, res = {};
752
+ async executeWrappedCommand({wrappedCmd, protocol}) {
753
+ let cmdRes,
754
+ cmdErr,
755
+ res = {};
472
756
  try {
473
757
  // At this point, `wrappedCmd` defines a whole sequence of plugin handlers, culminating in
474
758
  // our default handler. Whatever it returns is what we're going to want to send back to the
@@ -491,18 +775,17 @@ class AppiumDriver extends BaseDriver {
491
775
  return res;
492
776
  }
493
777
 
494
-
495
- proxyActive (sessionId) {
778
+ proxyActive(sessionId) {
496
779
  const dstSession = this.sessions[sessionId];
497
780
  return dstSession && _.isFunction(dstSession.proxyActive) && dstSession.proxyActive(sessionId);
498
781
  }
499
782
 
500
- getProxyAvoidList (sessionId) {
783
+ getProxyAvoidList(sessionId) {
501
784
  const dstSession = this.sessions[sessionId];
502
785
  return dstSession ? dstSession.getProxyAvoidList() : [];
503
786
  }
504
787
 
505
- canProxy (sessionId) {
788
+ canProxy(sessionId) {
506
789
  const dstSession = this.sessions[sessionId];
507
790
  return dstSession && dstSession.canProxy(sessionId);
508
791
  }
@@ -510,8 +793,56 @@ class AppiumDriver extends BaseDriver {
510
793
 
511
794
  // help decide which commands should be proxied to sub-drivers and which
512
795
  // should be handled by this, our umbrella driver
513
- function isAppiumDriverCommand (cmd) {
514
- return !isSessionCommand(cmd) || cmd === 'deleteSession';
796
+ function isAppiumDriverCommand(cmd) {
797
+ return !isSessionCommand(cmd) || cmd === DELETE_SESSION_COMMAND;
798
+ }
799
+
800
+ /**
801
+ * Thrown when Appium tried to proxy a command using a driver's `proxyCommand` method but the
802
+ * method did not exist
803
+ */
804
+ export class NoDriverProxyCommandError extends Error {
805
+ /**
806
+ * @type {Readonly<string>}
807
+ */
808
+ code = 'APPIUMERR_NO_DRIVER_PROXYCOMMAND';
809
+
810
+ constructor() {
811
+ super(
812
+ `The default behavior for this command was to proxy, but the driver ` +
813
+ `did not have the 'proxyCommand' method defined. To fully support ` +
814
+ `plugins, drivers should have 'proxyCommand' set to a jwpProxy object's ` +
815
+ `'command()' method, in addition to the normal 'proxyReqRes'`
816
+ );
817
+ }
515
818
  }
516
819
 
517
- export { AppiumDriver };
820
+ export {AppiumDriver};
821
+
822
+ /**
823
+ * @typedef {import('@appium/types').ExternalDriver} ExternalDriver
824
+ * @typedef {import('@appium/types').Driver} Driver
825
+ * @typedef {import('@appium/types').DriverClass} DriverClass
826
+ * @typedef {import('@appium/types').W3CCapabilities} W3CCapabilities
827
+ * @typedef {import('@appium/types').DriverData} DriverData
828
+ * @typedef {import('@appium/types').ServerArgs} DriverOpts
829
+ * @typedef {import('@appium/types').Constraints} Constraints
830
+ * @typedef {import('@appium/types').AppiumServer} AppiumServer
831
+ * @typedef {import('@appium/types').ExtensionType} ExtensionType
832
+ * @typedef {import('./extension/driver-config').DriverConfig} DriverConfig
833
+ * @typedef {import('@appium/types').Plugin} Plugin
834
+ * @typedef {import('@appium/types').PluginClass} PluginClass
835
+ * @typedef {import('@appium/types').PluginType} PluginType
836
+ * @typedef {import('@appium/types').DriverType} DriverType
837
+ * @typedef {import('@appium/types').SessionHandler<SessionHandlerResult<any[]>,SessionHandlerResult<void>>} SessionHandler
838
+ */
839
+
840
+ /**
841
+ * Used by {@linkcode AppiumDriver.createSession} and {@linkcode AppiumDriver.deleteSession} to describe
842
+ * result.
843
+ * @template V
844
+ * @typedef SessionHandlerResult
845
+ * @property {V} [value]
846
+ * @property {Error} [error]
847
+ * @property {string} [protocol]
848
+ */