appium 2.0.0-beta.3 → 2.0.0-beta.34

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