@appium/base-driver 10.5.1 → 10.6.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 (130) hide show
  1. package/build/lib/basedriver/capabilities.d.ts.map +1 -1
  2. package/build/lib/basedriver/capabilities.js +45 -45
  3. package/build/lib/basedriver/capabilities.js.map +1 -1
  4. package/build/lib/basedriver/commands/bidi.d.ts.map +1 -1
  5. package/build/lib/basedriver/commands/bidi.js +9 -13
  6. package/build/lib/basedriver/commands/bidi.js.map +1 -1
  7. package/build/lib/basedriver/commands/event.d.ts.map +1 -1
  8. package/build/lib/basedriver/commands/event.js +4 -7
  9. package/build/lib/basedriver/commands/event.js.map +1 -1
  10. package/build/lib/basedriver/commands/execute.js +3 -6
  11. package/build/lib/basedriver/commands/execute.js.map +1 -1
  12. package/build/lib/basedriver/commands/log.d.ts.map +1 -1
  13. package/build/lib/basedriver/commands/log.js +1 -5
  14. package/build/lib/basedriver/commands/log.js.map +1 -1
  15. package/build/lib/basedriver/commands/timeout.d.ts.map +1 -1
  16. package/build/lib/basedriver/commands/timeout.js +5 -9
  17. package/build/lib/basedriver/commands/timeout.js.map +1 -1
  18. package/build/lib/basedriver/core.js +12 -12
  19. package/build/lib/basedriver/core.js.map +1 -1
  20. package/build/lib/basedriver/device-settings.d.ts.map +1 -1
  21. package/build/lib/basedriver/device-settings.js +3 -7
  22. package/build/lib/basedriver/device-settings.js.map +1 -1
  23. package/build/lib/basedriver/driver.d.ts.map +1 -1
  24. package/build/lib/basedriver/driver.js +13 -16
  25. package/build/lib/basedriver/driver.js.map +1 -1
  26. package/build/lib/basedriver/extension-core.d.ts +4 -1
  27. package/build/lib/basedriver/extension-core.d.ts.map +1 -1
  28. package/build/lib/basedriver/extension-core.js +27 -9
  29. package/build/lib/basedriver/extension-core.js.map +1 -1
  30. package/build/lib/basedriver/helpers.d.ts.map +1 -1
  31. package/build/lib/basedriver/helpers.js +28 -30
  32. package/build/lib/basedriver/helpers.js.map +1 -1
  33. package/build/lib/basedriver/ipc.d.ts +36 -0
  34. package/build/lib/basedriver/ipc.d.ts.map +1 -0
  35. package/build/lib/basedriver/ipc.js +155 -0
  36. package/build/lib/basedriver/ipc.js.map +1 -0
  37. package/build/lib/basedriver/validation.js +25 -28
  38. package/build/lib/basedriver/validation.js.map +1 -1
  39. package/build/lib/express/express-logging.d.ts.map +1 -1
  40. package/build/lib/express/express-logging.js +2 -3
  41. package/build/lib/express/express-logging.js.map +1 -1
  42. package/build/lib/express/idempotency.js +3 -6
  43. package/build/lib/express/idempotency.js.map +1 -1
  44. package/build/lib/express/middleware.d.ts.map +1 -1
  45. package/build/lib/express/middleware.js +6 -10
  46. package/build/lib/express/middleware.js.map +1 -1
  47. package/build/lib/express/server.d.ts.map +1 -1
  48. package/build/lib/express/server.js +64 -54
  49. package/build/lib/express/server.js.map +1 -1
  50. package/build/lib/express/static.d.ts.map +1 -1
  51. package/build/lib/express/static.js +14 -7
  52. package/build/lib/express/static.js.map +1 -1
  53. package/build/lib/express/websocket.d.ts.map +1 -1
  54. package/build/lib/express/websocket.js +6 -9
  55. package/build/lib/express/websocket.js.map +1 -1
  56. package/build/lib/helpers/capabilities.d.ts.map +1 -1
  57. package/build/lib/helpers/capabilities.js +14 -17
  58. package/build/lib/helpers/capabilities.js.map +1 -1
  59. package/build/lib/helpers/extension-command-name.js +2 -5
  60. package/build/lib/helpers/extension-command-name.js.map +1 -1
  61. package/build/lib/helpers/levenshtein-match.d.ts.map +1 -1
  62. package/build/lib/helpers/levenshtein-match.js +2 -6
  63. package/build/lib/helpers/levenshtein-match.js.map +1 -1
  64. package/build/lib/index.d.ts +1 -0
  65. package/build/lib/index.d.ts.map +1 -1
  66. package/build/lib/index.js +3 -14
  67. package/build/lib/index.js.map +1 -1
  68. package/build/lib/jsonwp-proxy/protocol-converter.d.ts.map +1 -1
  69. package/build/lib/jsonwp-proxy/protocol-converter.js +13 -17
  70. package/build/lib/jsonwp-proxy/protocol-converter.js.map +1 -1
  71. package/build/lib/jsonwp-proxy/proxy-request.d.ts +2 -2
  72. package/build/lib/jsonwp-proxy/proxy-request.d.ts.map +1 -1
  73. package/build/lib/jsonwp-proxy/proxy-request.js +25 -21
  74. package/build/lib/jsonwp-proxy/proxy-request.js.map +1 -1
  75. package/build/lib/jsonwp-proxy/proxy.d.ts.map +1 -1
  76. package/build/lib/jsonwp-proxy/proxy.js +29 -26
  77. package/build/lib/jsonwp-proxy/proxy.js.map +1 -1
  78. package/build/lib/protocol/errors.d.ts.map +1 -1
  79. package/build/lib/protocol/errors.js +25 -29
  80. package/build/lib/protocol/errors.js.map +1 -1
  81. package/build/lib/protocol/helpers.d.ts.map +1 -1
  82. package/build/lib/protocol/helpers.js +9 -8
  83. package/build/lib/protocol/helpers.js.map +1 -1
  84. package/build/lib/protocol/protocol.d.ts.map +1 -1
  85. package/build/lib/protocol/protocol.js +43 -48
  86. package/build/lib/protocol/protocol.js.map +1 -1
  87. package/build/lib/protocol/routes.d.ts +1 -1
  88. package/build/lib/protocol/routes.d.ts.map +1 -1
  89. package/build/lib/protocol/routes.js +9 -12
  90. package/build/lib/protocol/routes.js.map +1 -1
  91. package/build/lib/protocol/validators.d.ts.map +1 -1
  92. package/build/lib/protocol/validators.js +1 -5
  93. package/build/lib/protocol/validators.js.map +1 -1
  94. package/build/lib/utils.d.ts +16 -0
  95. package/build/lib/utils.d.ts.map +1 -0
  96. package/build/lib/utils.js +71 -0
  97. package/build/lib/utils.js.map +1 -0
  98. package/lib/basedriver/capabilities.ts +60 -55
  99. package/lib/basedriver/commands/bidi.ts +10 -10
  100. package/lib/basedriver/commands/event.ts +11 -10
  101. package/lib/basedriver/commands/execute.ts +3 -3
  102. package/lib/basedriver/commands/log.ts +3 -2
  103. package/lib/basedriver/commands/timeout.ts +5 -6
  104. package/lib/basedriver/core.ts +12 -12
  105. package/lib/basedriver/device-settings.ts +3 -4
  106. package/lib/basedriver/driver.ts +15 -13
  107. package/lib/basedriver/extension-core.ts +33 -7
  108. package/lib/basedriver/helpers.ts +28 -30
  109. package/lib/basedriver/ipc.ts +179 -0
  110. package/lib/basedriver/validation.ts +26 -26
  111. package/lib/express/express-logging.ts +3 -4
  112. package/lib/express/idempotency.ts +3 -3
  113. package/lib/express/middleware.ts +6 -8
  114. package/lib/express/server.ts +67 -61
  115. package/lib/express/static.ts +15 -7
  116. package/lib/express/websocket.ts +8 -10
  117. package/lib/helpers/capabilities.ts +18 -14
  118. package/lib/helpers/extension-command-name.ts +2 -2
  119. package/lib/helpers/levenshtein-match.ts +2 -5
  120. package/lib/index.js +1 -11
  121. package/lib/jsonwp-proxy/protocol-converter.ts +14 -15
  122. package/lib/jsonwp-proxy/proxy-request.ts +26 -26
  123. package/lib/jsonwp-proxy/proxy.ts +36 -37
  124. package/lib/protocol/errors.ts +29 -28
  125. package/lib/protocol/helpers.ts +9 -5
  126. package/lib/protocol/protocol.ts +44 -46
  127. package/lib/protocol/routes.ts +9 -9
  128. package/lib/protocol/validators.ts +1 -3
  129. package/lib/utils.ts +85 -0
  130. package/package.json +7 -9
@@ -1,4 +1,3 @@
1
- import _ from 'lodash';
2
1
  import {util, logger} from '@appium/support';
3
2
  import {validators} from './validators';
4
3
  import {
@@ -10,11 +9,11 @@ import {
10
9
  BadParametersError,
11
10
  } from './errors';
12
11
  import {METHOD_MAP, NO_SESSION_ID_COMMANDS} from './routes';
13
- import B from 'bluebird';
14
12
  import {formatResponseValue, ensureW3cResponse} from './helpers';
15
13
  import {MAX_LOG_BODY_LENGTH, PROTOCOLS, DEFAULT_BASE_PATH} from '../constants';
16
14
  import {isW3cCaps} from '../helpers/capabilities';
17
15
  import {log} from '../basedriver/logger';
16
+ import {omitKeys} from '../utils';
18
17
  import {generateDriverLogPrefix} from '../basedriver/helpers';
19
18
  import type {Core, AppiumLogger, PayloadParams, MethodMap, Driver, DriverMethodDef} from '@appium/types';
20
19
  import type {BaseDriver} from '../basedriver/driver';
@@ -35,7 +34,7 @@ export const deprecatedCommandsLogged: Set<string> = new Set();
35
34
  * @param createSessionArgs - Arguments passed to the createSession command
36
35
  */
37
36
  export function determineProtocol(createSessionArgs: any[]): keyof typeof PROTOCOLS {
38
- return _.some(createSessionArgs, isW3cCaps) ? PROTOCOLS.W3C : PROTOCOLS.MJSONWP;
37
+ return createSessionArgs.some(isW3cCaps) ? PROTOCOLS.W3C : PROTOCOLS.MJSONWP;
39
38
  }
40
39
 
41
40
  /**
@@ -72,7 +71,7 @@ export function getSessionId(driver: Core<any>, req: Request): string | undefine
72
71
  * @returns Whether the command requires a session id in the URL
73
72
  */
74
73
  export function isSessionCommand(command: string): boolean {
75
- return !_.includes(NO_SESSION_ID_COMMANDS, command);
74
+ return !NO_SESSION_ID_COMMANDS.includes(command);
76
75
  }
77
76
 
78
77
  /**
@@ -88,12 +87,12 @@ export function checkParams(
88
87
  ): Record<string, any> {
89
88
  let requiredParams: string[][] = [];
90
89
  let optionalParams: string[] = [];
91
- const actualParamNames: string[] = _.keys(args);
90
+ const actualParamNames: string[] = Object.keys(args);
92
91
 
93
92
  if (paramSpec.required) {
94
93
  // we might have an array of parameters,
95
94
  // or an array of arrays of parameters, so standardize
96
- requiredParams = _.cloneDeep(
95
+ requiredParams = structuredClone(
97
96
  (hasMultipleRequiredParamSets(paramSpec.required)
98
97
  ? paramSpec.required
99
98
  : [paramSpec.required]
@@ -102,7 +101,7 @@ export function checkParams(
102
101
  }
103
102
  // optional parameters are just an array
104
103
  if (paramSpec.optional) {
105
- optionalParams = _.cloneDeep(paramSpec.optional as string[]);
104
+ optionalParams = structuredClone(paramSpec.optional as string[]);
106
105
  }
107
106
 
108
107
  // If a function was provided as the 'validate' key, it will here be called with
@@ -112,41 +111,41 @@ export function checkParams(
112
111
  if (paramSpec.validate) {
113
112
  const message = paramSpec.validate(args, protocol ?? PROTOCOLS.W3C);
114
113
  if (message) {
115
- throw new errors.InvalidArgumentError(_.isString(message) ? message : undefined);
114
+ throw new errors.InvalidArgumentError(typeof message === 'string' ? message : undefined);
116
115
  }
117
116
  }
118
117
 
119
118
  // some clients pass in the session id in the params
120
- if (!_.includes(optionalParams, 'sessionId')) {
119
+ if (!optionalParams.includes('sessionId')) {
121
120
  optionalParams.push('sessionId');
122
121
  }
123
122
  // some clients pass in an element id in the params
124
- if (!_.includes(optionalParams, 'id')) {
123
+ if (!optionalParams.includes('id')) {
125
124
  optionalParams.push('id');
126
125
  }
127
126
 
128
- if (_.isEmpty(requiredParams)) {
127
+ if (util.isEmpty(requiredParams)) {
129
128
  // if we don't have any required parameters, then just filter out unknown ones
130
- return pickKnownParams(args, _.difference(actualParamNames, optionalParams));
129
+ return pickKnownParams(args, actualParamNames.filter((name) => !optionalParams.includes(name)));
131
130
  }
132
131
 
133
132
  // go through the required parameters and check against our arguments
134
133
  let matchedReqParamSet: string[] = [];
135
134
  for (const requiredParamsSet of requiredParams) {
136
- if (!_.isArray(requiredParamsSet)) {
135
+ if (!Array.isArray(requiredParamsSet)) {
137
136
  throw new Error(
138
137
  `The required parameter set item ${JSON.stringify(requiredParamsSet)} ` +
139
138
  `in ${JSON.stringify(paramSpec)} is not an array. ` +
140
139
  `This is a bug in the method map definition.`
141
140
  );
142
141
  }
143
- if (_.isEmpty(_.difference(requiredParamsSet, actualParamNames))) {
142
+ if (requiredParamsSet.every((name) => actualParamNames.includes(name))) {
144
143
  return pickKnownParams(
145
144
  args,
146
- _.difference(actualParamNames, requiredParamsSet, optionalParams)
145
+ actualParamNames.filter((name) => !requiredParamsSet.includes(name) && !optionalParams.includes(name))
147
146
  );
148
147
  }
149
- if (!_.isEmpty(requiredParamsSet) && _.isEmpty(matchedReqParamSet)) {
148
+ if (!util.isEmpty(requiredParamsSet) && util.isEmpty(matchedReqParamSet)) {
150
149
  matchedReqParamSet = requiredParamsSet;
151
150
  }
152
151
  }
@@ -168,7 +167,7 @@ export function makeArgs(requestParams: PayloadParams, jsonObj: any, payloadPara
168
167
  // since the command will sometimes want to ignore, say, the sessionId.
169
168
  // This has the effect of putting sessionId last, which means in JS we can
170
169
  // omit it from the function signature if we're not going to use it.
171
- const urlParams = _.keys(requestParams).reverse();
170
+ const urlParams = Object.keys(requestParams).reverse();
172
171
 
173
172
  // In the simple case, the required parameters are a basic array in
174
173
  // payloadParams.required, so start there. It's possible that there are
@@ -180,9 +179,9 @@ export function makeArgs(requestParams: PayloadParams, jsonObj: any, payloadPara
180
179
  // array of arrays in payloadParams.required, so loop through each set and
181
180
  // pick the one that matches which JSON params were actually sent. We've
182
181
  // already been through validation so we're guaranteed to find a match.
183
- const keys = _.keys(jsonObj);
182
+ const keys = Object.keys(jsonObj);
184
183
  for (const params of payloadParams.required) {
185
- if (_.without(params, ...keys).length === 0) {
184
+ if (params.filter((p) => !keys.includes(p)).length === 0) {
186
185
  requiredParams = params;
187
186
  break;
188
187
  }
@@ -191,7 +190,7 @@ export function makeArgs(requestParams: PayloadParams, jsonObj: any, payloadPara
191
190
 
192
191
  // Now we construct our list of arguments which will be passed to the command
193
192
  let args;
194
- if (_.isFunction(payloadParams.makeArgs)) {
193
+ if (typeof payloadParams.makeArgs === 'function') {
195
194
  // In the route spec, a particular route might define a 'makeArgs' function
196
195
  // if it wants full control over how to turn JSON parameters into command
197
196
  // arguments. So we pass it the JSON parameters and it returns an array
@@ -202,9 +201,9 @@ export function makeArgs(requestParams: PayloadParams, jsonObj: any, payloadPara
202
201
  } else {
203
202
  // Otherwise, collect all the required and optional params and flatten them
204
203
  // into an argument array
205
- args = _.flatten(requiredParams).map((p) => jsonObj[p]);
204
+ args = (requiredParams ?? []).flat().map((p) => jsonObj[p]);
206
205
  if (payloadParams.optional) {
207
- args = args.concat(_.flatten(payloadParams.optional).map((p) => jsonObj[p]));
206
+ args = args.concat((payloadParams.optional ?? []).flat().map((p) => jsonObj[p]));
208
207
  }
209
208
  }
210
209
  // Finally, get our url params (session id, element id, etc...) on the end of
@@ -222,14 +221,14 @@ export function validateExecuteMethodParams(params: any[], paramSpec?: PayloadPa
222
221
  // the w3c protocol will give us an array of arguments to apply to a javascript function.
223
222
  // that's not what we're doing. we're going to look for a JS object as the first arg, so we
224
223
  // can perform validation on it. we'll ignore everything else.
225
- if (!params || !_.isArray(params) || params.length > 1) {
224
+ if (!params || !Array.isArray(params) || params.length > 1) {
226
225
  throw new errors.InvalidArgumentError(
227
226
  `Did not get correct format of arguments for execute method. Expected zero or one ` +
228
227
  `arguments to execute script and instead received: ${JSON.stringify(params)}`
229
228
  );
230
229
  }
231
230
  const args: Record<string, any> = params[0] ?? {};
232
- if (!_.isPlainObject(args)) {
231
+ if (!util.isPlainObject(args)) {
233
232
  throw new errors.InvalidArgumentError(
234
233
  `Did not receive an appropriate execute method parameters object. It needs to be ` +
235
234
  `deserializable as a plain JS object`
@@ -265,8 +264,8 @@ export function routeConfiguringFunction(driver: Core<any>): RouteConfiguringFun
265
264
  driver.basePath = basePath;
266
265
 
267
266
  const allMethods: MethodMap<Driver> = {...METHOD_MAP, ...extraMethodMap};
268
- for (const [path, methods] of _.toPairs(allMethods)) {
269
- for (const [method, spec] of _.toPairs(methods)) {
267
+ for (const [path, methods] of Object.entries(allMethods)) {
268
+ for (const [method, spec] of Object.entries(methods)) {
270
269
  const isSessCommand = spec.command ? isSessionCommand(spec.command) : false;
271
270
  // set up the express route handler
272
271
  buildHandler(
@@ -311,7 +310,7 @@ export function driverShouldDoJwpProxy(driver: Core<any>, req: Request, command:
311
310
  }
312
311
 
313
312
  function extractProtocol(driver: Core<any>, sessionId: string | null = null): keyof typeof PROTOCOLS {
314
- const dstDriver = _.isFunction(driver.driverForSession) && sessionId
313
+ const dstDriver = typeof driver.driverForSession === 'function' && sessionId
315
314
  ? driver.driverForSession(sessionId)
316
315
  : driver;
317
316
  if (dstDriver === driver) {
@@ -327,10 +326,10 @@ function extractProtocol(driver: Core<any>, sessionId: string | null = null): ke
327
326
 
328
327
  function getLogger(driver: Core<any>, sessionId: string | null = null): AppiumLogger {
329
328
  const dstDriver =
330
- sessionId && _.isFunction(driver.driverForSession)
329
+ sessionId && typeof driver.driverForSession === 'function'
331
330
  ? driver.driverForSession(sessionId) ?? driver
332
331
  : driver;
333
- if (_.isFunction(dstDriver.log?.info)) {
332
+ if (typeof dstDriver.log?.info === 'function') {
334
333
  return dstDriver.log;
335
334
  }
336
335
 
@@ -345,7 +344,7 @@ function wrapParams<T>(paramSets, jsonObj: T): T | Record<string, T> {
345
344
  * The wrap option in the spec enforce wrapping before validation, so that all params are wrapped at
346
345
  * the time they are validated and later passed to the commands.
347
346
  */
348
- return (_.isArray(jsonObj) || !_.isObject(jsonObj)) && paramSets.wrap
347
+ return (Array.isArray(jsonObj) || typeof jsonObj !== 'object' || jsonObj === null) && paramSets.wrap
349
348
  ? {[paramSets.wrap]: jsonObj}
350
349
  : jsonObj;
351
350
  }
@@ -354,7 +353,7 @@ function unwrapParams<T>(paramSets: PayloadParams, jsonObj: T): T | Record<strin
354
353
  /* There are commands like setNetworkConnection which send parameters wrapped inside a key such as
355
354
  * "parameters". This function unwraps them (eg. {"parameters": {"type": 1}} becomes {"type": 1}).
356
355
  */
357
- return _.isObject(jsonObj) && paramSets.unwrap && jsonObj[paramSets.unwrap]
356
+ return typeof jsonObj === 'object' && jsonObj !== null && paramSets.unwrap && jsonObj[paramSets.unwrap]
358
357
  ? jsonObj[paramSets.unwrap]
359
358
  : jsonObj;
360
359
  }
@@ -363,16 +362,15 @@ function unwrapParams<T>(paramSets: PayloadParams, jsonObj: T): T | Record<strin
363
362
  function hasMultipleRequiredParamSets(
364
363
  required: ReadonlyArray<string> | MultidimensionalReadonlyArray<string, 2> | undefined
365
364
  ): required is MultidimensionalReadonlyArray<string, 2> {
366
- //@ts-expect-error Needed to convince lodash typechecks
367
- return Boolean(required && _.isArray(_.first(required)));
365
+ return Boolean(required && Array.isArray(required?.[0]));
368
366
  }
369
367
 
370
368
  function pickKnownParams(args: Record<string, any>, unknownNames: string[]): Record<string, any> {
371
- if (_.isEmpty(unknownNames)) {
369
+ if (util.isEmpty(unknownNames)) {
372
370
  return args;
373
371
  }
374
372
  log.info(`The following arguments are not known and will be ignored: ${unknownNames}`);
375
- return _.pickBy(args, (v, k) => !unknownNames.includes(k));
373
+ return omitKeys(args, unknownNames);
376
374
  }
377
375
 
378
376
  function buildHandler(
@@ -419,7 +417,7 @@ function buildHandler(
419
417
  let didPluginOverrideProxy = false;
420
418
  if (isSessCmd && !spec.neverProxy && spec.command && driverShouldDoJwpProxy(driver, req, spec.command)) {
421
419
  if (
422
- !('pluginsToHandleCmd' in driver) || !_.isFunction(driver.pluginsToHandleCmd) ||
420
+ !('pluginsToHandleCmd' in driver) || typeof driver.pluginsToHandleCmd !== 'function' ||
423
421
  driver.pluginsToHandleCmd(spec.command, sessionId).length === 0
424
422
  ) {
425
423
  await doJwpProxy(driver as BaseDriver<any>, req, res);
@@ -475,7 +473,7 @@ function buildHandler(
475
473
  getLogger(driver, sessionId).debug(
476
474
  `Calling %s.%s() with args: %s`,
477
475
  driver.constructor.name, spec.command,
478
- logger.markSensitive(_.truncate(JSON.stringify(args), {length: MAX_LOG_BODY_LENGTH}))
476
+ logger.markSensitive(util.truncateString(JSON.stringify(args), {length: MAX_LOG_BODY_LENGTH}))
479
477
  );
480
478
 
481
479
  if (didPluginOverrideProxy) {
@@ -492,8 +490,8 @@ function buildHandler(
492
490
 
493
491
  // If `executeCommand` was overridden and the method returns an object
494
492
  // with a protocol and value/error property, re-assign the protocol
495
- if (_.isPlainObject(driverRes) && _.has(driverRes, 'protocol')) {
496
- currentProtocol = driverRes.protocol || currentProtocol;
493
+ if (util.isPlainObject(driverRes) && Object.hasOwn(driverRes, 'protocol')) {
494
+ currentProtocol = (driverRes as {protocol?: keyof typeof PROTOCOLS}).protocol || currentProtocol;
497
495
  if (driverRes.error) {
498
496
  throw driverRes.error;
499
497
  }
@@ -520,7 +518,7 @@ function buildHandler(
520
518
  // delete should not return anything even if successful
521
519
  if (spec.command === DELETE_SESSION_COMMAND) {
522
520
  getLogger(driver, sessionId).debug(
523
- `Received response: ${_.truncate(JSON.stringify(driverRes), {
521
+ `Received response: ${util.truncateString(JSON.stringify(driverRes), {
524
522
  length: MAX_LOG_BODY_LENGTH,
525
523
  })}`
526
524
  );
@@ -536,7 +534,7 @@ function buildHandler(
536
534
  parseInt(driverRes.status, 10) !== 0
537
535
  ) {
538
536
  throw errorFromMJSONWPStatusCode(driverRes.status, driverRes.value);
539
- } else if (_.isPlainObject(driverRes.value) && driverRes.value.error) {
537
+ } else if (util.isPlainObject(driverRes.value) && driverRes.value.error) {
540
538
  throw errorFromW3CJsonCode(
541
539
  driverRes.value.error,
542
540
  driverRes.value.message,
@@ -548,7 +546,7 @@ function buildHandler(
548
546
  httpResBody.value = driverRes;
549
547
  getLogger(driver, sessionId || newSessionId).debug(
550
548
  `Responding ` +
551
- `to client with driver.${spec.command}() result: ${_.truncate(JSON.stringify(driverRes), {
549
+ `to client with driver.${spec.command}() result: ${util.truncateString(JSON.stringify(driverRes), {
552
550
  length: MAX_LOG_BODY_LENGTH,
553
551
  })}`
554
552
  );
@@ -556,7 +554,7 @@ function buildHandler(
556
554
  // if anything goes wrong, figure out what our response should be
557
555
  // based on the type of error that we encountered
558
556
  let actualErr;
559
- if (err instanceof Error || (_.has(err, 'stack') && _.has(err, 'message'))) {
557
+ if (err instanceof Error || (Object.hasOwn(err, 'stack') && Object.hasOwn(err, 'message'))) {
560
558
  actualErr = err;
561
559
  } else {
562
560
  getLogger(driver, sessionId || newSessionId).warn(
@@ -570,7 +568,7 @@ function buildHandler(
570
568
  currentProtocol || extractProtocol(driver, sessionId || newSessionId);
571
569
 
572
570
  let errMsg = err.stacktrace || err.stack;
573
- if (!_.includes(errMsg, err.message)) {
571
+ if (!errMsg.includes(err.message)) {
574
572
  // if the message has more information, add it. but often the message
575
573
  // is the first part of the stack trace
576
574
  errMsg = `${err.message}${errMsg ? '\n' + errMsg : ''}`;
@@ -587,7 +585,7 @@ function buildHandler(
587
585
  }
588
586
 
589
587
  // decode the response, which is either a string or json
590
- if (_.isString(httpResBody)) {
588
+ if (typeof httpResBody === 'string') {
591
589
  res.status(httpStatus)
592
590
  .setHeader('content-type', 'application/json; charset=utf-8')
593
591
  .send(httpResBody);
@@ -600,7 +598,7 @@ function buildHandler(
600
598
  };
601
599
  // add the method to the app
602
600
  app[method.toLowerCase()](path, (req, res) => {
603
- B.resolve(asyncHandler(req, res)).done();
601
+ void asyncHandler(req, res);
604
602
  });
605
603
  }
606
604
 
@@ -1,5 +1,5 @@
1
1
  import type {Driver, DriverMethodDef, HTTPMethod, MethodMap} from '@appium/types';
2
- import _ from 'lodash';
2
+ import {util} from '@appium/support';
3
3
  import {DEFAULT_BASE_PATH} from '../constants';
4
4
  import {match} from 'path-to-regexp';
5
5
  import {LRUCache} from 'lru-cache';
@@ -579,9 +579,9 @@ export const METHOD_MAP = {
579
579
  } as const satisfies MethodMap<Driver>;
580
580
 
581
581
  // driver command names
582
- export const ALL_COMMANDS = _.flatMap(_.values(METHOD_MAP).map(_.values))
583
- .filter((m) => Boolean(m.command))
584
- .map((m) => m.command);
582
+ export const ALL_COMMANDS = Object.values(METHOD_MAP)
583
+ .flatMap((methods) => Object.values(methods) as Array<{command?: string}>)
584
+ .flatMap((m) => (m.command ? [m.command] : []));
585
585
 
586
586
  /**
587
587
  * Resolve a WebDriver URL path and HTTP method to a driver command name from {@link METHOD_MAP}.
@@ -596,9 +596,9 @@ export function routeToCommandName(
596
596
  ): string | undefined {
597
597
  const resolvedBasePath = basePath ?? DEFAULT_BASE_PATH;
598
598
  let normalizedEndpoint = resolvedBasePath
599
- ? endpoint.replace(new RegExp(`^${_.escapeRegExp(resolvedBasePath)}`), '')
599
+ ? endpoint.replace(new RegExp(`^${util.escapeRegExp(resolvedBasePath)}`), '')
600
600
  : endpoint;
601
- normalizedEndpoint = `${_.startsWith(normalizedEndpoint, '/') ? '' : '/'}${normalizedEndpoint}`;
601
+ normalizedEndpoint = `${normalizedEndpoint.startsWith('/') ? '' : '/'}${normalizedEndpoint}`;
602
602
  let normalizedPathname: string;
603
603
  try {
604
604
  // we could use any prefix there as we anyway need to only extract the pathname
@@ -608,7 +608,7 @@ export function routeToCommandName(
608
608
  throw new Error(`'${endpoint}' cannot be translated to a command name: ${msg}`, {cause: err});
609
609
  }
610
610
 
611
- const normalizedMethod = _.toUpper(method ?? '');
611
+ const normalizedMethod = (method ?? '').toUpperCase();
612
612
  const cacheKey = toCommandNameCacheKey(normalizedPathname, normalizedMethod);
613
613
  const cached = COMMAND_NAMES_CACHE.get(cacheKey);
614
614
  if (cached !== undefined) {
@@ -620,12 +620,12 @@ export function routeToCommandName(
620
620
  possiblePathnames.push(`/session/any-session-id${normalizedPathname}`);
621
621
  }
622
622
  possiblePathnames.push(normalizedPathname);
623
- for (const [routePath, routeSpec] of _.toPairs(METHOD_MAP)) {
623
+ for (const [routePath, routeSpec] of Object.entries(METHOD_MAP)) {
624
624
  const routeMatcher = match(routePath);
625
625
  if (possiblePathnames.some((pp) => routeMatcher(pp))) {
626
626
  const spec = routeSpec as Record<string, DriverMethodDef<Driver>>;
627
627
  const commandForAnyMethod = () =>
628
- _.first(_.keys(spec).map((key) => spec[key]?.command));
628
+ Object.keys(spec).map((key) => spec[key]?.command)[0];
629
629
  const commandName = normalizedMethod ? spec[normalizedMethod]?.command : commandForAnyMethod();
630
630
  if (commandName) {
631
631
  COMMAND_NAMES_CACHE.set(cacheKey, commandName);
@@ -1,5 +1,3 @@
1
- import _ from 'lodash';
2
-
3
1
  export const validators = {
4
2
  setUrl: (url: any) => {
5
3
  // either an `xyz://`, `about:`, or `data:` scheme is allowed
@@ -15,5 +13,5 @@ export const validators = {
15
13
  };
16
14
 
17
15
  function isNumber(o: any): o is number {
18
- return _.isNumber(o) || !_.isNaN(parseInt(o, 10)) || !_.isNaN(parseFloat(o));
16
+ return typeof o === 'number' || !Number.isNaN(parseInt(o, 10)) || !Number.isNaN(parseFloat(o));
19
17
  }
package/lib/utils.ts ADDED
@@ -0,0 +1,85 @@
1
+ import {util} from '@appium/support';
2
+
3
+ /**
4
+ * Deep-merge plain objects into a clone of `target`. Skips null/undefined sources.
5
+ * Non-plain values on a key replace the previous value (same as lodash merge for objects).
6
+ */
7
+ export function mergePlainObjects<T extends Record<string, unknown>>(
8
+ target: T,
9
+ ...sources: Array<Partial<T> | undefined>
10
+ ): T {
11
+ const result = structuredClone(target);
12
+ for (const source of sources) {
13
+ if (source == null) {
14
+ continue;
15
+ }
16
+ for (const [key, value] of Object.entries(source)) {
17
+ const existing = result[key as keyof T];
18
+ if (util.isPlainObject(existing) && util.isPlainObject(value)) {
19
+ result[key as keyof T] = mergePlainObjects(
20
+ existing as Record<string, unknown>,
21
+ value as Record<string, unknown>,
22
+ ) as T[keyof T];
23
+ } else if (value !== undefined) {
24
+ result[key as keyof T] = value as T[keyof T];
25
+ }
26
+ }
27
+ }
28
+ return result;
29
+ }
30
+
31
+ /** Return a shallow copy of `obj` without `key`. Non-objects are returned unchanged. */
32
+ export function omit<T extends Record<string, unknown>>(obj: T, key: string): T {
33
+ if (!util.isPlainObject(obj)) {
34
+ return obj;
35
+ }
36
+ return Object.fromEntries(Object.entries(obj).filter(([k]) => k !== key)) as T;
37
+ }
38
+
39
+ /** Return a shallow copy of `obj` without any of `keys`. */
40
+ export function omitKeys<T extends Record<string, unknown>>(obj: T, keys: readonly string[]): T {
41
+ if (!util.isPlainObject(obj) || keys.length === 0) {
42
+ return obj;
43
+ }
44
+ const keysToOmit = new Set(keys);
45
+ return Object.fromEntries(Object.entries(obj).filter(([k]) => !keysToOmit.has(k))) as T;
46
+ }
47
+
48
+ /** Return a shallow copy of `obj` containing only listed keys. */
49
+ export function pick<T extends Record<string, unknown>>(
50
+ obj: T,
51
+ keys: readonly string[]
52
+ ): Partial<T> {
53
+ const keysToPick = new Set(keys);
54
+ return Object.fromEntries(Object.entries(obj).filter(([k]) => keysToPick.has(k))) as Partial<T>;
55
+ }
56
+
57
+ /** Return a shallow copy of `obj` whose entries pass `predicate`. */
58
+ export function pickBy<T extends Record<string, unknown>>(
59
+ obj: T,
60
+ predicate: (value: T[keyof T], key: keyof T) => boolean
61
+ ): Partial<T> {
62
+ return Object.fromEntries(
63
+ Object.entries(obj).filter(([key, value]) =>
64
+ predicate(value as T[keyof T], key as keyof T)
65
+ )
66
+ ) as Partial<T>;
67
+ }
68
+
69
+ /** Compile a lodash-style template string (`<%= expression %>`) into a render function. */
70
+ export function compileLodashTemplate(
71
+ template: string
72
+ ): (params: Record<string, unknown>) => string {
73
+ const parts: string[] = [];
74
+ let lastIndex = 0;
75
+ const re = /<%=\s*([\s\S]+?)\s*%>/g;
76
+ let match;
77
+ while ((match = re.exec(template)) !== null) {
78
+ parts.push(JSON.stringify(template.slice(lastIndex, match.index)));
79
+ parts.push(`String(${match[1]})`);
80
+ lastIndex = match.index + match[0].length;
81
+ }
82
+ parts.push(JSON.stringify(template.slice(lastIndex)));
83
+ const fn = new Function('obj', `with (obj) { return ${parts.join(' + ')}; }`);
84
+ return (params) => fn(params) as string;
85
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appium/base-driver",
3
- "version": "10.5.1",
3
+ "version": "10.6.0",
4
4
  "description": "Base driver class for Appium drivers",
5
5
  "keywords": [
6
6
  "automation",
@@ -45,19 +45,17 @@
45
45
  "test:types": "tsd"
46
46
  },
47
47
  "dependencies": {
48
- "@appium/support": "7.2.1",
49
- "@appium/types": "1.4.0",
48
+ "@appium/support": "7.2.3",
49
+ "@appium/types": "1.5.0",
50
50
  "@colors/colors": "1.6.0",
51
51
  "async-lock": "1.4.1",
52
- "asyncbox": "6.2.0",
53
- "axios": "1.16.0",
54
- "bluebird": "3.7.2",
52
+ "asyncbox": "6.3.0",
53
+ "axios": "1.16.1",
55
54
  "body-parser": "2.2.2",
56
55
  "express": "5.2.1",
57
56
  "fastest-levenshtein": "1.0.16",
58
57
  "http-status-codes": "2.3.0",
59
- "lodash": "4.18.1",
60
- "lru-cache": "11.3.5",
58
+ "lru-cache": "11.5.0",
61
59
  "method-override": "3.0.0",
62
60
  "morgan": "1.10.1",
63
61
  "path-to-regexp": "8.4.2",
@@ -74,7 +72,7 @@
74
72
  "publishConfig": {
75
73
  "access": "public"
76
74
  },
77
- "gitHead": "fd6dbe9eff4b52bd5436811ae71b0425c0ebfff4",
75
+ "gitHead": "8018f8b70ecc975fa115e19072ef337b8473865d",
78
76
  "tsd": {
79
77
  "directory": "test/types"
80
78
  }