@appium/base-driver 10.2.2 → 10.4.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 (59) hide show
  1. package/build/lib/basedriver/capabilities.d.ts +4 -0
  2. package/build/lib/basedriver/capabilities.d.ts.map +1 -1
  3. package/build/lib/basedriver/capabilities.js +4 -0
  4. package/build/lib/basedriver/capabilities.js.map +1 -1
  5. package/build/lib/basedriver/commands/execute.js +7 -17
  6. package/build/lib/basedriver/commands/execute.js.map +1 -1
  7. package/build/lib/basedriver/core.d.ts +24 -24
  8. package/build/lib/basedriver/core.d.ts.map +1 -1
  9. package/build/lib/basedriver/core.js +29 -29
  10. package/build/lib/basedriver/core.js.map +1 -1
  11. package/build/lib/basedriver/driver.d.ts.map +1 -1
  12. package/build/lib/basedriver/driver.js +7 -2
  13. package/build/lib/basedriver/driver.js.map +1 -1
  14. package/build/lib/basedriver/extension-core.d.ts +1 -1
  15. package/build/lib/basedriver/extension-core.d.ts.map +1 -1
  16. package/build/lib/basedriver/extension-core.js +1 -1
  17. package/build/lib/basedriver/extension-core.js.map +1 -1
  18. package/build/lib/basedriver/helpers.d.ts.map +1 -1
  19. package/build/lib/basedriver/helpers.js +2 -2
  20. package/build/lib/basedriver/helpers.js.map +1 -1
  21. package/build/lib/helpers/levenshtein-match.d.ts +27 -0
  22. package/build/lib/helpers/levenshtein-match.d.ts.map +1 -0
  23. package/build/lib/helpers/levenshtein-match.js +61 -0
  24. package/build/lib/helpers/levenshtein-match.js.map +1 -0
  25. package/build/lib/jsonwp-proxy/protocol-converter.d.ts +1 -1
  26. package/build/lib/jsonwp-proxy/protocol-converter.d.ts.map +1 -1
  27. package/build/lib/jsonwp-proxy/protocol-converter.js +3 -3
  28. package/build/lib/jsonwp-proxy/protocol-converter.js.map +1 -1
  29. package/build/lib/jsonwp-proxy/proxy.d.ts +53 -98
  30. package/build/lib/jsonwp-proxy/proxy.d.ts.map +1 -1
  31. package/build/lib/jsonwp-proxy/proxy.js +102 -145
  32. package/build/lib/jsonwp-proxy/proxy.js.map +1 -1
  33. package/build/lib/protocol/errors.d.ts +45 -45
  34. package/build/lib/protocol/errors.d.ts.map +1 -1
  35. package/build/lib/protocol/errors.js +162 -162
  36. package/build/lib/protocol/errors.js.map +1 -1
  37. package/build/lib/protocol/protocol.d.ts +35 -0
  38. package/build/lib/protocol/protocol.d.ts.map +1 -1
  39. package/build/lib/protocol/protocol.js +105 -77
  40. package/build/lib/protocol/protocol.js.map +1 -1
  41. package/build/lib/protocol/routes.d.ts +6 -0
  42. package/build/lib/protocol/routes.d.ts.map +1 -1
  43. package/build/lib/protocol/routes.js +6 -0
  44. package/build/lib/protocol/routes.js.map +1 -1
  45. package/lib/basedriver/capabilities.ts +4 -0
  46. package/lib/basedriver/commands/execute.ts +7 -18
  47. package/lib/basedriver/core.ts +34 -34
  48. package/lib/basedriver/driver.ts +9 -2
  49. package/lib/basedriver/extension-core.ts +1 -1
  50. package/lib/basedriver/helpers.ts +21 -21
  51. package/lib/helpers/levenshtein-match.ts +74 -0
  52. package/lib/jsonwp-proxy/protocol-converter.ts +4 -4
  53. package/lib/jsonwp-proxy/proxy.ts +506 -0
  54. package/lib/protocol/errors.ts +281 -246
  55. package/lib/protocol/protocol.ts +121 -93
  56. package/lib/protocol/routes.ts +6 -0
  57. package/package.json +10 -10
  58. package/tsconfig.json +6 -0
  59. package/lib/jsonwp-proxy/proxy.js +0 -493
@@ -30,6 +30,10 @@ export const LIST_DRIVER_EXTENSIONS_COMMAND = 'listExtensions';
30
30
 
31
31
  export const deprecatedCommandsLogged: Set<string> = new Set();
32
32
 
33
+ /**
34
+ * Infer W3C vs MJSONWP from new-session capability payloads.
35
+ * @param createSessionArgs - Arguments passed to the createSession command
36
+ */
33
37
  export function determineProtocol(createSessionArgs: any[]): keyof typeof PROTOCOLS {
34
38
  return _.some(createSessionArgs, isW3cCaps) ? PROTOCOLS.W3C : PROTOCOLS.MJSONWP;
35
39
  }
@@ -63,74 +67,20 @@ export function getSessionId(driver: Core<any>, req: Request): string | undefine
63
67
  return req.params.sessionId;
64
68
  }
65
69
 
66
- function extractProtocol(driver: Core<any>, sessionId: string | null = null): keyof typeof PROTOCOLS {
67
- const dstDriver = _.isFunction(driver.driverForSession) && sessionId
68
- ? driver.driverForSession(sessionId)
69
- : driver;
70
- if (dstDriver === driver) {
71
- // Shortcircuit if the driver instance is not an umbrella driver
72
- // or it is Fake driver instance, where `driver.driverForSession`
73
- // always returns self instance
74
- return driver.protocol ?? PROTOCOLS.W3C;
75
- }
76
-
77
- // Extract the protocol for the current session if the given driver is the umbrella one
78
- return dstDriver?.protocol ?? PROTOCOLS.W3C;
79
- }
80
-
70
+ /**
71
+ * @param command - Driver command name
72
+ * @returns Whether the command requires a session id in the URL
73
+ */
81
74
  export function isSessionCommand(command: string): boolean {
82
75
  return !_.includes(NO_SESSION_ID_COMMANDS, command);
83
76
  }
84
77
 
85
- function getLogger(driver: Core<any>, sessionId: string | null = null): AppiumLogger {
86
- const dstDriver =
87
- sessionId && _.isFunction(driver.driverForSession)
88
- ? driver.driverForSession(sessionId) ?? driver
89
- : driver;
90
- if (_.isFunction(dstDriver.log?.info)) {
91
- return dstDriver.log;
92
- }
93
-
94
- const logPrefix = generateDriverLogPrefix(dstDriver);
95
- return logger.getLogger(logPrefix);
96
- }
97
-
98
- function wrapParams<T>(paramSets, jsonObj: T): T | Record<string, T> {
99
- /* There are commands like performTouch which take a single parameter (primitive type or array).
100
- * Some drivers choose to pass this parameter as a value (eg. [action1, action2...]) while others to
101
- * wrap it within an object(eg' {gesture: [action1, action2...]}), which makes it hard to validate.
102
- * The wrap option in the spec enforce wrapping before validation, so that all params are wrapped at
103
- * the time they are validated and later passed to the commands.
104
- */
105
- return (_.isArray(jsonObj) || !_.isObject(jsonObj)) && paramSets.wrap
106
- ? {[paramSets.wrap]: jsonObj}
107
- : jsonObj;
108
- }
109
-
110
- function unwrapParams<T>(paramSets: PayloadParams, jsonObj: T): T | Record<string, T> {
111
- /* There are commands like setNetworkConnection which send parameters wrapped inside a key such as
112
- * "parameters". This function unwraps them (eg. {"parameters": {"type": 1}} becomes {"type": 1}).
113
- */
114
- return _.isObject(jsonObj) && paramSets.unwrap && jsonObj[paramSets.unwrap]
115
- ? jsonObj[paramSets.unwrap]
116
- : jsonObj;
117
- }
118
-
119
- function hasMultipleRequiredParamSets(
120
- required: ReadonlyArray<string> | MultidimensionalReadonlyArray<string, 2> | undefined
121
- ): required is MultidimensionalReadonlyArray<string, 2> {
122
- //@ts-expect-error Needed to convince lodash typechecks
123
- return Boolean(required && _.isArray(_.first(required)));
124
- }
125
-
126
- function pickKnownParams(args: Record<string, any>, unknownNames: string[]): Record<string, any> {
127
- if (_.isEmpty(unknownNames)) {
128
- return args;
129
- }
130
- log.info(`The following arguments are not known and will be ignored: ${unknownNames}`);
131
- return _.pickBy(args, (v, k) => !unknownNames.includes(k));
132
- }
133
-
78
+ /**
79
+ * Validate request arguments against a route payload spec and return filtered params.
80
+ * @param paramSpec - Required/optional parameter definition from the method map
81
+ * @param args - Raw arguments (e.g. JSON body)
82
+ * @param protocol - Active protocol, used when a custom validate function is present
83
+ */
134
84
  export function checkParams(
135
85
  paramSpec: PayloadParams,
136
86
  args: Record<string, any>,
@@ -207,12 +157,11 @@ export function checkParams(
207
157
  }, actualParamNames);
208
158
  }
209
159
 
210
- /*
211
- * This method takes 3 pieces of data: request parameters ('requestParams'),
212
- * a request JSON body ('jsonObj'), and 'payloadParams', which is the section
213
- * from the route definition for a particular endpoint which has instructions
214
- * on handling parameters. This method returns an array of arguments which will
215
- * be applied to a command.
160
+ /**
161
+ * Build the ordered argument list for a driver command from URL params, JSON body, and route spec.
162
+ * @param requestParams - Express route parameters (e.g. sessionId, element id)
163
+ * @param jsonObj - Parsed JSON request body
164
+ * @param payloadParams - Route payload definition (required/optional/makeArgs)
216
165
  */
217
166
  export function makeArgs(requestParams: PayloadParams, jsonObj: any, payloadParams: PayloadParams): any[] {
218
167
  // We want to pass the "url" parameters to the commands in reverse order
@@ -264,6 +213,11 @@ export function makeArgs(requestParams: PayloadParams, jsonObj: any, payloadPara
264
213
  return args;
265
214
  }
266
215
 
216
+ /**
217
+ * Validate parameters for execute/executeAsync script endpoints.
218
+ * @param params - Raw execute command arguments from the client
219
+ * @param paramSpec - Optional payload spec for additional validation
220
+ */
267
221
  export function validateExecuteMethodParams(params: any[], paramSpec?: PayloadParams): any[] {
268
222
  // the w3c protocol will give us an array of arguments to apply to a javascript function.
269
223
  // that's not what we're doing. we're going to look for a JS object as the first arg, so we
@@ -290,7 +244,10 @@ export function validateExecuteMethodParams(params: any[], paramSpec?: PayloadPa
290
244
  return makeArgs({}, filteredArgs, specToUse);
291
245
  }
292
246
 
293
-
247
+ /**
248
+ * Returns a function that registers default (and plugin) HTTP routes on an Express app for a driver.
249
+ * @param driver - Driver instance used to execute commands
250
+ */
294
251
  export function routeConfiguringFunction(driver: Core<any>): RouteConfiguringFunction {
295
252
  if (!driver.sessionExists) {
296
253
  throw new Error('Drivers must implement `sessionExists` property');
@@ -325,6 +282,99 @@ export function routeConfiguringFunction(driver: Core<any>): RouteConfiguringFun
325
282
  };
326
283
  }
327
284
 
285
+ /**
286
+ * Whether an incoming request should be forwarded to the driver's JWProxy for the given command.
287
+ * @param driver - Active driver
288
+ * @param req - Incoming HTTP request
289
+ * @param command - Resolved driver command name
290
+ */
291
+ export function driverShouldDoJwpProxy(driver: Core<any>, req: Request, command: string): boolean {
292
+ const sessionId = getSessionId(driver, req);
293
+ // drivers need to explicitly say when the proxy is active
294
+ if (!driver.proxyActive(sessionId)) {
295
+ return false;
296
+ }
297
+
298
+ // we should never proxy deleteSession because we need to give the containing
299
+ // driver an opportunity to clean itself up
300
+ if (command === DELETE_SESSION_COMMAND) {
301
+ return false;
302
+ }
303
+
304
+ // validate avoidance schema, and say we shouldn't proxy if anything in the
305
+ // avoid list matches our req
306
+ if (driver.proxyRouteIsAvoided(sessionId as string, req.method, req.originalUrl, req.body)) {
307
+ return false;
308
+ }
309
+
310
+ return true;
311
+ }
312
+
313
+ function extractProtocol(driver: Core<any>, sessionId: string | null = null): keyof typeof PROTOCOLS {
314
+ const dstDriver = _.isFunction(driver.driverForSession) && sessionId
315
+ ? driver.driverForSession(sessionId)
316
+ : driver;
317
+ if (dstDriver === driver) {
318
+ // Shortcircuit if the driver instance is not an umbrella driver
319
+ // or it is Fake driver instance, where `driver.driverForSession`
320
+ // always returns self instance
321
+ return driver.protocol ?? PROTOCOLS.W3C;
322
+ }
323
+
324
+ // Extract the protocol for the current session if the given driver is the umbrella one
325
+ return dstDriver?.protocol ?? PROTOCOLS.W3C;
326
+ }
327
+
328
+ function getLogger(driver: Core<any>, sessionId: string | null = null): AppiumLogger {
329
+ const dstDriver =
330
+ sessionId && _.isFunction(driver.driverForSession)
331
+ ? driver.driverForSession(sessionId) ?? driver
332
+ : driver;
333
+ if (_.isFunction(dstDriver.log?.info)) {
334
+ return dstDriver.log;
335
+ }
336
+
337
+ const logPrefix = generateDriverLogPrefix(dstDriver);
338
+ return logger.getLogger(logPrefix);
339
+ }
340
+
341
+ function wrapParams<T>(paramSets, jsonObj: T): T | Record<string, T> {
342
+ /* There are commands like performTouch which take a single parameter (primitive type or array).
343
+ * Some drivers choose to pass this parameter as a value (eg. [action1, action2...]) while others to
344
+ * wrap it within an object(eg' {gesture: [action1, action2...]}), which makes it hard to validate.
345
+ * The wrap option in the spec enforce wrapping before validation, so that all params are wrapped at
346
+ * the time they are validated and later passed to the commands.
347
+ */
348
+ return (_.isArray(jsonObj) || !_.isObject(jsonObj)) && paramSets.wrap
349
+ ? {[paramSets.wrap]: jsonObj}
350
+ : jsonObj;
351
+ }
352
+
353
+ function unwrapParams<T>(paramSets: PayloadParams, jsonObj: T): T | Record<string, T> {
354
+ /* There are commands like setNetworkConnection which send parameters wrapped inside a key such as
355
+ * "parameters". This function unwraps them (eg. {"parameters": {"type": 1}} becomes {"type": 1}).
356
+ */
357
+ return _.isObject(jsonObj) && paramSets.unwrap && jsonObj[paramSets.unwrap]
358
+ ? jsonObj[paramSets.unwrap]
359
+ : jsonObj;
360
+ }
361
+
362
+
363
+ function hasMultipleRequiredParamSets(
364
+ required: ReadonlyArray<string> | MultidimensionalReadonlyArray<string, 2> | undefined
365
+ ): required is MultidimensionalReadonlyArray<string, 2> {
366
+ //@ts-expect-error Needed to convince lodash typechecks
367
+ return Boolean(required && _.isArray(_.first(required)));
368
+ }
369
+
370
+ function pickKnownParams(args: Record<string, any>, unknownNames: string[]): Record<string, any> {
371
+ if (_.isEmpty(unknownNames)) {
372
+ return args;
373
+ }
374
+ log.info(`The following arguments are not known and will be ignored: ${unknownNames}`);
375
+ return _.pickBy(args, (v, k) => !unknownNames.includes(k));
376
+ }
377
+
328
378
  function buildHandler(
329
379
  app: Application,
330
380
  method: string,
@@ -554,28 +604,6 @@ function buildHandler(
554
604
  });
555
605
  }
556
606
 
557
- export function driverShouldDoJwpProxy(driver: Core<any>, req: Request, command: string): boolean {
558
- const sessionId = getSessionId(driver, req);
559
- // drivers need to explicitly say when the proxy is active
560
- if (!driver.proxyActive(sessionId)) {
561
- return false;
562
- }
563
-
564
- // we should never proxy deleteSession because we need to give the containing
565
- // driver an opportunity to clean itself up
566
- if (command === DELETE_SESSION_COMMAND) {
567
- return false;
568
- }
569
-
570
- // validate avoidance schema, and say we shouldn't proxy if anything in the
571
- // avoid list matches our req
572
- if (driver.proxyRouteIsAvoided(sessionId as string, req.method, req.originalUrl, req.body)) {
573
- return false;
574
- }
575
-
576
- return true;
577
- }
578
-
579
607
  async function doJwpProxy(driver: BaseDriver<any>, req: Request, res: Response): Promise<void> {
580
608
  const sessionId = getSessionId(driver, req) as string;
581
609
  getLogger(driver, sessionId).info(
@@ -572,6 +572,12 @@ export const ALL_COMMANDS = _.flatMap(_.values(METHOD_MAP).map(_.values))
572
572
  .filter((m) => Boolean(m.command))
573
573
  .map((m) => m.command);
574
574
 
575
+ /**
576
+ * Resolve a WebDriver URL path and HTTP method to a driver command name from {@link METHOD_MAP}.
577
+ * @param endpoint - Request URL or path (may include base path)
578
+ * @param method - HTTP method (used when one path maps to multiple commands)
579
+ * @param basePath - Optional base path prefix to strip before matching
580
+ */
575
581
  export function routeToCommandName(
576
582
  endpoint: string,
577
583
  method?: HTTPMethod,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appium/base-driver",
3
- "version": "10.2.2",
3
+ "version": "10.4.0",
4
4
  "description": "Base driver class for Appium drivers",
5
5
  "keywords": [
6
6
  "automation",
@@ -42,27 +42,27 @@
42
42
  "test:e2e": "mocha --exit --timeout 20s --slow 10s \"./test/e2e/**/*.spec.ts\"",
43
43
  "test:smoke": "node ./index.js",
44
44
  "test:unit": "mocha \"./test/unit/**/*.spec.ts\"",
45
- "test:types": "tsd && tsc -p tsconfig.test.json"
45
+ "test:types": "tsd"
46
46
  },
47
47
  "dependencies": {
48
- "@appium/support": "^7.0.6",
49
- "@appium/types": "^1.2.1",
48
+ "@appium/support": "7.1.1",
49
+ "@appium/types": "1.3.1",
50
50
  "@colors/colors": "1.6.0",
51
51
  "async-lock": "1.4.1",
52
52
  "asyncbox": "6.1.0",
53
- "axios": "1.13.6",
53
+ "axios": "1.15.0",
54
54
  "bluebird": "3.7.2",
55
55
  "body-parser": "2.2.2",
56
56
  "express": "5.2.1",
57
57
  "fastest-levenshtein": "1.0.16",
58
58
  "http-status-codes": "2.3.0",
59
- "lodash": "4.17.23",
60
- "lru-cache": "11.2.6",
59
+ "lodash": "4.18.1",
60
+ "lru-cache": "11.3.5",
61
61
  "method-override": "3.0.0",
62
62
  "morgan": "1.10.1",
63
- "path-to-regexp": "8.3.0",
63
+ "path-to-regexp": "8.4.2",
64
64
  "serve-favicon": "2.5.1",
65
- "type-fest": "5.4.4"
65
+ "type-fest": "5.6.0"
66
66
  },
67
67
  "optionalDependencies": {
68
68
  "spdy": "4.0.2"
@@ -74,7 +74,7 @@
74
74
  "publishConfig": {
75
75
  "access": "public"
76
76
  },
77
- "gitHead": "c745352c6500937a4590d1ef6ef19785143a8870",
77
+ "gitHead": "17f84265d10944fec06ae7684ae8ad77d1224b50",
78
78
  "tsd": {
79
79
  "directory": "test/types"
80
80
  }
package/tsconfig.json CHANGED
@@ -1,5 +1,11 @@
1
1
  {
2
2
  "extends": "@appium/tsconfig/tsconfig.json",
3
+ "ts-node": {
4
+ "transpileOnly": true,
5
+ "compilerOptions": {
6
+ "rootDir": "."
7
+ }
8
+ },
3
9
  "compilerOptions": {
4
10
  "outDir": "build",
5
11
  "paths": {