@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.
- package/build/lib/basedriver/capabilities.d.ts.map +1 -1
- package/build/lib/basedriver/capabilities.js +45 -45
- package/build/lib/basedriver/capabilities.js.map +1 -1
- package/build/lib/basedriver/commands/bidi.d.ts.map +1 -1
- package/build/lib/basedriver/commands/bidi.js +9 -13
- package/build/lib/basedriver/commands/bidi.js.map +1 -1
- package/build/lib/basedriver/commands/event.d.ts.map +1 -1
- package/build/lib/basedriver/commands/event.js +4 -7
- package/build/lib/basedriver/commands/event.js.map +1 -1
- package/build/lib/basedriver/commands/execute.js +3 -6
- package/build/lib/basedriver/commands/execute.js.map +1 -1
- package/build/lib/basedriver/commands/log.d.ts.map +1 -1
- package/build/lib/basedriver/commands/log.js +1 -5
- package/build/lib/basedriver/commands/log.js.map +1 -1
- package/build/lib/basedriver/commands/timeout.d.ts.map +1 -1
- package/build/lib/basedriver/commands/timeout.js +5 -9
- package/build/lib/basedriver/commands/timeout.js.map +1 -1
- package/build/lib/basedriver/core.js +12 -12
- package/build/lib/basedriver/core.js.map +1 -1
- package/build/lib/basedriver/device-settings.d.ts.map +1 -1
- package/build/lib/basedriver/device-settings.js +3 -7
- package/build/lib/basedriver/device-settings.js.map +1 -1
- package/build/lib/basedriver/driver.d.ts.map +1 -1
- package/build/lib/basedriver/driver.js +13 -16
- package/build/lib/basedriver/driver.js.map +1 -1
- package/build/lib/basedriver/extension-core.d.ts +4 -1
- package/build/lib/basedriver/extension-core.d.ts.map +1 -1
- package/build/lib/basedriver/extension-core.js +27 -9
- package/build/lib/basedriver/extension-core.js.map +1 -1
- package/build/lib/basedriver/helpers.d.ts.map +1 -1
- package/build/lib/basedriver/helpers.js +28 -30
- package/build/lib/basedriver/helpers.js.map +1 -1
- package/build/lib/basedriver/ipc.d.ts +36 -0
- package/build/lib/basedriver/ipc.d.ts.map +1 -0
- package/build/lib/basedriver/ipc.js +155 -0
- package/build/lib/basedriver/ipc.js.map +1 -0
- package/build/lib/basedriver/validation.js +25 -28
- package/build/lib/basedriver/validation.js.map +1 -1
- package/build/lib/express/express-logging.d.ts.map +1 -1
- package/build/lib/express/express-logging.js +2 -3
- package/build/lib/express/express-logging.js.map +1 -1
- package/build/lib/express/idempotency.js +3 -6
- package/build/lib/express/idempotency.js.map +1 -1
- package/build/lib/express/middleware.d.ts.map +1 -1
- package/build/lib/express/middleware.js +6 -10
- package/build/lib/express/middleware.js.map +1 -1
- package/build/lib/express/server.d.ts.map +1 -1
- package/build/lib/express/server.js +64 -54
- package/build/lib/express/server.js.map +1 -1
- package/build/lib/express/static.d.ts.map +1 -1
- package/build/lib/express/static.js +14 -7
- package/build/lib/express/static.js.map +1 -1
- package/build/lib/express/websocket.d.ts.map +1 -1
- package/build/lib/express/websocket.js +6 -9
- package/build/lib/express/websocket.js.map +1 -1
- package/build/lib/helpers/capabilities.d.ts.map +1 -1
- package/build/lib/helpers/capabilities.js +14 -17
- package/build/lib/helpers/capabilities.js.map +1 -1
- package/build/lib/helpers/extension-command-name.js +2 -5
- package/build/lib/helpers/extension-command-name.js.map +1 -1
- package/build/lib/helpers/levenshtein-match.d.ts.map +1 -1
- package/build/lib/helpers/levenshtein-match.js +2 -6
- package/build/lib/helpers/levenshtein-match.js.map +1 -1
- package/build/lib/index.d.ts +1 -0
- package/build/lib/index.d.ts.map +1 -1
- package/build/lib/index.js +3 -14
- package/build/lib/index.js.map +1 -1
- package/build/lib/jsonwp-proxy/protocol-converter.d.ts.map +1 -1
- package/build/lib/jsonwp-proxy/protocol-converter.js +13 -17
- package/build/lib/jsonwp-proxy/protocol-converter.js.map +1 -1
- package/build/lib/jsonwp-proxy/proxy-request.d.ts +2 -2
- package/build/lib/jsonwp-proxy/proxy-request.d.ts.map +1 -1
- package/build/lib/jsonwp-proxy/proxy-request.js +25 -21
- package/build/lib/jsonwp-proxy/proxy-request.js.map +1 -1
- package/build/lib/jsonwp-proxy/proxy.d.ts.map +1 -1
- package/build/lib/jsonwp-proxy/proxy.js +29 -26
- package/build/lib/jsonwp-proxy/proxy.js.map +1 -1
- package/build/lib/protocol/errors.d.ts.map +1 -1
- package/build/lib/protocol/errors.js +25 -29
- package/build/lib/protocol/errors.js.map +1 -1
- package/build/lib/protocol/helpers.d.ts.map +1 -1
- package/build/lib/protocol/helpers.js +9 -8
- package/build/lib/protocol/helpers.js.map +1 -1
- package/build/lib/protocol/protocol.d.ts.map +1 -1
- package/build/lib/protocol/protocol.js +43 -48
- package/build/lib/protocol/protocol.js.map +1 -1
- package/build/lib/protocol/routes.d.ts +1 -1
- package/build/lib/protocol/routes.d.ts.map +1 -1
- package/build/lib/protocol/routes.js +9 -12
- package/build/lib/protocol/routes.js.map +1 -1
- package/build/lib/protocol/validators.d.ts.map +1 -1
- package/build/lib/protocol/validators.js +1 -5
- package/build/lib/protocol/validators.js.map +1 -1
- package/build/lib/utils.d.ts +16 -0
- package/build/lib/utils.d.ts.map +1 -0
- package/build/lib/utils.js +71 -0
- package/build/lib/utils.js.map +1 -0
- package/lib/basedriver/capabilities.ts +60 -55
- package/lib/basedriver/commands/bidi.ts +10 -10
- package/lib/basedriver/commands/event.ts +11 -10
- package/lib/basedriver/commands/execute.ts +3 -3
- package/lib/basedriver/commands/log.ts +3 -2
- package/lib/basedriver/commands/timeout.ts +5 -6
- package/lib/basedriver/core.ts +12 -12
- package/lib/basedriver/device-settings.ts +3 -4
- package/lib/basedriver/driver.ts +15 -13
- package/lib/basedriver/extension-core.ts +33 -7
- package/lib/basedriver/helpers.ts +28 -30
- package/lib/basedriver/ipc.ts +179 -0
- package/lib/basedriver/validation.ts +26 -26
- package/lib/express/express-logging.ts +3 -4
- package/lib/express/idempotency.ts +3 -3
- package/lib/express/middleware.ts +6 -8
- package/lib/express/server.ts +67 -61
- package/lib/express/static.ts +15 -7
- package/lib/express/websocket.ts +8 -10
- package/lib/helpers/capabilities.ts +18 -14
- package/lib/helpers/extension-command-name.ts +2 -2
- package/lib/helpers/levenshtein-match.ts +2 -5
- package/lib/index.js +1 -11
- package/lib/jsonwp-proxy/protocol-converter.ts +14 -15
- package/lib/jsonwp-proxy/proxy-request.ts +26 -26
- package/lib/jsonwp-proxy/proxy.ts +36 -37
- package/lib/protocol/errors.ts +29 -28
- package/lib/protocol/helpers.ts +9 -5
- package/lib/protocol/protocol.ts +44 -46
- package/lib/protocol/routes.ts +9 -9
- package/lib/protocol/validators.ts +1 -3
- package/lib/utils.ts +85 -0
- package/package.json +7 -9
package/lib/protocol/protocol.ts
CHANGED
|
@@ -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
|
|
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 !
|
|
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[] =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 (!
|
|
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 (!
|
|
123
|
+
if (!optionalParams.includes('id')) {
|
|
125
124
|
optionalParams.push('id');
|
|
126
125
|
}
|
|
127
126
|
|
|
128
|
-
if (
|
|
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,
|
|
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 (!
|
|
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 (
|
|
142
|
+
if (requiredParamsSet.every((name) => actualParamNames.includes(name))) {
|
|
144
143
|
return pickKnownParams(
|
|
145
144
|
args,
|
|
146
|
-
|
|
145
|
+
actualParamNames.filter((name) => !requiredParamsSet.includes(name) && !optionalParams.includes(name))
|
|
147
146
|
);
|
|
148
147
|
}
|
|
149
|
-
if (!
|
|
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 =
|
|
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 =
|
|
182
|
+
const keys = Object.keys(jsonObj);
|
|
184
183
|
for (const params of payloadParams.required) {
|
|
185
|
-
if (
|
|
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 (
|
|
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 =
|
|
204
|
+
args = (requiredParams ?? []).flat().map((p) => jsonObj[p]);
|
|
206
205
|
if (payloadParams.optional) {
|
|
207
|
-
args = args.concat(
|
|
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 || !
|
|
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 (!
|
|
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
|
|
269
|
-
for (const [method, spec] of
|
|
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 =
|
|
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 &&
|
|
329
|
+
sessionId && typeof driver.driverForSession === 'function'
|
|
331
330
|
? driver.driverForSession(sessionId) ?? driver
|
|
332
331
|
: driver;
|
|
333
|
-
if (
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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) ||
|
|
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(
|
|
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 (
|
|
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: ${
|
|
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 (
|
|
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: ${
|
|
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 || (
|
|
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 (!
|
|
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 (
|
|
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
|
-
|
|
601
|
+
void asyncHandler(req, res);
|
|
604
602
|
});
|
|
605
603
|
}
|
|
606
604
|
|
package/lib/protocol/routes.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type {Driver, DriverMethodDef, HTTPMethod, MethodMap} from '@appium/types';
|
|
2
|
-
import
|
|
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 =
|
|
583
|
-
.
|
|
584
|
-
.
|
|
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(`^${
|
|
599
|
+
? endpoint.replace(new RegExp(`^${util.escapeRegExp(resolvedBasePath)}`), '')
|
|
600
600
|
: endpoint;
|
|
601
|
-
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
49
|
-
"@appium/types": "1.
|
|
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.
|
|
53
|
-
"axios": "1.16.
|
|
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
|
-
"
|
|
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": "
|
|
75
|
+
"gitHead": "8018f8b70ecc975fa115e19072ef337b8473865d",
|
|
78
76
|
"tsd": {
|
|
79
77
|
"directory": "test/types"
|
|
80
78
|
}
|