@appium/base-driver 10.0.0-beta.2 → 10.0.0-rc.2

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 (53) 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 +1 -1
  4. package/build/lib/basedriver/capabilities.js.map +1 -1
  5. package/build/lib/basedriver/commands/timeout.js +2 -2
  6. package/build/lib/basedriver/commands/timeout.js.map +1 -1
  7. package/build/lib/basedriver/core.d.ts +2 -1
  8. package/build/lib/basedriver/core.d.ts.map +1 -1
  9. package/build/lib/basedriver/core.js +2 -2
  10. package/build/lib/basedriver/core.js.map +1 -1
  11. package/build/lib/basedriver/driver.d.ts +3 -3
  12. package/build/lib/basedriver/driver.d.ts.map +1 -1
  13. package/build/lib/basedriver/driver.js +15 -11
  14. package/build/lib/basedriver/driver.js.map +1 -1
  15. package/build/lib/basedriver/extension-core.js +1 -1
  16. package/build/lib/basedriver/extension-core.js.map +1 -1
  17. package/build/lib/basedriver/helpers.js +7 -7
  18. package/build/lib/basedriver/helpers.js.map +1 -1
  19. package/build/lib/express/express-logging.d.ts.map +1 -1
  20. package/build/lib/express/express-logging.js +2 -1
  21. package/build/lib/express/express-logging.js.map +1 -1
  22. package/build/lib/express/server.js +2 -2
  23. package/build/lib/express/server.js.map +1 -1
  24. package/build/lib/helpers/extension-command-name.d.ts +11 -0
  25. package/build/lib/helpers/extension-command-name.d.ts.map +1 -0
  26. package/build/lib/helpers/extension-command-name.js +26 -0
  27. package/build/lib/helpers/extension-command-name.js.map +1 -0
  28. package/build/lib/jsonwp-proxy/protocol-converter.d.ts.map +1 -1
  29. package/build/lib/jsonwp-proxy/protocol-converter.js +7 -6
  30. package/build/lib/jsonwp-proxy/protocol-converter.js.map +1 -1
  31. package/build/lib/jsonwp-proxy/proxy.d.ts.map +1 -1
  32. package/build/lib/jsonwp-proxy/proxy.js +5 -5
  33. package/build/lib/jsonwp-proxy/proxy.js.map +1 -1
  34. package/build/lib/protocol/protocol.js +4 -6
  35. package/build/lib/protocol/protocol.js.map +1 -1
  36. package/build/lib/protocol/routes.d.ts +2 -0
  37. package/build/lib/protocol/routes.d.ts.map +1 -1
  38. package/build/lib/protocol/routes.js +10 -3
  39. package/build/lib/protocol/routes.js.map +1 -1
  40. package/lib/basedriver/capabilities.ts +2 -2
  41. package/lib/basedriver/commands/timeout.ts +2 -2
  42. package/lib/basedriver/core.ts +4 -2
  43. package/lib/basedriver/driver.ts +19 -14
  44. package/lib/basedriver/extension-core.ts +1 -1
  45. package/lib/basedriver/helpers.js +8 -8
  46. package/lib/express/express-logging.js +2 -1
  47. package/lib/express/server.js +2 -2
  48. package/lib/helpers/extension-command-name.ts +27 -0
  49. package/lib/jsonwp-proxy/protocol-converter.js +7 -8
  50. package/lib/jsonwp-proxy/proxy.js +9 -5
  51. package/lib/protocol/protocol.ts +6 -6
  52. package/lib/protocol/routes.js +10 -3
  53. package/package.json +13 -13
@@ -9,7 +9,6 @@ import {
9
9
  type Driver,
10
10
  type DriverCaps,
11
11
  type DriverData,
12
- type MultiSessionData,
13
12
  type ServerArgs,
14
13
  type StringRecord,
15
14
  type W3CDriverCaps,
@@ -26,6 +25,7 @@ import {DELETE_SESSION_COMMAND, determineProtocol, errors} from '../protocol';
26
25
  import {processCapabilities, validateCaps} from './capabilities';
27
26
  import {DriverCore} from './core';
28
27
  import * as helpers from './helpers';
28
+ import {resolveExecuteExtensionName} from '../helpers/extension-command-name';
29
29
 
30
30
  const EVENT_SESSION_INIT = 'newSessionRequested';
31
31
  const EVENT_SESSION_START = 'newSessionStarted';
@@ -162,6 +162,11 @@ export class BaseDriver<
162
162
 
163
163
  // log timing information about this command
164
164
  const endTime = Date.now();
165
+
166
+ if (this.clarifyCommandName) {
167
+ cmd = this.clarifyCommandName(cmd, args);
168
+ }
169
+
165
170
  this._eventHistory.commands.push({cmd, startTime, endTime});
166
171
  if (cmd === 'createSession') {
167
172
  this.logEvent(EVENT_SESSION_START);
@@ -172,6 +177,17 @@ export class BaseDriver<
172
177
  return res;
173
178
  }
174
179
 
180
+ clarifyCommandName(cmd: string, args: string[]): string {
181
+ if (cmd === 'execute') {
182
+ const firstArg = args?.[0];
183
+ if (_.isString(firstArg) && firstArg.trim().length > 0) {
184
+ return resolveExecuteExtensionName.call(this, firstArg);
185
+ }
186
+ }
187
+
188
+ return cmd;
189
+ }
190
+
175
191
  async startUnexpectedShutdown(
176
192
  err: Error = new errors.NoSuchDriverError('The driver was unexpectedly shut down!'),
177
193
  ) {
@@ -302,6 +318,7 @@ export class BaseDriver<
302
318
  this.validateDesiredCaps(caps);
303
319
 
304
320
  this.sessionId = util.uuidV4();
321
+ this.sessionCreationTimestampMs = Date.now();
305
322
  this.caps = caps;
306
323
  // merge caps onto opts so we don't need to worry about what's where
307
324
  this.opts = {..._.cloneDeep(this.initialOpts), ...this.caps};
@@ -345,22 +362,10 @@ export class BaseDriver<
345
362
 
346
363
  return [this.sessionId, caps] as CreateResult;
347
364
  }
348
- async getSessions() {
349
- const ret: MultiSessionData<C>[] = [];
350
-
351
- if (this.sessionId) {
352
- ret.push({
353
- id: this.sessionId,
354
- capabilities: this.caps,
355
- });
356
- }
357
-
358
- return ret;
359
- }
360
365
 
361
366
  /**
362
367
  * Returns capabilities for the session and event history (if applicable)
363
- * @deprecated Use {@linkcode ISessionCommands.getAppiumSessionCapabilities} instead for getting the capabilities.
368
+ * @deprecated Use {@linkcode getAppiumSessionCapabilities} instead for getting the capabilities.
364
369
  * Use {@linkcode EventCommands.getLogEvents} instead to get the event history.
365
370
  */
366
371
  async getSession() {
@@ -71,7 +71,7 @@ export class ExtensionCore {
71
71
  }
72
72
 
73
73
  // if the command module or method isn't part of our spec, reject
74
- if (!this.bidiCommands[moduleName] || !this.bidiCommands[moduleName][methodName]) {
74
+ if (!(this.bidiCommands[moduleName]?.[methodName])) {
75
75
  throw new errors.UnknownCommandError();
76
76
  }
77
77
 
@@ -101,7 +101,7 @@ export async function configureApp(
101
101
  const originalAppLink = app;
102
102
  let packageHash = null;
103
103
  /** @type {import('axios').AxiosResponse['headers']|undefined} */
104
- let headers = undefined;
104
+ let headers;
105
105
  /** @type {RemoteAppProps} */
106
106
  const remoteAppProps = {
107
107
  lastModified: null,
@@ -121,12 +121,12 @@ export async function configureApp(
121
121
  }
122
122
  const appCacheKey = toCacheKey(app);
123
123
 
124
- const cachedAppInfo = APPLICATIONS_CACHE.get(appCacheKey);
125
- if (cachedAppInfo) {
126
- logger.debug(`Cached app data: ${JSON.stringify(cachedAppInfo, null, 2)}`);
127
- }
128
-
129
124
  return await APPLICATIONS_CACHE_GUARD.acquire(appCacheKey, async () => {
125
+ const cachedAppInfo = APPLICATIONS_CACHE.get(appCacheKey);
126
+ if (cachedAppInfo) {
127
+ logger.debug(`Cached app data: ${JSON.stringify(cachedAppInfo, null, 2)}`);
128
+ }
129
+
130
130
  if (isUrl) {
131
131
  // Use the app from remote URL
132
132
  logger.info(`Using downloadable app '${newApp}'`);
@@ -434,7 +434,7 @@ async function fetchApp(srcStream, dstPath) {
434
434
  *
435
435
  * @param {string} app App link.
436
436
  * @returns {string} Transformed app link or the original arg if
437
- * no transfromation is needed.
437
+ * no transformation is needed.
438
438
  */
439
439
  function toCacheKey(app) {
440
440
  if (!isEnvOptionEnabled('APPIUM_APPS_CACHE_IGNORE_URL_QUERY') || !isSupportedUrl(app)) {
@@ -529,7 +529,7 @@ function isSupportedUrl(app) {
529
529
  *
530
530
  * @param {string} optionName Option name
531
531
  * @param {boolean|null} [defaultValue=null] The value to return if the given env value
532
- * is not set explcitly
532
+ * is not set explicitly
533
533
  * @returns {boolean} True if the option is enabled
534
534
  */
535
535
  function isEnvOptionEnabled(optionName, defaultValue = null) {
@@ -3,6 +3,7 @@ import '@colors/colors';
3
3
  import morgan from 'morgan';
4
4
  import log from './logger';
5
5
  import {MAX_LOG_BODY_LENGTH} from '../constants';
6
+ import {logger} from '@appium/support';
6
7
 
7
8
  // Copied the morgan compile function over so that cooler formats
8
9
  // may be configured
@@ -51,7 +52,7 @@ const startLogFormatter = morgan(
51
52
  });
52
53
  } catch {}
53
54
  }
54
- log.info(requestStartLoggingFormat(tokens, req, res), reqBody.grey);
55
+ log.info(requestStartLoggingFormat(tokens, req, res), logger.markSensitive(reqBody.grey));
55
56
  },
56
57
  {immediate: true}
57
58
  );
@@ -231,7 +231,7 @@ function configureHttp({httpServer, reject, keepAliveTimeout, gracefulShutdownTi
231
231
  }, gracefulShutdownTimeout);
232
232
  httpServer.once('close', () => {
233
233
  log.info(
234
- `Appium HTTP server has been succesfully closed after ` +
234
+ `Appium HTTP server has been successfully closed after ` +
235
235
  `${timer.getDuration().asMilliSeconds.toFixed(0)}ms`
236
236
  );
237
237
  clearTimeout(onTimeout);
@@ -309,7 +309,7 @@ export function normalizeBasePath(basePath) {
309
309
 
310
310
  // likewise, ensure the path prefix does always START with /, unless the path
311
311
  // is empty meaning no base path at all
312
- if (basePath !== '' && basePath[0] !== '/') {
312
+ if (basePath !== '' && !basePath.startsWith('/')) {
313
313
  basePath = `/${basePath}`;
314
314
  }
315
315
 
@@ -0,0 +1,27 @@
1
+ import _ from 'lodash';
2
+ import type {Constraints, Driver, DriverClass} from '@appium/types';
3
+ import type {BaseDriver} from '../basedriver/driver';
4
+
5
+ /**
6
+ * Resolves the name of extension method corresponding to an `execute` command string
7
+ * based on the driver's `executeMethodMap`.
8
+ *
9
+ * @param commandName - The command name to resolve.
10
+ * @returns The resolved extension command name if a mapping exists. Otherwise, the original command name.
11
+ */
12
+ export function resolveExecuteExtensionName<C extends Constraints>(
13
+ this: BaseDriver<C>,
14
+ commandName: string
15
+ ): string {
16
+ const Driver = this.constructor as DriverClass<Driver<C>>;
17
+ const methodMap = Driver.executeMethodMap;
18
+
19
+ if (methodMap && _.isPlainObject(methodMap) && commandName in methodMap) {
20
+ const command = methodMap[commandName]?.command;
21
+ if (typeof command === 'string') {
22
+ return command;
23
+ }
24
+ }
25
+
26
+ return commandName;
27
+ }
@@ -19,12 +19,12 @@ export const COMMAND_URLS_CONFLICTS = [
19
19
  {
20
20
  commandNames: ['getWindowHandles', 'getWindowHandle'],
21
21
  jsonwpConverter(url) {
22
- return /\/window$/.test(url)
22
+ return url.endsWith('/window')
23
23
  ? url.replace(/\/window$/, '/window_handle')
24
24
  : url.replace(/\/window\/handle(s?)$/, '/window_handle$1');
25
25
  },
26
26
  w3cConverter(url) {
27
- return /\/window_handle$/.test(url)
27
+ return url.endsWith('/window_handle')
28
28
  ? url.replace(/\/window_handle$/, '/window')
29
29
  : url.replace(/\/window_handles$/, '/window/handles');
30
30
  },
@@ -186,20 +186,19 @@ class ProtocolConverter {
186
186
  let {text, value} = bodyObj;
187
187
  if (util.hasValue(text) && !util.hasValue(value)) {
188
188
  value = _.isString(text) ? [...text] : _.isArray(text) ? text : [];
189
- this.log.debug(
190
- `Added 'value' property ${JSON.stringify(value)} to 'setValue' request body`
191
- );
189
+ this.log.debug(`Added 'value' property to 'setValue' request body`);
192
190
  } else if (!util.hasValue(text) && util.hasValue(value)) {
193
191
  text = _.isArray(value) ? value.join('') : _.isString(value) ? value : '';
194
- this.log.debug(`Added 'text' property ${JSON.stringify(text)} to 'setValue' request body`);
192
+ this.log.debug(`Added 'text' property to 'setValue' request body`);
195
193
  }
196
194
  return await this.proxyFunc(
197
195
  url,
198
196
  method,
199
- Object.assign({}, bodyObj, {
197
+ {
198
+ ...bodyObj,
200
199
  text,
201
200
  value,
202
- })
201
+ }
203
202
  );
204
203
  }
205
204
 
@@ -203,8 +203,11 @@ export class JWProxy {
203
203
  if (typeof body !== 'object') {
204
204
  try {
205
205
  reqOpts.data = JSON.parse(body);
206
- } catch {
207
- throw new Error(`Cannot interpret the request body as valid JSON: ${truncateBody(body)}`);
206
+ } catch (error) {
207
+ this.log.warn('Invalid body payload (%s): %s', error.message, logger.markSensitive(truncateBody(body)));
208
+ throw new Error(
209
+ 'Cannot interpret the request body as valid JSON. Check the server log for more details.'
210
+ );
208
211
  }
209
212
  } else {
210
213
  reqOpts.data = body;
@@ -212,8 +215,9 @@ export class JWProxy {
212
215
  }
213
216
 
214
217
  this.log.debug(
215
- `Proxying [${method} ${url || '/'}] to [${method} ${newUrl}] ` +
216
- (reqOpts.data ? `with body: ${truncateBody(reqOpts.data)}` : 'with no body')
218
+ `Proxying [%s %s] to [%s %s] with ${reqOpts.data ? 'body: %s' : '%s body'}`,
219
+ method, url || '/', method, newUrl,
220
+ reqOpts.data ? logger.markSensitive(truncateBody(reqOpts.data)) : 'no'
217
221
  );
218
222
 
219
223
  const throwProxyError = (error) => {
@@ -236,7 +240,7 @@ export class JWProxy {
236
240
  }
237
241
  this.log.debug(`Got response with status ${status}: ${truncateBody(data)}`);
238
242
  isResponseLogged = true;
239
- const isSessionCreationRequest = /\/session$/.test(url) && method === 'POST';
243
+ const isSessionCreationRequest = url.endsWith('/session') && method === 'POST';
240
244
  if (isSessionCreationRequest) {
241
245
  if (status === 200) {
242
246
  this.sessionId = data.sessionId || (data.value || {}).sessionId;
@@ -317,9 +317,9 @@ function buildHandler(
317
317
  if (spec.deprecated && spec.command && !deprecatedCommandsLogged.has(spec.command)) {
318
318
  deprecatedCommandsLogged.add(spec.command);
319
319
  getLogger(driver, req.params.sessionId).warn(
320
- `Command '${spec.command}' has been deprecated and will be removed in a future ` +
321
- `version of Appium or your driver/plugin. Please use a different method or contact the ` +
322
- `driver/plugin author to add explicit support for the command before it is removed`
320
+ `The ${method} ${path} endpoint has been deprecated and will be removed in a future ` +
321
+ `version of Appium or your driver/plugin. Please use a different endpoint or contact the ` +
322
+ `driver/plugin author to add explicit support for the endpoint before it is removed`
323
323
  );
324
324
  }
325
325
 
@@ -394,9 +394,9 @@ function buildHandler(
394
394
 
395
395
  // run the driver command wrapped inside the argument validators
396
396
  getLogger(driver, req.params.sessionId).debug(
397
- `Calling ` +
398
- `${driver.constructor.name}.${spec.command}() with args: ` +
399
- _.truncate(JSON.stringify(args), {length: MAX_LOG_BODY_LENGTH})
397
+ `Calling %s.%s() with args: %s`,
398
+ driver.constructor.name, spec.command,
399
+ logger.markSensitive(_.truncate(JSON.stringify(args), {length: MAX_LOG_BODY_LENGTH}))
400
400
  );
401
401
 
402
402
  if (didPluginOverrideProxy) {
@@ -269,8 +269,15 @@ export const METHOD_MAP = /** @type {const} */ ({
269
269
  POST: {command: 'setRotation', payloadParams: {required: ['x', 'y', 'z']}},
270
270
  },
271
271
  '/session/:sessionId/location': {
272
- GET: {command: 'getGeoLocation'},
273
- POST: {command: 'setGeoLocation', payloadParams: {required: ['location']}},
272
+ GET: {
273
+ command: 'getGeoLocation',
274
+ deprecated: true,
275
+ },
276
+ POST: {
277
+ command: 'setGeoLocation',
278
+ payloadParams: {required: ['location']},
279
+ deprecated: true,
280
+ },
274
281
  },
275
282
  '/session/:sessionId/orientation': {
276
283
  GET: {command: 'getOrientation'},
@@ -406,7 +413,7 @@ export const METHOD_MAP = /** @type {const} */ ({
406
413
  // #endregion
407
414
 
408
415
  //
409
- // 3rd party vendor/protcol support
416
+ // 3rd party vendor/protocol support
410
417
  //
411
418
  // #region Selenium/Chromium browsers
412
419
  '/session/:sessionId/se/log': {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appium/base-driver",
3
- "version": "10.0.0-beta.2",
3
+ "version": "10.0.0-rc.2",
4
4
  "description": "Base driver class for Appium drivers",
5
5
  "keywords": [
6
6
  "automation",
@@ -44,38 +44,38 @@
44
44
  "test:types": "tsd"
45
45
  },
46
46
  "dependencies": {
47
- "@appium/support": "^6.1.0",
48
- "@appium/types": "^0.25.3",
47
+ "@appium/support": "^7.0.0-rc.1",
48
+ "@appium/types": "^1.0.0-rc.1",
49
49
  "@colors/colors": "1.6.0",
50
50
  "async-lock": "1.4.1",
51
51
  "asyncbox": "3.0.0",
52
- "axios": "1.9.0",
52
+ "axios": "1.11.0",
53
53
  "bluebird": "3.7.2",
54
- "body-parser": "1.20.3",
55
- "express": "5.0.1",
54
+ "body-parser": "2.2.0",
55
+ "express": "5.1.0",
56
56
  "fastest-levenshtein": "1.0.16",
57
57
  "http-status-codes": "2.3.0",
58
58
  "lodash": "4.17.21",
59
- "lru-cache": "10.4.3",
59
+ "lru-cache": "11.1.0",
60
60
  "method-override": "3.0.0",
61
- "morgan": "1.10.0",
61
+ "morgan": "1.10.1",
62
62
  "path-to-regexp": "8.2.0",
63
- "serve-favicon": "2.5.0",
63
+ "serve-favicon": "2.5.1",
64
64
  "source-map-support": "0.5.21",
65
- "type-fest": "4.40.0"
65
+ "type-fest": "4.41.0"
66
66
  },
67
67
  "optionalDependencies": {
68
68
  "spdy": "4.0.2"
69
69
  },
70
70
  "engines": {
71
- "node": "^20.9.0 || >=22.11.0",
71
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0",
72
72
  "npm": ">=10"
73
73
  },
74
74
  "publishConfig": {
75
75
  "access": "public",
76
- "tag": "beta"
76
+ "tag": "rc"
77
77
  },
78
- "gitHead": "dc5ef8d5f62c8a0775131305107a4037cd9ec9c9",
78
+ "gitHead": "157425ce6aa01c009533f5bf6a56b14570222b00",
79
79
  "tsd": {
80
80
  "directory": "test/types"
81
81
  }