@appium/base-driver 10.6.0 → 10.7.0

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 (157) hide show
  1. package/build/lib/basedriver/capabilities.d.ts +1 -1
  2. package/build/lib/basedriver/capabilities.d.ts.map +1 -1
  3. package/build/lib/basedriver/capabilities.js +15 -7
  4. package/build/lib/basedriver/capabilities.js.map +1 -1
  5. package/build/lib/basedriver/commands/bidi.js +1 -1
  6. package/build/lib/basedriver/commands/event.js.map +1 -1
  7. package/build/lib/basedriver/commands/execute.js.map +1 -1
  8. package/build/lib/basedriver/commands/find.d.ts.map +1 -1
  9. package/build/lib/basedriver/commands/find.js +2 -1
  10. package/build/lib/basedriver/commands/find.js.map +1 -1
  11. package/build/lib/basedriver/commands/timeout.js +4 -4
  12. package/build/lib/basedriver/commands/timeout.js.map +1 -1
  13. package/build/lib/basedriver/core.d.ts.map +1 -1
  14. package/build/lib/basedriver/core.js +5 -2
  15. package/build/lib/basedriver/core.js.map +1 -1
  16. package/build/lib/basedriver/device-settings.d.ts.map +1 -1
  17. package/build/lib/basedriver/device-settings.js.map +1 -1
  18. package/build/lib/basedriver/driver.d.ts.map +1 -1
  19. package/build/lib/basedriver/driver.js +23 -24
  20. package/build/lib/basedriver/driver.js.map +1 -1
  21. package/build/lib/basedriver/extension-core.d.ts.map +1 -1
  22. package/build/lib/basedriver/extension-core.js +11 -5
  23. package/build/lib/basedriver/extension-core.js.map +1 -1
  24. package/build/lib/basedriver/helpers.d.ts.map +1 -1
  25. package/build/lib/basedriver/helpers.js +20 -4
  26. package/build/lib/basedriver/helpers.js.map +1 -1
  27. package/build/lib/basedriver/ipc.d.ts.map +1 -1
  28. package/build/lib/basedriver/ipc.js +6 -4
  29. package/build/lib/basedriver/ipc.js.map +1 -1
  30. package/build/lib/basedriver/validation.d.ts.map +1 -1
  31. package/build/lib/basedriver/validation.js +3 -2
  32. package/build/lib/basedriver/validation.js.map +1 -1
  33. package/build/lib/express/express-logging.d.ts +0 -1
  34. package/build/lib/express/express-logging.d.ts.map +1 -1
  35. package/build/lib/express/express-logging.js +9 -8
  36. package/build/lib/express/express-logging.js.map +1 -1
  37. package/build/lib/express/idempotency.js.map +1 -1
  38. package/build/lib/express/middleware.d.ts.map +1 -1
  39. package/build/lib/express/middleware.js.map +1 -1
  40. package/build/lib/express/server.d.ts +1 -1
  41. package/build/lib/express/server.d.ts.map +1 -1
  42. package/build/lib/express/server.js +19 -20
  43. package/build/lib/express/server.js.map +1 -1
  44. package/build/lib/express/websocket.d.ts.map +1 -1
  45. package/build/lib/express/websocket.js.map +1 -1
  46. package/build/lib/helpers/capabilities.d.ts.map +1 -1
  47. package/build/lib/helpers/capabilities.js.map +1 -1
  48. package/build/lib/helpers/levenshtein-match.d.ts.map +1 -1
  49. package/build/lib/helpers/levenshtein-match.js +4 -1
  50. package/build/lib/helpers/levenshtein-match.js.map +1 -1
  51. package/build/lib/index.d.ts +1 -1
  52. package/build/lib/index.d.ts.map +1 -1
  53. package/build/lib/index.js +3 -2
  54. package/build/lib/index.js.map +1 -1
  55. package/build/lib/jsonwp-proxy/protocol-converter.d.ts.map +1 -1
  56. package/build/lib/jsonwp-proxy/protocol-converter.js +14 -7
  57. package/build/lib/jsonwp-proxy/protocol-converter.js.map +1 -1
  58. package/build/lib/jsonwp-proxy/proxy.d.ts.map +1 -1
  59. package/build/lib/jsonwp-proxy/proxy.js +17 -11
  60. package/build/lib/jsonwp-proxy/proxy.js.map +1 -1
  61. package/build/lib/protocol/errors.d.ts.map +1 -1
  62. package/build/lib/protocol/errors.js +13 -13
  63. package/build/lib/protocol/errors.js.map +1 -1
  64. package/build/lib/protocol/protocol.d.ts +1 -1
  65. package/build/lib/protocol/protocol.d.ts.map +1 -1
  66. package/build/lib/protocol/protocol.js +35 -18
  67. package/build/lib/protocol/protocol.js.map +1 -1
  68. package/build/lib/protocol/routes.d.ts.map +1 -1
  69. package/build/lib/protocol/routes.js +7 -5
  70. package/build/lib/protocol/routes.js.map +1 -1
  71. package/build/lib/test-pages/crash.d.ts.map +1 -0
  72. package/build/lib/test-pages/crash.js.map +1 -0
  73. package/build/lib/test-pages/env.d.ts +5 -0
  74. package/build/lib/test-pages/env.d.ts.map +1 -0
  75. package/build/lib/test-pages/env.js +12 -0
  76. package/build/lib/test-pages/env.js.map +1 -0
  77. package/build/lib/{express/static.d.ts → test-pages/handlers.d.ts} +1 -2
  78. package/build/lib/test-pages/handlers.d.ts.map +1 -0
  79. package/build/lib/{express/static.js → test-pages/handlers.js} +7 -17
  80. package/build/lib/test-pages/handlers.js.map +1 -0
  81. package/build/lib/test-pages/index.d.ts +6 -0
  82. package/build/lib/test-pages/index.d.ts.map +1 -0
  83. package/build/lib/test-pages/index.js +35 -0
  84. package/build/lib/test-pages/index.js.map +1 -0
  85. package/build/lib/test-pages/static-dir.d.ts +8 -0
  86. package/build/lib/test-pages/static-dir.d.ts.map +1 -0
  87. package/build/lib/test-pages/static-dir.js +24 -0
  88. package/build/lib/test-pages/static-dir.js.map +1 -0
  89. package/build/lib/test-pages/template.d.ts +3 -0
  90. package/build/lib/test-pages/template.d.ts.map +1 -0
  91. package/build/lib/test-pages/template.js +19 -0
  92. package/build/lib/test-pages/template.js.map +1 -0
  93. package/build/lib/utils.d.ts +0 -2
  94. package/build/lib/utils.d.ts.map +1 -1
  95. package/build/lib/utils.js +0 -16
  96. package/build/lib/utils.js.map +1 -1
  97. package/lib/basedriver/capabilities.ts +72 -66
  98. package/lib/basedriver/commands/bidi.ts +1 -1
  99. package/lib/basedriver/commands/event.ts +10 -5
  100. package/lib/basedriver/commands/execute.ts +12 -9
  101. package/lib/basedriver/commands/find.ts +20 -12
  102. package/lib/basedriver/commands/log.ts +2 -2
  103. package/lib/basedriver/commands/timeout.ts +17 -8
  104. package/lib/basedriver/core.ts +14 -14
  105. package/lib/basedriver/device-settings.ts +4 -8
  106. package/lib/basedriver/driver.ts +50 -40
  107. package/lib/basedriver/extension-core.ts +33 -17
  108. package/lib/basedriver/helpers.ts +57 -26
  109. package/lib/basedriver/ipc.ts +37 -18
  110. package/lib/basedriver/validation.ts +13 -6
  111. package/lib/express/express-logging.ts +14 -17
  112. package/lib/express/idempotency.ts +6 -6
  113. package/lib/express/middleware.ts +10 -12
  114. package/lib/express/server.ts +53 -61
  115. package/lib/express/websocket.ts +5 -7
  116. package/lib/helpers/capabilities.ts +5 -4
  117. package/lib/helpers/extension-command-name.ts +1 -1
  118. package/lib/helpers/levenshtein-match.ts +20 -11
  119. package/lib/index.js +2 -1
  120. package/lib/jsonwp-proxy/protocol-converter.ts +51 -27
  121. package/lib/jsonwp-proxy/proxy.ts +42 -42
  122. package/lib/protocol/errors.ts +47 -67
  123. package/lib/protocol/protocol.ts +116 -72
  124. package/lib/protocol/routes.ts +9 -9
  125. package/lib/test-pages/env.ts +9 -0
  126. package/lib/{express/static.ts → test-pages/handlers.ts} +7 -27
  127. package/lib/test-pages/index.ts +34 -0
  128. package/lib/test-pages/static-dir.ts +19 -0
  129. package/lib/test-pages/template.ts +17 -0
  130. package/lib/utils.ts +3 -23
  131. package/package.json +9 -10
  132. package/tsconfig.json +1 -0
  133. package/build/lib/express/crash.d.ts.map +0 -1
  134. package/build/lib/express/crash.js.map +0 -1
  135. package/build/lib/express/static.d.ts.map +0 -1
  136. package/build/lib/express/static.js.map +0 -1
  137. /package/build/lib/{express → test-pages}/crash.d.ts +0 -0
  138. /package/build/lib/{express → test-pages}/crash.js +0 -0
  139. /package/lib/{express → test-pages}/crash.ts +0 -0
  140. /package/{static → test-fixtures/static}/appium.png +0 -0
  141. /package/{static → test-fixtures/static}/favicon.ico +0 -0
  142. /package/{static → test-fixtures/static}/js/jquery.min.js +0 -0
  143. /package/{static → test-fixtures/static}/test/frameset.html +0 -0
  144. /package/{static → test-fixtures/static}/test/guinea-pig-app-banner.html +0 -0
  145. /package/{static → test-fixtures/static}/test/guinea-pig-scrollable.html +0 -0
  146. /package/{static → test-fixtures/static}/test/guinea-pig.html +0 -0
  147. /package/{static → test-fixtures/static}/test/guinea-pig2.html +0 -0
  148. /package/{static → test-fixtures/static}/test/guinea-pig3.html +0 -0
  149. /package/{static → test-fixtures/static}/test/guinea-pig4.html +0 -0
  150. /package/{static → test-fixtures/static}/test/guinea-pig5.html +0 -0
  151. /package/{static → test-fixtures/static}/test/iframes.html +0 -0
  152. /package/{static → test-fixtures/static}/test/shadow-dom.html +0 -0
  153. /package/{static → test-fixtures/static}/test/subframe1.html +0 -0
  154. /package/{static → test-fixtures/static}/test/subframe2.html +0 -0
  155. /package/{static → test-fixtures/static}/test/subframe3.html +0 -0
  156. /package/{static → test-fixtures/static}/test/touch.html +0 -0
  157. /package/{static → test-fixtures/static}/test/welcome.html +0 -0
@@ -14,10 +14,7 @@ import type {
14
14
  import AsyncLock from 'async-lock';
15
15
  import {util} from '@appium/support';
16
16
  import os from 'node:os';
17
- import {
18
- DEFAULT_BASE_PATH,
19
- PROTOCOLS,
20
- } from '../constants';
17
+ import {DEFAULT_BASE_PATH, PROTOCOLS} from '../constants';
21
18
  import {errors} from '../protocol';
22
19
  import {DeviceSettings} from './device-settings';
23
20
  import * as helpers from './helpers';
@@ -31,7 +28,8 @@ const ALL_DRIVERS_MATCH = '*';
31
28
  const FEATURE_NAME_SEPARATOR = ':';
32
29
 
33
30
  export class DriverCore<const C extends Constraints, Settings extends StringRecord = StringRecord>
34
- extends ExtensionCore implements Core<C, Settings>
31
+ extends ExtensionCore
32
+ implements Core<C, Settings>
35
33
  {
36
34
  /**
37
35
  * Make the basedriver version available so for any driver which inherits from this package, we
@@ -41,7 +39,7 @@ export class DriverCore<const C extends Constraints, Settings extends StringReco
41
39
 
42
40
  sessionId: string | null;
43
41
 
44
- sessionCreationTimestampMs: number;
42
+ sessionCreationTimestampMs!: number;
45
43
 
46
44
  opts: DriverOpts<C>;
47
45
 
@@ -104,7 +102,6 @@ export class DriverCore<const C extends Constraints, Settings extends StringReco
104
102
  super();
105
103
  this._log = this.log; // TODO: remove references to this._log and use this.log instead
106
104
 
107
-
108
105
  // setup state
109
106
  this.opts = opts as DriverOpts<C>;
110
107
 
@@ -273,19 +270,23 @@ export class DriverCore<const C extends Constraints, Settings extends StringReco
273
270
  // but better be safe than sorry
274
271
  throw new Error(
275
272
  `The full feature name must include both the automation name ` +
276
- `'${this.opts.automationName}' or the '${ALL_DRIVERS_MATCH}' ` +
277
- `wildcard to apply the feature to all installed drivers, and ` +
278
- `the feature name split by a colon. Got '${fullName}' instead`
273
+ `'${this.opts.automationName}' or the '${ALL_DRIVERS_MATCH}' ` +
274
+ `wildcard to apply the feature to all installed drivers, and ` +
275
+ `the feature name split by a colon. Got '${fullName}' instead`,
279
276
  );
280
277
  }
281
278
  return [
282
279
  fullName.substring(0, separatorPos).toLowerCase(),
283
- fullName.substring(separatorPos + 1)
280
+ fullName.substring(separatorPos + 1),
284
281
  ];
285
282
  };
286
283
  const parseFullNames = (fullNames: string[]) => fullNames.map(parseFullName);
287
- const matches = ([automationName, featureName]: [string, string]) =>
288
- [currentAutomationName, ALL_DRIVERS_MATCH].includes(automationName) && featureName === name;
284
+ const matches = (pair: string[]) => {
285
+ const [automationName, featureName] = pair;
286
+ return (
287
+ [currentAutomationName, ALL_DRIVERS_MATCH].includes(automationName) && featureName === name
288
+ );
289
+ };
289
290
 
290
291
  // if we have explicitly denied this feature, return false immediately
291
292
  if (!util.isEmpty(this.denyInsecure) && parseFullNames(this.denyInsecure).some(matches)) {
@@ -406,5 +407,4 @@ export class DriverCore<const C extends Constraints, Settings extends StringReco
406
407
  this.noCommandTimer = null;
407
408
  }
408
409
  }
409
-
410
410
  }
@@ -23,7 +23,7 @@ export class DeviceSettings<T extends StringRecord = StringRecord> implements ID
23
23
  */
24
24
  constructor(
25
25
  defaultSettings: T = {} as T,
26
- onSettingsUpdate: SettingsUpdateListener<T> = async () => {}
26
+ onSettingsUpdate: SettingsUpdateListener<T> = async () => {},
27
27
  ) {
28
28
  this._settings = {...defaultSettings};
29
29
  this._onSettingsUpdate = onSettingsUpdate;
@@ -38,14 +38,14 @@ export class DeviceSettings<T extends StringRecord = StringRecord> implements ID
38
38
  if (!util.isPlainObject(newSettings)) {
39
39
  throw new errors.InvalidArgumentError(
40
40
  `Settings update should be called with valid JSON. Got ` +
41
- `${JSON.stringify(newSettings)} instead`
41
+ `${JSON.stringify(newSettings)} instead`,
42
42
  );
43
43
  }
44
44
 
45
45
  if (node.getObjectSize({...this._settings, ...newSettings}) >= MAX_SETTINGS_SIZE) {
46
46
  throw new errors.InvalidArgumentError(
47
47
  `New settings cannot be applied, because the overall ` +
48
- `object size exceeds the allowed limit of ${util.toReadableSizeString(MAX_SETTINGS_SIZE)}`
48
+ `object size exceeds the allowed limit of ${util.toReadableSizeString(MAX_SETTINGS_SIZE)}`,
49
49
  );
50
50
  }
51
51
 
@@ -56,11 +56,7 @@ export class DeviceSettings<T extends StringRecord = StringRecord> implements ID
56
56
  continue;
57
57
  }
58
58
  }
59
- await this._onSettingsUpdate(
60
- prop as keyof T,
61
- newSettings[prop],
62
- this._settings[prop]
63
- );
59
+ await this._onSettingsUpdate(prop as keyof T, newSettings[prop], this._settings[prop]);
64
60
  this._settings[prop] = newSettings[prop];
65
61
  }
66
62
  }
@@ -1,3 +1,4 @@
1
+ import type AsyncLock from 'async-lock';
1
2
  import {util} from '@appium/support';
2
3
  import {
3
4
  BASE_DESIRED_CAP_CONSTRAINTS,
@@ -24,8 +25,11 @@ import {DELETE_SESSION_COMMAND, determineProtocol, errors} from '../protocol';
24
25
  import {processCapabilities, validateCaps} from './capabilities';
25
26
  import {DriverCore} from './core';
26
27
  import * as helpers from './helpers';
27
- import {resolveExecuteExtensionName} from '../helpers/extension-command-name';
28
28
  import {mergePlainObjects} from '../utils';
29
+ import {resolveExecuteExtensionName} from '../helpers/extension-command-name';
30
+
31
+ type CommandInvoker<C extends Constraints> = BaseDriver<C> &
32
+ Record<string, ((...args: any[]) => any) | undefined>;
29
33
 
30
34
  const EVENT_SESSION_INIT = 'newSessionRequested';
31
35
  const EVENT_SESSION_START = 'newSessionStarted';
@@ -34,20 +38,20 @@ const EVENT_SESSION_QUIT_DONE = 'quitSessionFinished';
34
38
  const ON_UNEXPECTED_SHUTDOWN_EVENT = 'onUnexpectedShutdown';
35
39
 
36
40
  export class BaseDriver<
37
- const C extends Constraints,
38
- CArgs extends StringRecord = StringRecord,
39
- Settings extends StringRecord = StringRecord,
40
- CreateResult = DefaultCreateSessionResult<C>,
41
- DeleteResult = DefaultDeleteSessionResult,
42
- SessionData extends StringRecord = StringRecord,
43
- >
41
+ const C extends Constraints,
42
+ CArgs extends StringRecord = StringRecord,
43
+ Settings extends StringRecord = StringRecord,
44
+ CreateResult = DefaultCreateSessionResult<C>,
45
+ DeleteResult = DefaultDeleteSessionResult,
46
+ SessionData extends StringRecord = StringRecord,
47
+ >
44
48
  extends DriverCore<C, Settings>
45
49
  implements Driver<C, CArgs, Settings, CreateResult, DeleteResult, SessionData>
46
50
  {
47
51
  cliArgs: CArgs & ServerArgs;
48
52
  caps: DriverCaps<C>;
49
- originalCaps: W3CDriverCaps<C>;
50
- desiredCapConstraints: C;
53
+ originalCaps!: W3CDriverCaps<C>;
54
+ desiredCapConstraints!: C;
51
55
  server?: AppiumServer;
52
56
  serverHost?: string;
53
57
  serverPort?: number;
@@ -69,7 +73,11 @@ export class BaseDriver<
69
73
  */
70
74
  protected get _desiredCapConstraints(): Readonly<BaseDriverCapConstraints & C> {
71
75
  return Object.freeze(
72
- mergePlainObjects({}, BASE_DESIRED_CAP_CONSTRAINTS, this.desiredCapConstraints) as BaseDriverCapConstraints & C
76
+ mergePlainObjects(
77
+ {},
78
+ BASE_DESIRED_CAP_CONSTRAINTS,
79
+ this.desiredCapConstraints,
80
+ ) as BaseDriverCapConstraints & C,
73
81
  );
74
82
  }
75
83
 
@@ -99,8 +107,10 @@ export class BaseDriver<
99
107
  throw new errors.NoSuchDriverError('The driver was unexpectedly shut down!');
100
108
  }
101
109
 
110
+ const invoker = this as unknown as CommandInvoker<C>;
111
+ const command = invoker[cmd];
102
112
  // If we don't have this command, it must not be implemented
103
- if (!this[cmd]) {
113
+ if (!command) {
104
114
  await this.startNewCommandTimeout();
105
115
  throw new errors.NotYetImplementedError();
106
116
  }
@@ -115,14 +125,14 @@ export class BaseDriver<
115
125
  };
116
126
  try {
117
127
  return await Promise.race([
118
- this[cmd](...args),
128
+ command.call(this, ...args),
119
129
  // This promise is needed to monitor if the session has been
120
130
  // shut down unexpectedly while the command was running
121
131
  new Promise((resolve, reject) => {
122
132
  unexpectedShutdownResolver = resolve;
123
133
  unexpectedShutdownRejecter = reject;
124
134
  this.eventEmitter.once(ON_UNEXPECTED_SHUTDOWN_EVENT, onUnexpectedShutdown);
125
- })
135
+ }),
126
136
  ]);
127
137
  } finally {
128
138
  if (unexpectedShutdownRejecter && unexpectedShutdownResolver) {
@@ -139,7 +149,11 @@ export class BaseDriver<
139
149
  // automatic session deletion in this.onCommandTimeout. Of course we don't
140
150
  // want to trigger the timer when the user is shutting down the session
141
151
  // intentionally
142
- if (!wasSessionShutdownUnexpectedly && this.isCommandsQueueEnabled && cmd !== DELETE_SESSION_COMMAND) {
152
+ if (
153
+ !wasSessionShutdownUnexpectedly &&
154
+ this.isCommandsQueueEnabled &&
155
+ cmd !== DELETE_SESSION_COMMAND
156
+ ) {
143
157
  // resetting existing timeout
144
158
  await this.startNewCommandTimeout();
145
159
  }
@@ -147,13 +161,15 @@ export class BaseDriver<
147
161
  };
148
162
 
149
163
  const synchronizationKey = BaseDriver.name;
150
- // eslint-disable-next-line dot-notation
151
- const commandsQueueLen: number = this.commandsQueueGuard['queues']?.[synchronizationKey]?.length ?? 0;
164
+ const commandsQueueGuard = this.commandsQueueGuard as AsyncLock & {
165
+ queues?: Record<string, unknown[]>;
166
+ };
167
+ const commandsQueueLen: number = commandsQueueGuard.queues?.[synchronizationKey]?.length ?? 0;
152
168
  if (this.isCommandsQueueEnabled && commandsQueueLen > 0) {
153
169
  this.log.debug(
154
170
  `Scheduling the '${cmd}' command to the ${this.constructor.name} commands queue. ` +
155
- `${util.pluralize('queue item', commandsQueueLen, true)} ${commandsQueueLen === 1 ? 'is' : 'are'} ` +
156
- `already waiting for execution.`
171
+ `${util.pluralize('queue item', commandsQueueLen, true)} ${commandsQueueLen === 1 ? 'is' : 'are'} ` +
172
+ `already waiting for execution.`,
157
173
  );
158
174
  }
159
175
 
@@ -182,7 +198,7 @@ export class BaseDriver<
182
198
  if (cmd === 'execute') {
183
199
  const firstArg = args?.[0];
184
200
  if (typeof firstArg === 'string' && firstArg.trim().length > 0) {
185
- return resolveExecuteExtensionName.call(this, firstArg);
201
+ return resolveExecuteExtensionName.call(this as BaseDriver<Constraints>, firstArg);
186
202
  }
187
203
  }
188
204
 
@@ -240,15 +256,12 @@ export class BaseDriver<
240
256
  this.log.debug('Running generic full reset');
241
257
 
242
258
  // preserving state
243
- const currentConfig = {};
244
- for (const property of [
245
- 'implicitWaitMs',
246
- 'newCommandTimeoutMs',
247
- 'sessionId',
248
- 'resetOnUnexpectedShutdown',
249
- ]) {
250
- currentConfig[property] = this[property];
251
- }
259
+ const currentConfig = {
260
+ implicitWaitMs: this.implicitWaitMs,
261
+ newCommandTimeoutMs: this.newCommandTimeoutMs,
262
+ sessionId: this.sessionId,
263
+ shutdownUnexpectedly: this.shutdownUnexpectedly,
264
+ };
252
265
 
253
266
  try {
254
267
  if (this.sessionId !== null) {
@@ -258,9 +271,7 @@ export class BaseDriver<
258
271
  await this.createSession(this.originalCaps);
259
272
  } finally {
260
273
  // always restore state.
261
- for (const [key, value] of Object.entries(currentConfig)) {
262
- this[key] = value;
263
- }
274
+ Object.assign(this, currentConfig);
264
275
  }
265
276
  await this.clearNewCommandTimeout();
266
277
  }
@@ -313,7 +324,8 @@ export class BaseDriver<
313
324
  ) as DriverCaps<C>;
314
325
  caps = fixCaps(caps, this._desiredCapConstraints, this.log) as DriverCaps<C>;
315
326
  } catch (e) {
316
- throw new errors.SessionNotCreatedError(e.message);
327
+ const message = e instanceof Error ? e.message : String(e);
328
+ throw new errors.SessionNotCreatedError(message);
317
329
  }
318
330
 
319
331
  this.validateDesiredCaps(caps);
@@ -404,11 +416,7 @@ export class BaseDriver<
404
416
  this.log.warn(`The following provided capabilities were not recognized by this driver:`);
405
417
  for (const cap of extraCaps) {
406
418
  const suggestion = getLevenshteinSuggestion(cap, knownCaps);
407
- this.log.warn(
408
- suggestion
409
- ? ` ${cap} (did you mean '${suggestion}'?)`
410
- : ` ${cap}`,
411
- );
419
+ this.log.warn(suggestion ? ` ${cap} (did you mean '${suggestion}'?)` : ` ${cap}`);
412
420
  }
413
421
  }
414
422
  }
@@ -421,11 +429,13 @@ export class BaseDriver<
421
429
  try {
422
430
  validateCaps(caps, this._desiredCapConstraints);
423
431
  } catch (e) {
432
+ const capError = e instanceof Error ? e : new Error(String(e));
424
433
  throw this.log.errorWithException(
425
434
  new errors.SessionNotCreatedError(
426
435
  `Session capabilities were not valid for the ` +
427
- `following reason(s): ${e.message}`, e
428
- )
436
+ `following reason(s): ${capError.message}`,
437
+ capError,
438
+ ),
429
439
  );
430
440
  }
431
441
 
@@ -9,9 +9,7 @@ import type {
9
9
  IpcData,
10
10
  StringRecord,
11
11
  } from '@appium/types';
12
- import {
13
- MAX_LOG_BODY_LENGTH,
14
- } from '../constants';
12
+ import {MAX_LOG_BODY_LENGTH} from '../constants';
15
13
  import {errors} from '../protocol';
16
14
  import {BIDI_COMMANDS} from '../protocol/bidi-commands';
17
15
  import {generateDriverLogPrefix} from './helpers';
@@ -22,10 +20,9 @@ export class ExtensionCore {
22
20
  _logPrefix?: string;
23
21
  // used to handle driver events
24
22
  readonly eventEmitter: NodeJS.EventEmitter;
25
- protected _log: AppiumLogger;
23
+ protected _log!: AppiumLogger;
26
24
  private ipc?: IAppiumIpc;
27
25
 
28
-
29
26
  constructor(logPrefix?: string) {
30
27
  this._logPrefix = logPrefix;
31
28
  this.bidiEventSubs = {};
@@ -46,7 +43,9 @@ export class ExtensionCore {
46
43
  updateBidiCommands(cmds: BidiModuleMap): void {
47
44
  const overlappingKeys = Object.keys(cmds).filter((key) => key in this.bidiCommands);
48
45
  if (overlappingKeys.length) {
49
- this.log.warn(`Overwriting existing bidi modules: ${JSON.stringify(overlappingKeys)}. This may not be intended!`);
46
+ this.log.warn(
47
+ `Overwriting existing bidi modules: ${JSON.stringify(overlappingKeys)}. This may not be intended!`,
48
+ );
50
49
  }
51
50
  this.bidiCommands = {
52
51
  ...this.bidiCommands,
@@ -74,7 +73,7 @@ export class ExtensionCore {
74
73
  }
75
74
 
76
75
  // if the command module or method isn't part of our spec, reject
77
- if (!(this.bidiCommands[moduleName]?.[methodName])) {
76
+ if (!this.bidiCommands[moduleName]?.[methodName]) {
78
77
  throw new errors.UnknownCommandError();
79
78
  }
80
79
 
@@ -85,13 +84,19 @@ export class ExtensionCore {
85
84
  }
86
85
 
87
86
  // If the driver doesn't have this command, it must not be implemented
88
- if (!this[command]) {
87
+ const handler = (this as ExtensionCore & Record<string, unknown>)[command];
88
+ if (typeof handler !== 'function') {
89
89
  throw new errors.NotYetImplementedError();
90
90
  }
91
91
  }
92
92
 
93
- async executeBidiCommand(bidiCmd: string, bidiParams: StringRecord, next?: () => Promise<any>, driver?: ExtensionCore): Promise<BiDiResultData> {
94
- const handlerType = (next && driver) ? 'plugin' : 'driver';
93
+ async executeBidiCommand(
94
+ bidiCmd: string,
95
+ bidiParams: StringRecord,
96
+ next?: () => Promise<any>,
97
+ driver?: ExtensionCore,
98
+ ): Promise<BiDiResultData> {
99
+ const handlerType = next && driver ? 'plugin' : 'driver';
95
100
  const [moduleName, methodName] = bidiCmd.split('.');
96
101
  this.ensureBidiCommandExists(moduleName, methodName);
97
102
  const {command, params} = this.bidiCommands[moduleName][methodName];
@@ -114,17 +119,26 @@ export class ExtensionCore {
114
119
  args.push(bidiParams[optionalParam]);
115
120
  }
116
121
  }
117
- const logParams = util.truncateString(JSON.stringify(bidiParams), {length: MAX_LOG_BODY_LENGTH});
122
+ const logParams = util.truncateString(JSON.stringify(bidiParams), {
123
+ length: MAX_LOG_BODY_LENGTH,
124
+ });
118
125
  this.log.debug(
119
126
  `Executing bidi command '${bidiCmd}' with params ${logParams} by passing to ${handlerType} ` +
120
127
  `method '${command}'`,
121
128
  );
122
129
  // call the handler with the signature appropriate to extension type (plugin or driver)
123
- const response = (next && driver) ? await this[command](next, driver, ...args) : await this[command](...args);
124
- const finalResponse = response === undefined ? {} : response;
130
+ const commandHandler = (
131
+ this as unknown as Record<string, (...handlerArgs: any[]) => Promise<unknown>>
132
+ )[command];
133
+ const response =
134
+ next && driver
135
+ ? await commandHandler.call(this, next, driver, ...args)
136
+ : await commandHandler.call(this, ...args);
137
+ const finalResponse: BiDiResultData =
138
+ response === undefined ? {} : (response as BiDiResultData);
125
139
  this.log.debug(
126
140
  `Responding to bidi command '${bidiCmd}' with ` +
127
- `${util.truncateString(JSON.stringify(finalResponse), {length: MAX_LOG_BODY_LENGTH})}`
141
+ `${util.truncateString(JSON.stringify(finalResponse), {length: MAX_LOG_BODY_LENGTH})}`,
128
142
  );
129
143
  return finalResponse;
130
144
  }
@@ -145,9 +159,11 @@ export class ExtensionCore {
145
159
 
146
160
  ipcSubscribe<T extends IpcData>(topic: string): IIpcSubscription<T> {
147
161
  if (!this.ipc) {
148
- throw new Error(`Cannot subscribe to an IPC topic without an IPC object assigned. ` +
149
- `This is likely a programming error. ipcSubscribe should be called in the ` +
150
- `onIpcInit handler or after you are certain that createSession has completed successfully.`);
162
+ throw new Error(
163
+ `Cannot subscribe to an IPC topic without an IPC object assigned. ` +
164
+ `This is likely a programming error. ipcSubscribe should be called in the ` +
165
+ `onIpcInit handler or after you are certain that createSession has completed successfully.`,
166
+ );
151
167
  }
152
168
  return this.ipc.subscribe<T>(topic, generateDriverLogPrefix(this));
153
169
  }
@@ -1,3 +1,4 @@
1
+ import nodeFs from 'node:fs';
1
2
  import path from 'node:path';
2
3
  import {log as logger} from './logger';
3
4
  import {tempDir, fs, util, timing, node} from '@appium/support';
@@ -13,14 +14,15 @@ import type {
13
14
  } from '@appium/types';
14
15
  import type {AxiosResponseHeaders, RawAxiosRequestHeaders} from 'axios';
15
16
  import type {Readable} from 'node:stream';
17
+ import type {PackageJson} from 'type-fest';
16
18
 
17
19
  // for compat with running tests transpiled and in-place
18
- export const {version: BASEDRIVER_VER} = fs.readPackageJsonFrom(__dirname);
20
+ export const BASEDRIVER_VER = readBaseDriverVersion();
19
21
 
20
22
  const CACHED_APPS_MAX_AGE_MS = 1000 * 60 * toNaturalNumber(60 * 24, 'APPIUM_APPS_CACHE_MAX_AGE');
21
23
  const MAX_CACHED_APPS = toNaturalNumber(1024, 'APPIUM_APPS_CACHE_MAX_ITEMS');
22
24
  const HTTP_STATUS_NOT_MODIFIED = 304;
23
- const DEFAULT_REQ_HEADERS = Object.freeze({
25
+ const DEFAULT_REQ_HEADERS: RawAxiosRequestHeaders = Object.freeze({
24
26
  'user-agent': `Appium (BaseDriver v${BASEDRIVER_VER})`,
25
27
  });
26
28
  const AVG_DOWNLOAD_SPEED_MEASUREMENT_THRESHOLD_SEC = 2;
@@ -31,7 +33,7 @@ const APPLICATIONS_CACHE = new LRUCache<string, CachedAppInfoEntry>({
31
33
  dispose: ({fullPath}, app) => {
32
34
  logger.info(
33
35
  `The application '${app}' cached at '${fullPath}' has ` +
34
- `expired after ${CACHED_APPS_MAX_AGE_MS}ms`
36
+ `expired after ${CACHED_APPS_MAX_AGE_MS}ms`,
35
37
  );
36
38
  if (fullPath) {
37
39
  void fs.rimraf(fullPath);
@@ -51,7 +53,7 @@ process.on('exit', () => {
51
53
 
52
54
  const appPaths = [...APPLICATIONS_CACHE.values()].map(({fullPath}) => fullPath);
53
55
  logger.debug(
54
- `Performing cleanup of ${util.pluralize('cached application', appPaths.length, true)}`
56
+ `Performing cleanup of ${util.pluralize('cached application', appPaths.length, true)}`,
55
57
  );
56
58
  for (const appPath of appPaths) {
57
59
  if (!appPath) {
@@ -98,7 +100,7 @@ interface CachedAppInfoEntry extends Omit<CachedAppInfo, 'packageHash'> {
98
100
  */
99
101
  export async function configureApp(
100
102
  app: string,
101
- options: string | string[] | ConfigureAppOptions = {} as ConfigureAppOptions
103
+ options: string | string[] | ConfigureAppOptions = {} as ConfigureAppOptions,
102
104
  ): Promise<string> {
103
105
  if (typeof app !== 'string') {
104
106
  // immediately shortcircuit if not given an app
@@ -140,7 +142,7 @@ export async function configureApp(
140
142
  newApp = path.resolve(process.cwd(), newApp);
141
143
  logger.warn(
142
144
  `The current application path '${app}' is not absolute ` +
143
- `and has been rewritten to '${newApp}'. Consider using absolute paths rather than relative`
145
+ `and has been rewritten to '${newApp}'. Consider using absolute paths rather than relative`,
144
146
  );
145
147
  app = newApp;
146
148
  }
@@ -194,7 +196,7 @@ export async function configureApp(
194
196
  }
195
197
  logger.info(
196
198
  `The application at '${cachedAppInfo.fullPath}' does not exist anymore ` +
197
- `or its integrity has been damaged. Deleting it from the internal cache`
199
+ `or its integrity has been damaged. Deleting it from the internal cache`,
198
200
  );
199
201
  APPLICATIONS_CACHE.delete(appCacheKey);
200
202
 
@@ -215,10 +217,13 @@ export async function configureApp(
215
217
  });
216
218
  } else {
217
219
  const fileName = determineFilename(headers, pathname ?? '', supportedAppExtensions);
218
- newApp = await fetchApp(stream, await tempDir.path({
219
- prefix: fileName,
220
- suffix: '',
221
- }));
220
+ newApp = await fetchApp(
221
+ stream,
222
+ await tempDir.path({
223
+ prefix: fileName,
224
+ suffix: '',
225
+ }),
226
+ );
222
227
  }
223
228
  } finally {
224
229
  if (!stream.closed) {
@@ -280,7 +285,8 @@ export async function configureApp(
280
285
  }
281
286
 
282
287
  verifyAppExtension(newApp, supportedAppExtensions);
283
- return appCacheKey !== toCacheKey(newApp) && (packageHash || Object.values(remoteAppProps).some(Boolean))
288
+ return appCacheKey !== toCacheKey(newApp) &&
289
+ (packageHash || Object.values(remoteAppProps).some(Boolean))
284
290
  ? await storeAppInCache(newApp)
285
291
  : newApp;
286
292
  });
@@ -380,7 +386,9 @@ export function generateDriverLogPrefix(obj: object | null, _sessionId?: string
380
386
 
381
387
  // #region Private helpers
382
388
 
383
- function parseAppLink(appLink: string): URL | {protocol?: string; pathname?: string; href?: string; search?: string} {
389
+ function parseAppLink(
390
+ appLink: string,
391
+ ): URL | {protocol?: string; pathname?: string; href?: string; search?: string} {
384
392
  try {
385
393
  return new URL(appLink);
386
394
  } catch {
@@ -430,7 +438,10 @@ function toCacheKey(app: string): string {
430
438
  return app;
431
439
  }
432
440
 
433
- async function queryAppLink(appLink: string, reqHeaders: RawAxiosRequestHeaders): Promise<RemoteAppData> {
441
+ async function queryAppLink(
442
+ appLink: string,
443
+ reqHeaders: RawAxiosRequestHeaders,
444
+ ): Promise<RemoteAppData> {
434
445
  const url = new URL(appLink);
435
446
  // Extract credentials, then remove them from the URL for axios
436
447
  const {username, password} = url;
@@ -451,10 +462,9 @@ async function queryAppLink(appLink: string, reqHeaders: RawAxiosRequestHeaders)
451
462
  const {data: stream, headers, status} = await axios(requestOpts);
452
463
  return {stream, headers, status};
453
464
  } catch (err) {
454
- throw new Error(
455
- `Cannot download the app from ${axiosUrl}: ${(err as Error).message}`,
456
- {cause: err}
457
- );
465
+ throw new Error(`Cannot download the app from ${axiosUrl}: ${(err as Error).message}`, {
466
+ cause: err,
467
+ });
458
468
  }
459
469
  }
460
470
 
@@ -480,7 +490,7 @@ async function fetchApp(srcStream: Readable, dstPath: string): Promise<string> {
480
490
  const {size} = await fs.stat(dstPath);
481
491
  logger.debug(
482
492
  `The application (${util.toReadableSizeString(size)}) ` +
483
- `has been downloaded to '${dstPath}' in ${secondsElapsed.toFixed(3)}s`
493
+ `has been downloaded to '${dstPath}' in ${secondsElapsed.toFixed(3)}s`,
484
494
  );
485
495
  // it does not make much sense to approximate the speed for short downloads
486
496
  if (secondsElapsed >= AVG_DOWNLOAD_SPEED_MEASUREMENT_THRESHOLD_SEC) {
@@ -494,13 +504,16 @@ async function fetchApp(srcStream: Readable, dstPath: string): Promise<string> {
494
504
  function determineFilename(
495
505
  headers: AxiosResponseHeaders | RawAxiosRequestHeaders,
496
506
  pathname: string,
497
- supportedAppExtensions: string[]
507
+ supportedAppExtensions: string[],
498
508
  ): string {
499
509
  const basename = fs.sanitizeName(path.basename(decodeURIComponent(pathname ?? '')), {
500
510
  replacement: SANITIZE_REPLACEMENT,
501
511
  });
502
512
  const extname = path.extname(basename);
503
- if (headers['content-disposition'] && /^attachment/i.test(String(headers['content-disposition']))) {
513
+ if (
514
+ headers['content-disposition'] &&
515
+ /^attachment/i.test(String(headers['content-disposition']))
516
+ ) {
504
517
  logger.debug(`Content-Disposition: ${headers['content-disposition']}`);
505
518
  const match = /filename="([^"]+)/i.exec(String(headers['content-disposition']));
506
519
  if (match) {
@@ -513,10 +526,12 @@ function determineFilename(
513
526
  ? basename.substring(0, basename.length - extname.length)
514
527
  : DEFAULT_BASENAME;
515
528
  let resultingExt = extname;
516
- if (!supportedAppExtensions.map((ext) => ext.toLowerCase()).includes(resultingExt.toLowerCase())) {
529
+ if (
530
+ !supportedAppExtensions.map((ext) => ext.toLowerCase()).includes(resultingExt.toLowerCase())
531
+ ) {
517
532
  logger.info(
518
533
  `The current file extension '${resultingExt}' is not supported. ` +
519
- `Defaulting to '${supportedAppExtensions[0]}'`
534
+ `Defaulting to '${supportedAppExtensions[0]}'`,
520
535
  );
521
536
  resultingExt = supportedAppExtensions[0] as string;
522
537
  }
@@ -524,13 +539,15 @@ function determineFilename(
524
539
  }
525
540
 
526
541
  function verifyAppExtension(app: string, supportedAppExtensions: string[]): string {
527
- if (supportedAppExtensions.map((ext) => ext.toLowerCase()).includes(path.extname(app).toLowerCase())) {
542
+ if (
543
+ supportedAppExtensions.map((ext) => ext.toLowerCase()).includes(path.extname(app).toLowerCase())
544
+ ) {
528
545
  return app;
529
546
  }
530
547
  throw new Error(
531
548
  `New app path '${app}' did not have ` +
532
549
  `${util.pluralize('extension', supportedAppExtensions.length, false)}: ` +
533
- supportedAppExtensions
550
+ supportedAppExtensions,
534
551
  );
535
552
  }
536
553
 
@@ -544,7 +561,7 @@ async function calculateFileIntegrity(filePath: string): Promise<string> {
544
561
 
545
562
  async function isAppIntegrityOk(
546
563
  currentPath: string,
547
- expectedIntegrity: {file?: string; folder?: number} = {}
564
+ expectedIntegrity: {file?: string; folder?: number} = {},
548
565
  ): Promise<boolean> {
549
566
  if (!(await fs.exists(currentPath))) {
550
567
  return false;
@@ -570,6 +587,20 @@ function toNaturalNumber(defaultValue: number, envVarName?: string): number {
570
587
  return num > 0 ? num : defaultValue;
571
588
  }
572
589
 
590
+ function readBaseDriverVersion(): string {
591
+ const pkgRoot = node.getModuleRootSync('@appium/base-driver', __filename);
592
+ if (!pkgRoot) {
593
+ throw new Error('Cannot find the @appium/base-driver package root');
594
+ }
595
+ const pkg = JSON.parse(
596
+ nodeFs.readFileSync(path.join(pkgRoot, 'package.json'), 'utf8'),
597
+ ) as PackageJson;
598
+ if (typeof pkg.version !== 'string') {
599
+ throw new Error('Invalid `package.json` for @appium/base-driver');
600
+ }
601
+ return pkg.version;
602
+ }
603
+
573
604
  export default {
574
605
  configureApp,
575
606
  isPackageOrBundle,