appium 3.4.2 → 3.5.1

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 (196) hide show
  1. package/build/lib/appium.d.ts +3 -0
  2. package/build/lib/appium.d.ts.map +1 -1
  3. package/build/lib/appium.js +73 -51
  4. package/build/lib/appium.js.map +1 -1
  5. package/build/lib/bidi-commands.d.ts.map +1 -1
  6. package/build/lib/bidi-commands.js +36 -31
  7. package/build/lib/bidi-commands.js.map +1 -1
  8. package/build/lib/bootstrap/appium-initializer.d.ts.map +1 -1
  9. package/build/lib/bootstrap/appium-initializer.js +13 -13
  10. package/build/lib/bootstrap/appium-initializer.js.map +1 -1
  11. package/build/lib/bootstrap/appium-main-runner.d.ts.map +1 -1
  12. package/build/lib/bootstrap/appium-main-runner.js +2 -2
  13. package/build/lib/bootstrap/appium-main-runner.js.map +1 -1
  14. package/build/lib/bootstrap/config-file.d.ts.map +1 -1
  15. package/build/lib/bootstrap/config-file.js +11 -11
  16. package/build/lib/bootstrap/config-file.js.map +1 -1
  17. package/build/lib/bootstrap/grid-v3-register.d.ts.map +1 -1
  18. package/build/lib/bootstrap/grid-v3-register.js +9 -6
  19. package/build/lib/bootstrap/grid-v3-register.js.map +1 -1
  20. package/build/lib/bootstrap/main-helpers.d.ts +1 -1
  21. package/build/lib/bootstrap/main-helpers.d.ts.map +1 -1
  22. package/build/lib/bootstrap/main-helpers.js +19 -20
  23. package/build/lib/bootstrap/main-helpers.js.map +1 -1
  24. package/build/lib/bootstrap/node-helpers.d.ts +1 -1
  25. package/build/lib/bootstrap/node-helpers.d.ts.map +1 -1
  26. package/build/lib/bootstrap/node-helpers.js +8 -16
  27. package/build/lib/bootstrap/node-helpers.js.map +1 -1
  28. package/build/lib/bootstrap/startup-config.d.ts.map +1 -1
  29. package/build/lib/bootstrap/startup-config.js +32 -30
  30. package/build/lib/bootstrap/startup-config.js.map +1 -1
  31. package/build/lib/cli/args.d.ts +3 -2
  32. package/build/lib/cli/args.d.ts.map +1 -1
  33. package/build/lib/cli/args.js +2 -5
  34. package/build/lib/cli/args.js.map +1 -1
  35. package/build/lib/cli/driver-command.d.ts +1 -2
  36. package/build/lib/cli/driver-command.d.ts.map +1 -1
  37. package/build/lib/cli/driver-command.js +7 -8
  38. package/build/lib/cli/driver-command.js.map +1 -1
  39. package/build/lib/cli/extension-command.d.ts +2 -2
  40. package/build/lib/cli/extension-command.d.ts.map +1 -1
  41. package/build/lib/cli/extension-command.js +105 -71
  42. package/build/lib/cli/extension-command.js.map +1 -1
  43. package/build/lib/cli/extension.d.ts.map +1 -1
  44. package/build/lib/cli/extension.js.map +1 -1
  45. package/build/lib/cli/parser.d.ts.map +1 -1
  46. package/build/lib/cli/parser.js +21 -14
  47. package/build/lib/cli/parser.js.map +1 -1
  48. package/build/lib/cli/plugin-command.d.ts +1 -1
  49. package/build/lib/cli/plugin-command.d.ts.map +1 -1
  50. package/build/lib/cli/plugin-command.js +5 -5
  51. package/build/lib/cli/plugin-command.js.map +1 -1
  52. package/build/lib/cli/setup-command.d.ts +1 -1
  53. package/build/lib/cli/setup-command.d.ts.map +1 -1
  54. package/build/lib/cli/setup-command.js +14 -19
  55. package/build/lib/cli/setup-command.js.map +1 -1
  56. package/build/lib/cli/utils.d.ts.map +1 -1
  57. package/build/lib/cli/utils.js +3 -2
  58. package/build/lib/cli/utils.js.map +1 -1
  59. package/build/lib/doctor/doctor.d.ts +0 -1
  60. package/build/lib/doctor/doctor.d.ts.map +1 -1
  61. package/build/lib/doctor/doctor.js +12 -17
  62. package/build/lib/doctor/doctor.js.map +1 -1
  63. package/build/lib/extension/driver-config.d.ts.map +1 -1
  64. package/build/lib/extension/driver-config.js +11 -12
  65. package/build/lib/extension/driver-config.js.map +1 -1
  66. package/build/lib/extension/extension-config.d.ts.map +1 -1
  67. package/build/lib/extension/extension-config.js +30 -30
  68. package/build/lib/extension/extension-config.js.map +1 -1
  69. package/build/lib/extension/index.d.ts.map +1 -1
  70. package/build/lib/extension/index.js +28 -41
  71. package/build/lib/extension/index.js.map +1 -1
  72. package/build/lib/extension/manifest.d.ts +3 -2
  73. package/build/lib/extension/manifest.d.ts.map +1 -1
  74. package/build/lib/extension/manifest.js +20 -24
  75. package/build/lib/extension/manifest.js.map +1 -1
  76. package/build/lib/extension/plugin-config.d.ts.map +1 -1
  77. package/build/lib/extension/plugin-config.js +7 -9
  78. package/build/lib/extension/plugin-config.js.map +1 -1
  79. package/build/lib/helpers/build.d.ts +0 -1
  80. package/build/lib/helpers/build.d.ts.map +1 -1
  81. package/build/lib/helpers/build.js +2 -4
  82. package/build/lib/helpers/build.js.map +1 -1
  83. package/build/lib/helpers/capability.d.ts.map +1 -1
  84. package/build/lib/helpers/capability.js +27 -22
  85. package/build/lib/helpers/capability.js.map +1 -1
  86. package/build/lib/helpers/network.d.ts.map +1 -1
  87. package/build/lib/helpers/network.js +4 -3
  88. package/build/lib/helpers/network.js.map +1 -1
  89. package/build/lib/insecure-features.d.ts.map +1 -1
  90. package/build/lib/insecure-features.js +9 -13
  91. package/build/lib/insecure-features.js.map +1 -1
  92. package/build/lib/inspector-commands.d.ts.map +1 -1
  93. package/build/lib/inspector-commands.js +29 -28
  94. package/build/lib/inspector-commands.js.map +1 -1
  95. package/build/lib/logsink.d.ts.map +1 -1
  96. package/build/lib/logsink.js +8 -7
  97. package/build/lib/logsink.js.map +1 -1
  98. package/build/lib/schema/arg-spec.d.ts.map +1 -1
  99. package/build/lib/schema/arg-spec.js +5 -8
  100. package/build/lib/schema/arg-spec.js.map +1 -1
  101. package/build/lib/schema/cli-args.d.ts.map +1 -1
  102. package/build/lib/schema/cli-args.js +20 -19
  103. package/build/lib/schema/cli-args.js.map +1 -1
  104. package/build/lib/schema/cli-transformers.d.ts.map +1 -1
  105. package/build/lib/schema/cli-transformers.js.map +1 -1
  106. package/build/lib/schema/schema.d.ts.map +1 -1
  107. package/build/lib/schema/schema.js +28 -21
  108. package/build/lib/schema/schema.js.map +1 -1
  109. package/build/lib/utils/hash.d.ts +5 -0
  110. package/build/lib/utils/hash.d.ts.map +1 -0
  111. package/build/lib/{utils.js → utils/hash.js} +3 -21
  112. package/build/lib/utils/hash.js.map +1 -0
  113. package/build/lib/utils/index.d.ts +8 -0
  114. package/build/lib/utils/index.d.ts.map +1 -0
  115. package/build/lib/utils/index.js +31 -0
  116. package/build/lib/utils/index.js.map +1 -0
  117. package/build/lib/utils/is-package-changed.d.ts +16 -0
  118. package/build/lib/utils/is-package-changed.d.ts.map +1 -0
  119. package/build/lib/utils/is-package-changed.js +62 -0
  120. package/build/lib/utils/is-package-changed.js.map +1 -0
  121. package/build/lib/utils/object.d.ts +120 -0
  122. package/build/lib/utils/object.d.ts.map +1 -0
  123. package/build/lib/utils/object.js +288 -0
  124. package/build/lib/utils/object.js.map +1 -0
  125. package/build/lib/{extension → utils}/package-changed.d.ts +0 -1
  126. package/build/lib/utils/package-changed.d.ts.map +1 -0
  127. package/build/lib/{extension → utils}/package-changed.js +3 -4
  128. package/build/lib/utils/package-changed.js.map +1 -0
  129. package/build/lib/utils/package-json.d.ts +9 -0
  130. package/build/lib/utils/package-json.d.ts.map +1 -0
  131. package/build/lib/utils/package-json.js +32 -0
  132. package/build/lib/utils/package-json.js.map +1 -0
  133. package/build/lib/utils/resolve-from.d.ts +11 -0
  134. package/build/lib/utils/resolve-from.d.ts.map +1 -0
  135. package/build/lib/utils/resolve-from.js +41 -0
  136. package/build/lib/utils/resolve-from.js.map +1 -0
  137. package/build/types/cli.d.ts.map +1 -1
  138. package/build/types/index.d.ts.map +1 -1
  139. package/build/types/manifest/base.d.ts.map +1 -1
  140. package/build/types/manifest/v3.d.ts.map +1 -1
  141. package/build/types/manifest/v4.d.ts.map +1 -1
  142. package/lib/appium.ts +96 -61
  143. package/lib/bidi-commands.ts +107 -61
  144. package/lib/bootstrap/appium-initializer.ts +27 -19
  145. package/lib/bootstrap/appium-main-runner.ts +8 -4
  146. package/lib/bootstrap/config-file.ts +33 -16
  147. package/lib/bootstrap/grid-v3-register.ts +26 -19
  148. package/lib/bootstrap/main-helpers.ts +25 -19
  149. package/lib/bootstrap/node-helpers.ts +20 -24
  150. package/lib/bootstrap/startup-config.ts +42 -39
  151. package/lib/cli/args.ts +3 -4
  152. package/lib/cli/driver-command.ts +19 -13
  153. package/lib/cli/extension-command.ts +214 -149
  154. package/lib/cli/extension.ts +4 -6
  155. package/lib/cli/parser.ts +32 -30
  156. package/lib/cli/plugin-command.ts +20 -9
  157. package/lib/cli/setup-command.ts +54 -39
  158. package/lib/cli/utils.ts +4 -3
  159. package/lib/doctor/doctor.ts +15 -17
  160. package/lib/extension/driver-config.ts +22 -17
  161. package/lib/extension/extension-config.ts +76 -69
  162. package/lib/extension/index.ts +60 -51
  163. package/lib/extension/manifest.ts +35 -33
  164. package/lib/extension/plugin-config.ts +15 -10
  165. package/lib/helpers/build.ts +6 -5
  166. package/lib/helpers/capability.ts +40 -35
  167. package/lib/helpers/network.ts +6 -5
  168. package/lib/insecure-features.ts +16 -14
  169. package/lib/inspector-commands.ts +72 -40
  170. package/lib/logsink.ts +22 -26
  171. package/lib/schema/arg-spec.ts +9 -8
  172. package/lib/schema/cli-args-guards.ts +4 -4
  173. package/lib/schema/cli-args.ts +25 -21
  174. package/lib/schema/cli-transformers.ts +6 -2
  175. package/lib/schema/format-errors.ts +1 -1
  176. package/lib/schema/schema.ts +52 -38
  177. package/lib/{utils.ts → utils/hash.ts} +2 -19
  178. package/lib/utils/index.ts +23 -0
  179. package/lib/utils/is-package-changed.ts +78 -0
  180. package/lib/utils/object.ts +312 -0
  181. package/lib/{extension → utils}/package-changed.ts +6 -7
  182. package/lib/utils/package-json.ts +36 -0
  183. package/lib/utils/resolve-from.ts +40 -0
  184. package/package.json +16 -19
  185. package/scripts/autoinstall-extensions.js +8 -13
  186. package/tsconfig.json +8 -2
  187. package/types/cli.ts +18 -18
  188. package/types/index.ts +2 -2
  189. package/types/manifest/base.ts +2 -2
  190. package/types/manifest/v3.ts +2 -2
  191. package/types/manifest/v4.ts +2 -2
  192. package/build/lib/extension/package-changed.d.ts.map +0 -1
  193. package/build/lib/extension/package-changed.js.map +0 -1
  194. package/build/lib/utils.d.ts +0 -13
  195. package/build/lib/utils.d.ts.map +0 -1
  196. package/build/lib/utils.js.map +0 -1
package/lib/appium.ts CHANGED
@@ -1,4 +1,3 @@
1
- import _ from 'lodash';
2
1
  import type WebSocket from 'ws';
3
2
  import type {
4
3
  AppiumServer,
@@ -11,6 +10,7 @@ import type {
11
10
  PluginCommand,
12
11
  Protocol,
13
12
  RouteMatcher,
13
+ IAppiumIpc,
14
14
  StringRecord,
15
15
  TimestampedMultiSessionData,
16
16
  W3CDriverCaps,
@@ -46,6 +46,8 @@ import * as bidiCommands from './bidi-commands';
46
46
  import * as insecureFeatures from './insecure-features';
47
47
  import * as inspectorCommands from './inspector-commands';
48
48
  import type {DriverConfig} from './extension/driver-config';
49
+ import {AppiumIpc} from '@appium/base-driver';
50
+ import {compact, pickBy, pull} from './utils';
49
51
 
50
52
  const desiredCapabilityConstraints = {
51
53
  automationName: {
@@ -74,6 +76,11 @@ type SessionHandlerCreateResult = SessionHandlerResult<
74
76
 
75
77
  type SessionHandlerDeleteResult = SessionHandlerResult<void>;
76
78
 
79
+ /** @internal Not part of {@link ExternalDriver}; used only when wiring session IPC. */
80
+ type IpcAssignable = {
81
+ assignIpc?: (ipc: IAppiumIpc) => Promise<void>;
82
+ };
83
+
77
84
  /**
78
85
  * Umbrella driver: owns the session table, loads platform drivers and plugins, and routes
79
86
  * commands to the right session driver or plugin chain.
@@ -93,6 +100,8 @@ export class AppiumDriver extends DriverCore<AppiumDriverConstraints> {
93
100
 
94
101
  readonly sessionPlugins: Record<string, Plugin[]> = {};
95
102
 
103
+ readonly sessionIpcs: Record<string, AppiumIpc> = {};
104
+
96
105
  sessionlessPlugins: Plugin[] = [];
97
106
 
98
107
  /** Set during server init before listen; tests may assign a mock after construction. */
@@ -182,7 +191,7 @@ export class AppiumDriver extends DriverCore<AppiumDriverConstraints> {
182
191
  };
183
192
  return {
184
193
  ...statusObj,
185
- build: _.clone(getBuildInfo()),
194
+ build: structuredClone(getBuildInfo()),
186
195
  };
187
196
  }
188
197
 
@@ -201,7 +210,7 @@ export class AppiumDriver extends DriverCore<AppiumDriverConstraints> {
201
210
  */
202
211
  async getAppiumSessions(): Promise<TimestampedMultiSessionData[]> {
203
212
  this.assertFeatureEnabled(SESSION_DISCOVERY_FEATURE);
204
- return _.toPairs(this.sessions).map(([id, driver]) => ({
213
+ return Object.entries(this.sessions).map(([id, driver]) => ({
205
214
  id,
206
215
  created: driver.sessionCreationTimestampMs,
207
216
  capabilities: driver.caps as DriverCaps<AppiumDriverConstraints>,
@@ -212,7 +221,7 @@ export class AppiumDriver extends DriverCore<AppiumDriverConstraints> {
212
221
  printNewSessionAnnouncement(
213
222
  driverName: string,
214
223
  driverVersion?: string,
215
- driverBaseVersion?: string
224
+ driverBaseVersion?: string,
216
225
  ): void {
217
226
  this.log.info(
218
227
  driverVersion
@@ -246,15 +255,18 @@ export class AppiumDriver extends DriverCore<AppiumDriverConstraints> {
246
255
  */
247
256
  getCliArgsForDriver(extName: string): StringRecord | undefined {
248
257
  const allCliArgsForExt = this.args.driver?.[extName] as StringRecord | undefined;
249
- if (_.isEmpty(allCliArgsForExt)) {
258
+ if (util.isEmpty(allCliArgsForExt)) {
250
259
  return undefined;
251
260
  }
252
261
 
253
262
  const defaults = getDefaultsForExtension(DRIVER_TYPE, extName);
254
- const cliArgs = _.isEmpty(defaults)
263
+ const cliArgs = util.isEmpty(defaults)
255
264
  ? allCliArgsForExt
256
- : _.omitBy(allCliArgsForExt, (value, key) => _.isEqual(defaults[key], value));
257
- return _.isEmpty(cliArgs) ? undefined : cliArgs;
265
+ : pickBy(
266
+ allCliArgsForExt as Record<string, unknown>,
267
+ (value, key) => !util.isEqual(defaults[key as string], value),
268
+ );
269
+ return util.isEmpty(cliArgs) ? undefined : (cliArgs as StringRecord);
258
270
  }
259
271
 
260
272
  /**
@@ -265,12 +277,12 @@ export class AppiumDriver extends DriverCore<AppiumDriverConstraints> {
265
277
  async createSession(
266
278
  w3cCapabilities1: W3CAppiumDriverCaps,
267
279
  w3cCapabilities2?: W3CAppiumDriverCaps,
268
- w3cCapabilities3?: W3CAppiumDriverCaps
280
+ w3cCapabilities3?: W3CAppiumDriverCaps,
269
281
  ): Promise<SessionHandlerCreateResult> {
270
- const defaultCapabilities = _.cloneDeep(this.args.defaultCapabilities);
282
+ const defaultCapabilities = structuredClone(this.args.defaultCapabilities);
271
283
  const defaultSettings = pullSettings((defaultCapabilities ?? {}) as StringRecord);
272
- const w3cCapabilities = _.cloneDeep(
273
- [w3cCapabilities3, w3cCapabilities2, w3cCapabilities1].find(isW3cCaps)
284
+ const w3cCapabilities = structuredClone(
285
+ [w3cCapabilities3, w3cCapabilities2, w3cCapabilities1].find(isW3cCaps),
274
286
  );
275
287
  if (!w3cCapabilities) {
276
288
  throw makeNonW3cCapsError();
@@ -291,7 +303,7 @@ export class AppiumDriver extends DriverCore<AppiumDriverConstraints> {
291
303
  const parsedCaps = parseCapsForInnerDriver<AppiumDriverConstraints>(
292
304
  promoteAppiumOptions(w3cCapabilities),
293
305
  this.desiredCapConstraints,
294
- defaultCapabilities ? promoteAppiumOptionsForObject(defaultCapabilities) : undefined
306
+ defaultCapabilities ? promoteAppiumOptionsForObject(defaultCapabilities) : undefined,
295
307
  );
296
308
 
297
309
  if ('error' in parsedCaps && parsedCaps.error) {
@@ -321,14 +333,14 @@ export class AppiumDriver extends DriverCore<AppiumDriverConstraints> {
321
333
  // We also want to assign any new Bidi Commands that the driver has specified, including all
322
334
  // the standard bidi commands. But add a method existence guard since some old driver class
323
335
  // instances might not have this method
324
- if (_.isFunction(driverInstance.updateBidiCommands)) {
336
+ if (typeof driverInstance.updateBidiCommands === 'function') {
325
337
  driverInstance.updateBidiCommands(InnerDriver.newBidiCommands ?? {});
326
338
  }
327
339
 
328
340
  // Likewise, any driver-specific CLI args that were passed in should be assigned directly to
329
341
  // the driver so that they cannot be mimicked by a malicious user sending in capabilities
330
342
  const cliArgs = this.getCliArgsForDriver(driverName);
331
- if (!_.isUndefined(cliArgs)) {
343
+ if (cliArgs !== undefined) {
332
344
  (driverInstance as ExternalDriver & {cliArgs?: StringRecord}).cliArgs = cliArgs;
333
345
  }
334
346
 
@@ -348,7 +360,7 @@ export class AppiumDriver extends DriverCore<AppiumDriverConstraints> {
348
360
  throw new errors.SessionNotCreatedError(msg);
349
361
  }
350
362
  this.pendingDrivers[InnerDriver.name] = this.pendingDrivers[InnerDriver.name] || [];
351
- otherPendingDriversData = _.compact(
363
+ otherPendingDriversData = compact(
352
364
  this.pendingDrivers[InnerDriver.name].map((drv) => drv.driverData),
353
365
  );
354
366
  this.pendingDrivers[InnerDriver.name].push(driverInstance);
@@ -358,11 +370,22 @@ export class AppiumDriver extends DriverCore<AppiumDriverConstraints> {
358
370
  processedW3CCapabilities as never,
359
371
  processedW3CCapabilities,
360
372
  processedW3CCapabilities,
361
- [...runningDriversData, ...otherPendingDriversData]
373
+ [...runningDriversData, ...otherPendingDriversData],
362
374
  )) as [string, DriverCaps<AppiumDriverConstraints> & {webSocketUrl?: string | boolean}];
363
375
  this.sessions[innerSessionId] = driverInstance;
376
+ // create an IPC channel for the driver and all plugins on this session
377
+ this.sessionIpcs[innerSessionId] = new AppiumIpc({
378
+ maxObjSize: this.args.maxIpcDataSize,
379
+ maxTopics: this.args.maxIpcTopics,
380
+ log: driverInstance.log,
381
+ });
382
+ const extDriver = driverInstance as unknown as IpcAssignable;
383
+ if (typeof extDriver.assignIpc === 'function') {
384
+ // TODO remove this existence guard as a breaking change in Appium 3
385
+ await extDriver.assignIpc(this.sessionIpcs[innerSessionId]);
386
+ }
364
387
  } finally {
365
- _.pull(this.pendingDrivers[InnerDriver.name], driverInstance);
388
+ pull(this.pendingDrivers[InnerDriver.name], driverInstance);
366
389
  }
367
390
 
368
391
  this.attachUnexpectedShutdownHandler(driverInstance, innerSessionId);
@@ -376,7 +399,7 @@ export class AppiumDriver extends DriverCore<AppiumDriverConstraints> {
376
399
  await driverInstance.startNewCommandTimeout();
377
400
 
378
401
  // apply initial values to Appium settings (if provided)
379
- if (driverInstance.isW3CProtocol() && !_.isEmpty(w3cSettings)) {
402
+ if (driverInstance.isW3CProtocol() && !util.isEmpty(w3cSettings)) {
380
403
  this.log.info(
381
404
  `Applying the initial values to Appium settings parsed from W3C caps: ` +
382
405
  JSON.stringify(w3cSettings),
@@ -394,7 +417,7 @@ export class AppiumDriver extends DriverCore<AppiumDriverConstraints> {
394
417
  const bidiUrl = `${scheme}://${host}:${port}${basePath}${BIDI_BASE_PATH}/${innerSessionId}`;
395
418
  this.log.info(
396
419
  `Upstream driver responded with webSocketUrl ${dCaps.webSocketUrl}, will rewrite to ` +
397
- `${bidiUrl} for response to client`
420
+ `${bidiUrl} for response to client`,
398
421
  );
399
422
  // @ts-ignore webSocketUrl gets sent by the client as a boolean, but then it is supposed
400
423
  // to come back from the server as a string. TODO figure out how to express this in our
@@ -424,7 +447,7 @@ export class AppiumDriver extends DriverCore<AppiumDriverConstraints> {
424
447
 
425
448
  if (this.sessionPlugins[innerSessionId]) {
426
449
  for (const plugin of this.sessionPlugins[innerSessionId]) {
427
- if (_.isFunction(plugin.onUnexpectedShutdown)) {
450
+ if (typeof plugin.onUnexpectedShutdown === 'function') {
428
451
  this.log.debug(
429
452
  `Plugin ${plugin.name} defines an unexpected shutdown handler; calling it now`,
430
453
  );
@@ -442,11 +465,10 @@ export class AppiumDriver extends DriverCore<AppiumDriverConstraints> {
442
465
  }
443
466
 
444
467
  this.log.info(`Removing session '${innerSessionId}' from our master session list`);
445
- delete this.sessions[innerSessionId];
446
- delete this.sessionPlugins[innerSessionId];
468
+ this.deleteLinkedSessionObjects(innerSessionId);
447
469
  };
448
470
 
449
- if (_.isFunction(driver.onUnexpectedShutdown)) {
471
+ if (typeof driver.onUnexpectedShutdown === 'function') {
450
472
  driver.onUnexpectedShutdown(onShutdown);
451
473
  } else {
452
474
  this.log.warn(
@@ -462,8 +484,8 @@ export class AppiumDriver extends DriverCore<AppiumDriverConstraints> {
462
484
  * @remarks `InnerDriver` is expected to be the driver class; only `.name` is read.
463
485
  */
464
486
  async curSessionDataForDriver(InnerDriver: {name: string}): Promise<DriverData[]> {
465
- const data = _.compact(
466
- _.values(this.sessions)
487
+ const data = compact(
488
+ Object.values(this.sessions)
467
489
  .filter((s) => s.constructor.name === InnerDriver.name)
468
490
  .map((s) => s.driverData),
469
491
  );
@@ -489,20 +511,13 @@ export class AppiumDriver extends DriverCore<AppiumDriverConstraints> {
489
511
  let dstSession: ExternalDriver | undefined;
490
512
  if (this.sessions[sessionId]) {
491
513
  const curConstructorName = this.sessions[sessionId].constructor.name;
492
- otherSessionsData = _.toPairs(this.sessions)
514
+ otherSessionsData = Object.entries(this.sessions)
493
515
  .filter(
494
516
  ([key, value]) => value.constructor.name === curConstructorName && key !== sessionId,
495
517
  )
496
518
  .map(([, value]) => value.driverData);
497
519
  dstSession = this.sessions[sessionId];
498
520
  protocol = dstSession.protocol;
499
- this.log.info(`Removing session ${sessionId} from our master session list`);
500
- // regardless of whether the deleteSession completes successfully or not
501
- // make the session unavailable, because who knows what state it might
502
- // be in otherwise
503
- delete this.sessions[sessionId];
504
- delete this.sessionPlugins[sessionId];
505
-
506
521
  this.cleanupBidiSockets(sessionId);
507
522
  }
508
523
  // this may not be correct, but if `dstSession` was falsy, the call to `deleteSession()` would
@@ -510,6 +525,7 @@ export class AppiumDriver extends DriverCore<AppiumDriverConstraints> {
510
525
  if (!dstSession) {
511
526
  throw new Error('Session not found');
512
527
  }
528
+
513
529
  return {
514
530
  protocol,
515
531
  value: await dstSession.deleteSession(sessionId, otherSessionsData),
@@ -521,6 +537,12 @@ export class AppiumDriver extends DriverCore<AppiumDriverConstraints> {
521
537
  protocol,
522
538
  error: e instanceof Error ? e : new Error(msg),
523
539
  };
540
+ } finally {
541
+ // regardless of whether the deleteSession completes successfully or not
542
+ // make the session unavailable, because who knows what state it might
543
+ // be in otherwise
544
+ this.log.info(`Removing session ${sessionId} from our master session list`);
545
+ this.deleteLinkedSessionObjects(sessionId);
524
546
  }
525
547
  }
526
548
 
@@ -529,7 +551,7 @@ export class AppiumDriver extends DriverCore<AppiumDriverConstraints> {
529
551
  * when `force` is true.
530
552
  */
531
553
  async deleteAllSessions(opts: {force?: boolean; reason?: string} = {}): Promise<void> {
532
- const sessionsCount = _.size(this.sessions);
554
+ const sessionsCount = Object.keys(this.sessions).length;
533
555
  if (0 === sessionsCount) {
534
556
  this.log.debug('There are no active sessions for cleanup');
535
557
  return;
@@ -538,10 +560,10 @@ export class AppiumDriver extends DriverCore<AppiumDriverConstraints> {
538
560
  const {force = false, reason} = opts;
539
561
  this.log.debug(`Cleaning up ${util.pluralize('active session', sessionsCount, true)}`);
540
562
  const cleanupPromises = force
541
- ? _.values(this.sessions).map((drv) =>
563
+ ? Object.values(this.sessions).map((drv) =>
542
564
  drv.startUnexpectedShutdown(reason ? new Error(reason) : undefined),
543
565
  )
544
- : _.keys(this.sessions).map((id) => this.deleteSession(id));
566
+ : Object.keys(this.sessions).map((id) => this.deleteSession(id));
545
567
  for (const cleanupPromise of cleanupPromises) {
546
568
  try {
547
569
  await cleanupPromise;
@@ -570,7 +592,7 @@ export class AppiumDriver extends DriverCore<AppiumDriverConstraints> {
570
592
  ));
571
593
  }
572
594
 
573
- if (_.isEmpty(this.sessionlessPlugins)) {
595
+ if (util.isEmpty(this.sessionlessPlugins)) {
574
596
  this.sessionlessPlugins = this.createPluginInstances();
575
597
  }
576
598
  return this.sessionlessPlugins;
@@ -583,9 +605,10 @@ export class AppiumDriver extends DriverCore<AppiumDriverConstraints> {
583
605
  pluginsToHandleCmd(cmd: string, sessionId: string | null = null): Plugin[] {
584
606
  // to handle a given command, a plugin should either implement that command as a plugin
585
607
  // instance method or it should implement a generic 'handle' method
586
- return this.pluginsForSession(sessionId).filter(
587
- (p) => _.isFunction(p[cmd]) || _.isFunction(p.handle),
588
- );
608
+ return this.pluginsForSession(sessionId).filter((p) => {
609
+ const pluginMethod = (p as Plugin & Record<string, unknown>)[cmd];
610
+ return typeof pluginMethod === 'function' || typeof p.handle === 'function';
611
+ });
589
612
  }
590
613
 
591
614
  /**
@@ -598,7 +621,7 @@ export class AppiumDriver extends DriverCore<AppiumDriverConstraints> {
598
621
  const cliArgs = this.getCliArgsForPlugin(name);
599
622
  const plugin = new PluginClass(name, cliArgs, driverId);
600
623
  const extPlugin = plugin as Plugin & ExtensionCore;
601
- if (_.isFunction(extPlugin.updateBidiCommands)) {
624
+ if (typeof extPlugin.updateBidiCommands === 'function') {
602
625
  extPlugin.updateBidiCommands(PluginClass.newBidiCommands ?? {});
603
626
  }
604
627
  pluginInstances.push(plugin);
@@ -627,7 +650,7 @@ export class AppiumDriver extends DriverCore<AppiumDriverConstraints> {
627
650
  // if a plugin override proxying for this command and that is why we are here instead of just
628
651
  // letting the protocol proxy the command entirely, determine that, get the request object for
629
652
  // use later on, then clean up the args
630
- const reqForProxy = _.last(args)?.reqForProxy;
653
+ const reqForProxy = args.at(-1)?.reqForProxy;
631
654
  if (reqForProxy) {
632
655
  args.pop();
633
656
  }
@@ -640,7 +663,7 @@ export class AppiumDriver extends DriverCore<AppiumDriverConstraints> {
640
663
  // eslint-disable-next-line @typescript-eslint/no-this-alias
641
664
  let driver: this | ExternalDriver = this;
642
665
  if (isSessionCmd) {
643
- sessionId = _.last(args) as string;
666
+ sessionId = args.at(-1) as string;
644
667
  dstSession = this.sessions[sessionId] ?? null;
645
668
  if (!dstSession) {
646
669
  throw new Error(`The session with id '${sessionId}' does not exist`);
@@ -754,7 +777,7 @@ export class AppiumDriver extends DriverCore<AppiumDriverConstraints> {
754
777
  // previously sessionless to use the new sessionId, so that plugins can share state between
755
778
  // their createSession method and other instance methods
756
779
  if (cmd === CREATE_SESSION_COMMAND && this.sessionlessPlugins.length && !res.error) {
757
- const newSessionId = _.first(res.value as unknown[]) as string;
780
+ const newSessionId = (res.value as unknown[])[0] as string;
758
781
  this.log.info(
759
782
  `Promoting ${this.sessionlessPlugins.length} sessionless plugins to be attached ` +
760
783
  `to session ID ${newSessionId}`,
@@ -762,11 +785,17 @@ export class AppiumDriver extends DriverCore<AppiumDriverConstraints> {
762
785
  this.sessionPlugins[newSessionId] = this.sessionlessPlugins;
763
786
  const promoted = this.sessionPlugins[newSessionId] as (Plugin & ExtensionCore)[];
764
787
  for (const p of promoted) {
765
- if (_.isFunction(p.updateLogPrefix)) {
788
+ if (typeof p.updateLogPrefix === 'function') {
766
789
  p.updateLogPrefix(
767
- `${generateDriverLogPrefix(p)} <${generateDriverLogPrefix(this.sessions[newSessionId])}>`
790
+ `${generateDriverLogPrefix(p)} <${generateDriverLogPrefix(this.sessions[newSessionId])}>`,
768
791
  );
769
792
  }
793
+ // we also want to assign the IPC channel for this session to the plugins
794
+ const extPlugin = p as unknown as IpcAssignable;
795
+ if (typeof extPlugin.assignIpc === 'function') {
796
+ // TODO remove this existence guard as a breaking change in Appium 4
797
+ await extPlugin.assignIpc(this.sessionIpcs[newSessionId]);
798
+ }
770
799
  }
771
800
  this.sessionlessPlugins = [];
772
801
  }
@@ -806,7 +835,7 @@ export class AppiumDriver extends DriverCore<AppiumDriverConstraints> {
806
835
  cmdHandledBy[plugin.name] = true; // if we make it here, this plugin has attempted to handle cmd
807
836
  // first attempt to handle the command via a command-specific handler on the plugin
808
837
  const cmdHandler = (plugin as Plugin & Record<string, unknown>)[cmd];
809
- if (_.isFunction(cmdHandler)) {
838
+ if (typeof cmdHandler === 'function') {
810
839
  // Command methods must run with plugin as `this` (detached property access drops binding).
811
840
  return await (cmdHandler as PluginCommand).call(
812
841
  plugin,
@@ -815,7 +844,7 @@ export class AppiumDriver extends DriverCore<AppiumDriverConstraints> {
815
844
  ...args,
816
845
  );
817
846
  }
818
- if (!_.isFunction(plugin.handle)) {
847
+ if (typeof plugin.handle !== 'function') {
819
848
  throw new Error(`Plugin ${plugin.name} cannot handle command '${cmd}'`);
820
849
  }
821
850
  return await plugin.handle(_next, driver as ExternalDriver, cmd, ...args);
@@ -828,7 +857,7 @@ export class AppiumDriver extends DriverCore<AppiumDriverConstraints> {
828
857
  /** After a command with plugins, logs which handlers ran vs. were skipped (debugging aid). */
829
858
  logPluginHandlerReport(
830
859
  plugins: Plugin[],
831
- {cmd, cmdHandledBy}: {cmd: string; cmdHandledBy: Record<string, boolean>}
860
+ {cmd, cmdHandledBy}: {cmd: string; cmdHandledBy: Record<string, boolean>},
832
861
  ): void {
833
862
  if (!plugins.length) {
834
863
  return;
@@ -874,7 +903,7 @@ export class AppiumDriver extends DriverCore<AppiumDriverConstraints> {
874
903
  // Sadly, we don't know exactly what kind of object will be returned. It will either be a bare
875
904
  // object, or a protocol-aware object with protocol and error/value keys. So we need to sniff
876
905
  // it and make sure we don't double-wrap it if it's the latter kind.
877
- if (_.isPlainObject(cmdRes) && _.has(cmdRes, 'protocol')) {
906
+ if (util.isPlainObject(cmdRes) && 'protocol' in cmdRes) {
878
907
  Object.assign(res, cmdRes);
879
908
  } else {
880
909
  res.value = cmdRes;
@@ -887,21 +916,28 @@ export class AppiumDriver extends DriverCore<AppiumDriverConstraints> {
887
916
  /** Whether the inner session driver is actively proxying for this session id. */
888
917
  override proxyActive(sessionId: string): boolean {
889
918
  const dstSession = this.sessions[sessionId];
890
- return _.isFunction(dstSession?.proxyActive) && dstSession.proxyActive(sessionId);
919
+ return typeof dstSession?.proxyActive === 'function' && dstSession.proxyActive(sessionId);
891
920
  }
892
921
 
893
922
  /** URL patterns the session driver does not want proxied; empty if no session or no list. */
894
923
  override getProxyAvoidList(sessionId: string): RouteMatcher[] {
895
924
  const dstSession = this.sessions[sessionId];
896
- return _.isFunction(dstSession?.getProxyAvoidList) ? dstSession.getProxyAvoidList() : [];
925
+ return typeof dstSession?.getProxyAvoidList === 'function'
926
+ ? dstSession.getProxyAvoidList()
927
+ : [];
897
928
  }
898
929
 
899
930
  /** Whether the session driver supports proxying for this session. */
900
931
  override canProxy(sessionId: string): boolean {
901
932
  const dstSession = this.sessions[sessionId];
902
- return _.isFunction(dstSession?.canProxy) && dstSession.canProxy(sessionId);
933
+ return typeof dstSession?.canProxy === 'function' && dstSession.canProxy(sessionId);
903
934
  }
904
935
 
936
+ private deleteLinkedSessionObjects(sessionId: string): void {
937
+ delete this.sessions[sessionId];
938
+ delete this.sessionPlugins[sessionId];
939
+ delete this.sessionIpcs[sessionId];
940
+ }
905
941
  }
906
942
 
907
943
  /**
@@ -923,11 +959,10 @@ export class NoDriverProxyCommandError extends Error {
923
959
 
924
960
  /** True if `cmd` should run on the umbrella driver instead of only on the session’s inner driver. */
925
961
  function isAppiumDriverCommand(cmd: string): boolean {
926
- return !isSessionCommand(cmd)
927
- || _.includes([
928
- DELETE_SESSION_COMMAND,
929
- LIST_DRIVER_COMMANDS_COMMAND,
930
- LIST_DRIVER_EXTENSIONS_COMMAND,
931
- ], cmd);
962
+ return (
963
+ !isSessionCommand(cmd) ||
964
+ [DELETE_SESSION_COMMAND, LIST_DRIVER_COMMANDS_COMMAND, LIST_DRIVER_EXTENSIONS_COMMAND].includes(
965
+ cmd,
966
+ )
967
+ );
932
968
  }
933
-