@appium/base-driver 10.0.0-beta.0 → 10.0.0-beta.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.
- package/README.md +0 -8
- package/build/lib/basedriver/capabilities.d.ts.map +1 -1
- package/build/lib/basedriver/capabilities.js +2 -4
- package/build/lib/basedriver/capabilities.js.map +1 -1
- package/build/lib/basedriver/commands/execute.js.map +1 -1
- package/build/lib/basedriver/commands/timeout.js +12 -31
- package/build/lib/basedriver/commands/timeout.js.map +1 -1
- package/build/lib/basedriver/core.d.ts +0 -8
- package/build/lib/basedriver/core.d.ts.map +1 -1
- package/build/lib/basedriver/core.js +8 -18
- package/build/lib/basedriver/core.js.map +1 -1
- package/build/lib/basedriver/driver.js +2 -2
- package/build/lib/basedriver/driver.js.map +1 -1
- package/build/lib/basedriver/helpers.d.ts +9 -1
- package/build/lib/basedriver/helpers.d.ts.map +1 -1
- package/build/lib/basedriver/helpers.js +56 -142
- package/build/lib/basedriver/helpers.js.map +1 -1
- package/build/lib/basedriver/validation.d.ts +7 -0
- package/build/lib/basedriver/validation.d.ts.map +1 -0
- package/build/lib/basedriver/validation.js +130 -0
- package/build/lib/basedriver/validation.js.map +1 -0
- package/build/lib/express/middleware.d.ts +0 -6
- package/build/lib/express/middleware.d.ts.map +1 -1
- package/build/lib/express/middleware.js +28 -60
- 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 +0 -1
- package/build/lib/express/server.js.map +1 -1
- package/build/lib/helpers/capabilities.d.ts +13 -6
- package/build/lib/helpers/capabilities.d.ts.map +1 -1
- package/build/lib/helpers/capabilities.js +7 -0
- package/build/lib/helpers/capabilities.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 -1
- package/build/lib/index.js.map +1 -1
- package/build/lib/jsonwp-proxy/proxy.d.ts +0 -8
- package/build/lib/jsonwp-proxy/proxy.d.ts.map +1 -1
- package/build/lib/jsonwp-proxy/proxy.js +7 -38
- package/build/lib/jsonwp-proxy/proxy.js.map +1 -1
- package/build/lib/protocol/errors.d.ts +171 -277
- package/build/lib/protocol/errors.d.ts.map +1 -1
- package/build/lib/protocol/errors.js +201 -421
- package/build/lib/protocol/errors.js.map +1 -1
- package/build/lib/protocol/helpers.d.ts +6 -6
- package/build/lib/protocol/helpers.d.ts.map +1 -1
- package/build/lib/protocol/helpers.js +11 -7
- package/build/lib/protocol/helpers.js.map +1 -1
- package/build/lib/protocol/index.d.ts +2 -1
- package/build/lib/protocol/index.d.ts.map +1 -1
- package/build/lib/protocol/index.js +2 -1
- package/build/lib/protocol/index.js.map +1 -1
- package/build/lib/protocol/protocol.d.ts +16 -19
- package/build/lib/protocol/protocol.d.ts.map +1 -1
- package/build/lib/protocol/protocol.js +98 -119
- package/build/lib/protocol/protocol.js.map +1 -1
- package/build/lib/protocol/routes.d.ts +12 -714
- package/build/lib/protocol/routes.d.ts.map +1 -1
- package/build/lib/protocol/routes.js +24 -488
- package/build/lib/protocol/routes.js.map +1 -1
- package/build/lib/protocol/validators.d.ts +4 -7
- package/build/lib/protocol/validators.d.ts.map +1 -1
- package/build/lib/protocol/validators.js +4 -21
- package/build/lib/protocol/validators.js.map +1 -1
- package/lib/basedriver/capabilities.ts +2 -4
- package/lib/basedriver/commands/execute.ts +1 -1
- package/lib/basedriver/commands/timeout.ts +16 -43
- package/lib/basedriver/core.ts +10 -19
- package/lib/basedriver/driver.ts +3 -3
- package/lib/basedriver/helpers.js +61 -167
- package/lib/basedriver/validation.ts +145 -0
- package/lib/express/middleware.js +32 -70
- package/lib/express/server.js +0 -2
- package/lib/helpers/capabilities.js +9 -4
- package/lib/index.js +2 -0
- package/lib/jsonwp-proxy/proxy.js +8 -45
- package/lib/protocol/{errors.js → errors.ts} +322 -436
- package/lib/protocol/helpers.js +12 -8
- package/lib/protocol/index.js +8 -1
- package/lib/protocol/{protocol.js → protocol.ts} +147 -146
- package/lib/protocol/routes.js +26 -498
- package/lib/protocol/validators.ts +19 -0
- package/package.json +10 -11
- package/build/lib/basedriver/desired-caps.d.ts +0 -5
- package/build/lib/basedriver/desired-caps.d.ts.map +0 -1
- package/build/lib/basedriver/desired-caps.js +0 -92
- package/build/lib/basedriver/desired-caps.js.map +0 -1
- package/lib/basedriver/README.md +0 -36
- package/lib/basedriver/desired-caps.js +0 -103
- package/lib/express/README.md +0 -59
- package/lib/jsonwp-proxy/README.md +0 -52
- package/lib/jsonwp-status/README.md +0 -20
- package/lib/protocol/README.md +0 -100
- package/lib/protocol/validators.js +0 -38
package/lib/protocol/helpers.js
CHANGED
|
@@ -7,17 +7,19 @@ import {MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY} from '../constants';
|
|
|
7
7
|
* so they have keys for both W3C and JSONWP protocols.
|
|
8
8
|
* The argument value is NOT mutated
|
|
9
9
|
*
|
|
10
|
-
* @param {
|
|
11
|
-
* @returns {
|
|
10
|
+
* @param {Object | undefined} resValue The actual response value
|
|
11
|
+
* @returns {Object | null} Either modified value or the same one if
|
|
12
12
|
* nothing has been modified
|
|
13
13
|
*/
|
|
14
|
-
function formatResponseValue(resValue) {
|
|
14
|
+
export function formatResponseValue(resValue) {
|
|
15
15
|
if (_.isUndefined(resValue)) {
|
|
16
16
|
// convert undefined to null
|
|
17
17
|
return null;
|
|
18
18
|
}
|
|
19
|
-
// If the MJSONWP element key format (ELEMENT) was provided,
|
|
20
|
-
//
|
|
19
|
+
// If the MJSONWP element key format (ELEMENT) was provided,
|
|
20
|
+
// add a duplicate key (element-6066-11e4-a52e-4f735466cecf)
|
|
21
|
+
// If the W3C element key format (element-6066-11e4-a52e-4f735466cecf)
|
|
22
|
+
// was provided, add a duplicate key (ELEMENT)
|
|
21
23
|
return duplicateKeys(resValue, MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY);
|
|
22
24
|
}
|
|
23
25
|
|
|
@@ -28,8 +30,10 @@ function formatResponseValue(resValue) {
|
|
|
28
30
|
* @param {Object} responseBody
|
|
29
31
|
* @returns {Object} The fixed response body
|
|
30
32
|
*/
|
|
31
|
-
function
|
|
32
|
-
return _.isPlainObject(responseBody)
|
|
33
|
+
export function ensureW3cResponse(responseBody) {
|
|
34
|
+
return _.isPlainObject(responseBody)
|
|
35
|
+
? _.omit(responseBody, ['status', 'sessionId'])
|
|
36
|
+
: responseBody;
|
|
33
37
|
}
|
|
34
38
|
|
|
35
|
-
export {MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY
|
|
39
|
+
export {MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY};
|
package/lib/protocol/index.js
CHANGED
|
@@ -12,7 +12,13 @@ import {
|
|
|
12
12
|
validateExecuteMethodParams,
|
|
13
13
|
} from './protocol';
|
|
14
14
|
import {NO_SESSION_ID_COMMANDS, ALL_COMMANDS, METHOD_MAP, routeToCommandName} from './routes';
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
errors,
|
|
17
|
+
isErrorType,
|
|
18
|
+
errorFromMJSONWPStatusCode,
|
|
19
|
+
errorFromW3CJsonCode,
|
|
20
|
+
getResponseForW3CError,
|
|
21
|
+
} from './errors';
|
|
16
22
|
|
|
17
23
|
export {
|
|
18
24
|
routeConfiguringFunction,
|
|
@@ -34,4 +40,5 @@ export {
|
|
|
34
40
|
GET_STATUS_COMMAND,
|
|
35
41
|
LIST_DRIVER_COMMANDS_COMMAND,
|
|
36
42
|
LIST_DRIVER_EXTENSIONS_COMMAND,
|
|
43
|
+
getResponseForW3CError,
|
|
37
44
|
};
|
|
@@ -7,14 +7,20 @@ import {
|
|
|
7
7
|
getResponseForW3CError,
|
|
8
8
|
errorFromMJSONWPStatusCode,
|
|
9
9
|
errorFromW3CJsonCode,
|
|
10
|
+
BadParametersError,
|
|
10
11
|
} from './errors';
|
|
11
12
|
import {METHOD_MAP, NO_SESSION_ID_COMMANDS} from './routes';
|
|
12
13
|
import B from 'bluebird';
|
|
13
|
-
import {formatResponseValue,
|
|
14
|
+
import {formatResponseValue, ensureW3cResponse} from './helpers';
|
|
14
15
|
import {MAX_LOG_BODY_LENGTH, PROTOCOLS, DEFAULT_BASE_PATH} from '../constants';
|
|
15
16
|
import {isW3cCaps} from '../helpers/capabilities';
|
|
16
17
|
import log from '../basedriver/logger';
|
|
17
18
|
import { generateDriverLogPrefix } from '../basedriver/helpers';
|
|
19
|
+
import type { Core, AppiumLogger, PayloadParams, MethodMap, Driver, DriverMethodDef } from '@appium/types';
|
|
20
|
+
import type { BaseDriver } from '../basedriver/driver';
|
|
21
|
+
import type { Request, Response, Application } from 'express';
|
|
22
|
+
import type { MultidimensionalReadonlyArray } from 'type-fest';
|
|
23
|
+
import type { RouteConfiguringFunction } from '../express/server';
|
|
18
24
|
|
|
19
25
|
export const CREATE_SESSION_COMMAND = 'createSession';
|
|
20
26
|
export const DELETE_SESSION_COMMAND = 'deleteSession';
|
|
@@ -22,39 +28,33 @@ export const GET_STATUS_COMMAND = 'getStatus';
|
|
|
22
28
|
export const LIST_DRIVER_COMMANDS_COMMAND = 'listCommands';
|
|
23
29
|
export const LIST_DRIVER_EXTENSIONS_COMMAND = 'listExtensions';
|
|
24
30
|
|
|
25
|
-
|
|
26
|
-
const deprecatedCommandsLogged = new Set();
|
|
31
|
+
export const deprecatedCommandsLogged: Set<string> = new Set();
|
|
27
32
|
|
|
28
|
-
function determineProtocol(createSessionArgs) {
|
|
33
|
+
export function determineProtocol(createSessionArgs: any[]): keyof typeof PROTOCOLS {
|
|
29
34
|
return _.some(createSessionArgs, isW3cCaps) ? PROTOCOLS.W3C : PROTOCOLS.MJSONWP;
|
|
30
35
|
}
|
|
31
36
|
|
|
32
|
-
|
|
33
|
-
|
|
37
|
+
|
|
38
|
+
function extractProtocol(driver: Core<any>, sessionId: string | null = null): keyof typeof PROTOCOLS {
|
|
39
|
+
const dstDriver = _.isFunction(driver.driverForSession) && sessionId
|
|
34
40
|
? driver.driverForSession(sessionId)
|
|
35
41
|
: driver;
|
|
36
42
|
if (dstDriver === driver) {
|
|
37
43
|
// Shortcircuit if the driver instance is not an umbrella driver
|
|
38
44
|
// or it is Fake driver instance, where `driver.driverForSession`
|
|
39
45
|
// always returns self instance
|
|
40
|
-
return driver.protocol;
|
|
46
|
+
return driver.protocol ?? PROTOCOLS.W3C;
|
|
41
47
|
}
|
|
42
48
|
|
|
43
49
|
// Extract the protocol for the current session if the given driver is the umbrella one
|
|
44
50
|
return dstDriver?.protocol ?? PROTOCOLS.W3C;
|
|
45
51
|
}
|
|
46
52
|
|
|
47
|
-
function isSessionCommand(command) {
|
|
53
|
+
export function isSessionCommand(command: string): boolean {
|
|
48
54
|
return !_.includes(NO_SESSION_ID_COMMANDS, command);
|
|
49
55
|
}
|
|
50
56
|
|
|
51
|
-
|
|
52
|
-
*
|
|
53
|
-
* @param {import('@appium/types').ExternalDriver} driver
|
|
54
|
-
* @param {string?} [sessionId]
|
|
55
|
-
* @returns {import('@appium/types').AppiumLogger}
|
|
56
|
-
*/
|
|
57
|
-
function getLogger(driver, sessionId = null) {
|
|
57
|
+
function getLogger(driver: Core<any>, sessionId: string | null = null): AppiumLogger {
|
|
58
58
|
const dstDriver =
|
|
59
59
|
sessionId && _.isFunction(driver.driverForSession)
|
|
60
60
|
? driver.driverForSession(sessionId) ?? driver
|
|
@@ -67,94 +67,116 @@ function getLogger(driver, sessionId = null) {
|
|
|
67
67
|
return logger.getLogger(logPrefix);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
function wrapParams(paramSets, jsonObj) {
|
|
70
|
+
function wrapParams<T>(paramSets, jsonObj: T): T | Record<string, T> {
|
|
71
71
|
/* There are commands like performTouch which take a single parameter (primitive type or array).
|
|
72
72
|
* Some drivers choose to pass this parameter as a value (eg. [action1, action2...]) while others to
|
|
73
73
|
* wrap it within an object(eg' {gesture: [action1, action2...]}), which makes it hard to validate.
|
|
74
74
|
* The wrap option in the spec enforce wrapping before validation, so that all params are wrapped at
|
|
75
75
|
* the time they are validated and later passed to the commands.
|
|
76
76
|
*/
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
res[paramSets.wrap] = jsonObj;
|
|
81
|
-
}
|
|
82
|
-
return res;
|
|
77
|
+
return (_.isArray(jsonObj) || !_.isObject(jsonObj)) && paramSets.wrap
|
|
78
|
+
? {[paramSets.wrap]: jsonObj}
|
|
79
|
+
: jsonObj;
|
|
83
80
|
}
|
|
84
81
|
|
|
85
|
-
function unwrapParams(paramSets, jsonObj) {
|
|
82
|
+
function unwrapParams<T>(paramSets: PayloadParams, jsonObj: T): T | Record<string, T> {
|
|
86
83
|
/* There are commands like setNetworkConnection which send parameters wrapped inside a key such as
|
|
87
84
|
* "parameters". This function unwraps them (eg. {"parameters": {"type": 1}} becomes {"type": 1}).
|
|
88
85
|
*/
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (jsonObj[paramSets.unwrap]) {
|
|
93
|
-
res = jsonObj[paramSets.unwrap];
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
return res;
|
|
86
|
+
return _.isObject(jsonObj) && paramSets.unwrap && jsonObj[paramSets.unwrap]
|
|
87
|
+
? jsonObj[paramSets.unwrap]
|
|
88
|
+
: jsonObj;
|
|
97
89
|
}
|
|
98
90
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (paramSets.required) {
|
|
106
|
-
// we might have an array of parameters,
|
|
107
|
-
// or an array of arrays of parameters, so standardize
|
|
108
|
-
if (!_.isArray(_.first(paramSets.required))) {
|
|
109
|
-
requiredParams = [paramSets.required];
|
|
110
|
-
} else {
|
|
111
|
-
requiredParams = paramSets.required;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
// optional parameters are just an array
|
|
115
|
-
if (paramSets.optional) {
|
|
116
|
-
optionalParams = paramSets.optional;
|
|
117
|
-
}
|
|
91
|
+
function hasMultipleRequiredParamSets(
|
|
92
|
+
required: ReadonlyArray<string> | MultidimensionalReadonlyArray<string, 2> | undefined
|
|
93
|
+
): required is MultidimensionalReadonlyArray<string, 2> {
|
|
94
|
+
//@ts-expect-error Needed to convince lodash typechecks
|
|
95
|
+
return Boolean(required && _.isArray(_.first(required)));
|
|
96
|
+
}
|
|
118
97
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
// argument to an error which is thrown to the user
|
|
123
|
-
if (paramSets.validate) {
|
|
124
|
-
let message = paramSets.validate(jsonObj, protocol);
|
|
125
|
-
if (message) {
|
|
126
|
-
throw new errors.BadParametersError(message, jsonObj);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
98
|
+
function pickKnownParams(args: Record<string, any>, unknownNames: string[]): Record<string, any> {
|
|
99
|
+
if (_.isEmpty(unknownNames)) {
|
|
100
|
+
return args;
|
|
129
101
|
}
|
|
102
|
+
log.info(`The following arguments are not known and will be ignored: ${unknownNames}`);
|
|
103
|
+
return _.pickBy(args, (v, k) => !unknownNames.includes(k));
|
|
104
|
+
}
|
|
130
105
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
106
|
+
export function checkParams(
|
|
107
|
+
paramSpec: PayloadParams,
|
|
108
|
+
args: Record<string, any>,
|
|
109
|
+
protocol?: keyof typeof PROTOCOLS
|
|
110
|
+
): Record<string, any> {
|
|
111
|
+
let requiredParams: string[][] = [];
|
|
112
|
+
let optionalParams: string[] = [];
|
|
113
|
+
const actualParamNames: string[] = _.keys(args);
|
|
114
|
+
|
|
115
|
+
if (paramSpec.required) {
|
|
116
|
+
// we might have an array of parameters,
|
|
117
|
+
// or an array of arrays of parameters, so standardize
|
|
118
|
+
requiredParams = _.cloneDeep(
|
|
119
|
+
(hasMultipleRequiredParamSets(paramSpec.required)
|
|
120
|
+
? paramSpec.required
|
|
121
|
+
: [paramSpec.required]
|
|
122
|
+
) as string[][]
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
// optional parameters are just an array
|
|
126
|
+
if (paramSpec.optional) {
|
|
127
|
+
optionalParams = _.cloneDeep(paramSpec.optional as string[]);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// If a function was provided as the 'validate' key, it will here be called with
|
|
131
|
+
// args as the param. If it returns something falsy, verification will be
|
|
132
|
+
// considered to have passed. If it returns something else, that will be the
|
|
133
|
+
// argument to an error which is thrown to the user
|
|
134
|
+
if (paramSpec.validate) {
|
|
135
|
+
const message = paramSpec.validate(args, protocol ?? PROTOCOLS.W3C);
|
|
136
|
+
if (message) {
|
|
137
|
+
throw new errors.InvalidArgumentError(_.isString(message) ? message : undefined);
|
|
138
|
+
}
|
|
134
139
|
}
|
|
135
140
|
|
|
136
141
|
// some clients pass in the session id in the params
|
|
137
|
-
if (
|
|
142
|
+
if (!_.includes(optionalParams, 'sessionId')) {
|
|
138
143
|
optionalParams.push('sessionId');
|
|
139
144
|
}
|
|
140
|
-
|
|
141
145
|
// some clients pass in an element id in the params
|
|
142
|
-
if (
|
|
146
|
+
if (!_.includes(optionalParams, 'id')) {
|
|
143
147
|
optionalParams.push('id');
|
|
144
148
|
}
|
|
145
149
|
|
|
150
|
+
if (_.isEmpty(requiredParams)) {
|
|
151
|
+
// if we don't have any required parameters, then just filter out unknown ones
|
|
152
|
+
return pickKnownParams(args, _.difference(actualParamNames, optionalParams));
|
|
153
|
+
}
|
|
154
|
+
|
|
146
155
|
// go through the required parameters and check against our arguments
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
156
|
+
let matchedReqParamSet: string[] = [];
|
|
157
|
+
for (const requiredParamsSet of requiredParams) {
|
|
158
|
+
if (!_.isArray(requiredParamsSet)) {
|
|
159
|
+
throw new Error(
|
|
160
|
+
`The required parameter set item ${JSON.stringify(requiredParamsSet)} ` +
|
|
161
|
+
`in ${JSON.stringify(paramSpec)} is not an array. ` +
|
|
162
|
+
`This is a bug in the method map definition.`
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
if (_.isEmpty(_.difference(requiredParamsSet, actualParamNames))) {
|
|
166
|
+
return pickKnownParams(
|
|
167
|
+
args,
|
|
168
|
+
_.difference(actualParamNames, requiredParamsSet, optionalParams)
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
if (!_.isEmpty(requiredParamsSet) && _.isEmpty(matchedReqParamSet)) {
|
|
172
|
+
matchedReqParamSet = requiredParamsSet;
|
|
155
173
|
}
|
|
156
174
|
}
|
|
157
|
-
throw new
|
|
175
|
+
throw new BadParametersError({
|
|
176
|
+
...paramSpec,
|
|
177
|
+
required: matchedReqParamSet,
|
|
178
|
+
optional: optionalParams,
|
|
179
|
+
}, actualParamNames);
|
|
158
180
|
}
|
|
159
181
|
|
|
160
182
|
/*
|
|
@@ -164,25 +186,25 @@ export function checkParams(paramSets, jsonObj, protocol) {
|
|
|
164
186
|
* on handling parameters. This method returns an array of arguments which will
|
|
165
187
|
* be applied to a command.
|
|
166
188
|
*/
|
|
167
|
-
export function makeArgs(requestParams, jsonObj, payloadParams
|
|
189
|
+
export function makeArgs(requestParams: PayloadParams, jsonObj: any, payloadParams: PayloadParams): any[] {
|
|
168
190
|
// We want to pass the "url" parameters to the commands in reverse order
|
|
169
191
|
// since the command will sometimes want to ignore, say, the sessionId.
|
|
170
192
|
// This has the effect of putting sessionId last, which means in JS we can
|
|
171
193
|
// omit it from the function signature if we're not going to use it.
|
|
172
|
-
|
|
194
|
+
const urlParams = _.keys(requestParams).reverse();
|
|
173
195
|
|
|
174
196
|
// In the simple case, the required parameters are a basic array in
|
|
175
197
|
// payloadParams.required, so start there. It's possible that there are
|
|
176
198
|
// multiple optional sets of required params, though, so handle that case
|
|
177
199
|
// too.
|
|
178
200
|
let requiredParams = payloadParams.required;
|
|
179
|
-
if (
|
|
201
|
+
if (hasMultipleRequiredParamSets(payloadParams.required)) {
|
|
180
202
|
// If there are optional sets of required params, then we will have an
|
|
181
203
|
// array of arrays in payloadParams.required, so loop through each set and
|
|
182
204
|
// pick the one that matches which JSON params were actually sent. We've
|
|
183
205
|
// already been through validation so we're guaranteed to find a match.
|
|
184
|
-
|
|
185
|
-
for (
|
|
206
|
+
const keys = _.keys(jsonObj);
|
|
207
|
+
for (const params of payloadParams.required) {
|
|
186
208
|
if (_.without(params, ...keys).length === 0) {
|
|
187
209
|
requiredParams = params;
|
|
188
210
|
break;
|
|
@@ -199,7 +221,7 @@ export function makeArgs(requestParams, jsonObj, payloadParams, protocol) {
|
|
|
199
221
|
// which will be applied to the handling command. For example if it returns
|
|
200
222
|
// [1, 2, 3], we will call `command(1, 2, 3, ...)` (url params are separate
|
|
201
223
|
// from JSON params and get concatenated below).
|
|
202
|
-
args = payloadParams.makeArgs(jsonObj
|
|
224
|
+
args = payloadParams.makeArgs(jsonObj);
|
|
203
225
|
} else {
|
|
204
226
|
// Otherwise, collect all the required and optional params and flatten them
|
|
205
227
|
// into an argument array
|
|
@@ -214,7 +236,7 @@ export function makeArgs(requestParams, jsonObj, payloadParams, protocol) {
|
|
|
214
236
|
return args;
|
|
215
237
|
}
|
|
216
238
|
|
|
217
|
-
function validateExecuteMethodParams(params, paramSpec) {
|
|
239
|
+
export function validateExecuteMethodParams(params: any[], paramSpec?: PayloadParams): any[] {
|
|
218
240
|
// the w3c protocol will give us an array of arguments to apply to a javascript function.
|
|
219
241
|
// that's not what we're doing. we're going to look for a JS object as the first arg, so we
|
|
220
242
|
// can perform validation on it. we'll ignore everything else.
|
|
@@ -224,40 +246,29 @@ function validateExecuteMethodParams(params, paramSpec) {
|
|
|
224
246
|
`arguments to execute script and instead received: ${JSON.stringify(params)}`
|
|
225
247
|
);
|
|
226
248
|
}
|
|
227
|
-
|
|
249
|
+
const args: Record<string, any> = params[0] ?? {};
|
|
228
250
|
if (!_.isPlainObject(args)) {
|
|
229
251
|
throw new errors.InvalidArgumentError(
|
|
230
252
|
`Did not receive an appropriate execute method parameters object. It needs to be ` +
|
|
231
253
|
`deserializable as a plain JS object`
|
|
232
254
|
);
|
|
233
255
|
}
|
|
234
|
-
|
|
235
|
-
paramSpec
|
|
236
|
-
|
|
237
|
-
paramSpec
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
log.info(`The following script arguments are not known and will be ignored: ${unknownNames}`);
|
|
242
|
-
args = _.pickBy(args, (v, k) => !unknownNames.includes(k));
|
|
243
|
-
}
|
|
244
|
-
checkParams(paramSpec, args, null);
|
|
245
|
-
}
|
|
246
|
-
const argsToApply = makeArgs({}, args, paramSpec, null);
|
|
247
|
-
return argsToApply;
|
|
256
|
+
const specToUse = {
|
|
257
|
+
...(paramSpec ?? {}),
|
|
258
|
+
required: paramSpec?.required ?? [],
|
|
259
|
+
optional: paramSpec?.optional ?? [],
|
|
260
|
+
};
|
|
261
|
+
const filteredArgs = checkParams(specToUse, args);
|
|
262
|
+
return makeArgs({}, filteredArgs, specToUse);
|
|
248
263
|
}
|
|
249
264
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
* @param {import('@appium/types').Core} driver
|
|
253
|
-
* @returns {import('../express/server').RouteConfiguringFunction}
|
|
254
|
-
*/
|
|
255
|
-
function routeConfiguringFunction(driver) {
|
|
265
|
+
|
|
266
|
+
export function routeConfiguringFunction(driver: Core<any>): RouteConfiguringFunction {
|
|
256
267
|
if (!driver.sessionExists) {
|
|
257
268
|
throw new Error('Drivers must implement `sessionExists` property');
|
|
258
269
|
}
|
|
259
270
|
|
|
260
|
-
if (!(
|
|
271
|
+
if (!((driver as any).executeCommand || (driver as any).execute)) {
|
|
261
272
|
throw new Error('Drivers must implement `executeCommand` or `execute` method');
|
|
262
273
|
}
|
|
263
274
|
|
|
@@ -268,10 +279,10 @@ function routeConfiguringFunction(driver) {
|
|
|
268
279
|
// for example in determining proxy avoidance
|
|
269
280
|
driver.basePath = basePath;
|
|
270
281
|
|
|
271
|
-
const allMethods = {...METHOD_MAP, ...extraMethodMap};
|
|
272
|
-
|
|
282
|
+
const allMethods: MethodMap<Driver> = {...METHOD_MAP, ...extraMethodMap};
|
|
273
283
|
for (const [path, methods] of _.toPairs(allMethods)) {
|
|
274
284
|
for (const [method, spec] of _.toPairs(methods)) {
|
|
285
|
+
const isSessCommand = spec.command ? isSessionCommand(spec.command) : false;
|
|
275
286
|
// set up the express route handler
|
|
276
287
|
buildHandler(
|
|
277
288
|
app,
|
|
@@ -279,24 +290,31 @@ function routeConfiguringFunction(driver) {
|
|
|
279
290
|
`${basePath}${path}`,
|
|
280
291
|
spec,
|
|
281
292
|
driver,
|
|
282
|
-
|
|
293
|
+
isSessCommand
|
|
283
294
|
);
|
|
284
295
|
}
|
|
285
296
|
}
|
|
286
297
|
};
|
|
287
298
|
}
|
|
288
299
|
|
|
289
|
-
function buildHandler(
|
|
290
|
-
|
|
300
|
+
function buildHandler(
|
|
301
|
+
app: Application,
|
|
302
|
+
method: string,
|
|
303
|
+
path: string,
|
|
304
|
+
spec: DriverMethodDef<Driver>,
|
|
305
|
+
driver: Core<any>,
|
|
306
|
+
isSessCmd: boolean
|
|
307
|
+
): void {
|
|
308
|
+
const asyncHandler = async (req: Request, res: Response) => {
|
|
291
309
|
let jsonObj = req.body;
|
|
292
|
-
let httpResBody = {};
|
|
310
|
+
let httpResBody = {} as any;
|
|
293
311
|
let httpStatus = 200;
|
|
294
|
-
let newSessionId;
|
|
312
|
+
let newSessionId: string | undefined;
|
|
295
313
|
let currentProtocol = extractProtocol(driver, req.params.sessionId);
|
|
296
314
|
|
|
297
315
|
try {
|
|
298
316
|
// if the route accessed is deprecated, log a warning
|
|
299
|
-
if (spec.deprecated && !deprecatedCommandsLogged.has(spec.command)) {
|
|
317
|
+
if (spec.deprecated && spec.command && !deprecatedCommandsLogged.has(spec.command)) {
|
|
300
318
|
deprecatedCommandsLogged.add(spec.command);
|
|
301
319
|
getLogger(driver, req.params.sessionId).warn(
|
|
302
320
|
`Command '${spec.command}' has been deprecated and will be removed in a future ` +
|
|
@@ -320,12 +338,12 @@ function buildHandler(app, method, path, spec, driver, isSessCmd) {
|
|
|
320
338
|
// commands and generally would not want that command to be proxied instead of handled by the
|
|
321
339
|
// plugin)
|
|
322
340
|
let didPluginOverrideProxy = false;
|
|
323
|
-
if (isSessCmd && !spec.neverProxy && driverShouldDoJwpProxy(driver, req, spec.command)) {
|
|
341
|
+
if (isSessCmd && !spec.neverProxy && spec.command && driverShouldDoJwpProxy(driver, req, spec.command)) {
|
|
324
342
|
if (
|
|
325
|
-
!driver.pluginsToHandleCmd ||
|
|
343
|
+
!('pluginsToHandleCmd' in driver) || !_.isFunction(driver.pluginsToHandleCmd) ||
|
|
326
344
|
driver.pluginsToHandleCmd(spec.command, req.params.sessionId).length === 0
|
|
327
345
|
) {
|
|
328
|
-
await doJwpProxy(driver
|
|
346
|
+
await doJwpProxy(driver as BaseDriver<any>, req, res);
|
|
329
347
|
return;
|
|
330
348
|
}
|
|
331
349
|
getLogger(driver, req.params.sessionId).debug(
|
|
@@ -361,12 +379,14 @@ function buildHandler(app, method, path, spec, driver, isSessCmd) {
|
|
|
361
379
|
}
|
|
362
380
|
|
|
363
381
|
// ensure that the json payload conforms to the spec
|
|
364
|
-
|
|
382
|
+
if (spec.payloadParams) {
|
|
383
|
+
checkParams(spec.payloadParams, jsonObj, currentProtocol);
|
|
384
|
+
}
|
|
365
385
|
|
|
366
386
|
// turn the command and json payload into an argument list for
|
|
367
387
|
// the driver methods
|
|
368
|
-
|
|
369
|
-
let driverRes;
|
|
388
|
+
const args = makeArgs(req.params, jsonObj, spec.payloadParams || {});
|
|
389
|
+
let driverRes: any;
|
|
370
390
|
// validate command args according to MJSONWP
|
|
371
391
|
if (validators[spec.command]) {
|
|
372
392
|
validators[spec.command](...args);
|
|
@@ -386,7 +406,7 @@ function buildHandler(app, method, path, spec, driver, isSessCmd) {
|
|
|
386
406
|
args.push({reqForProxy: req});
|
|
387
407
|
}
|
|
388
408
|
|
|
389
|
-
driverRes = await driver.executeCommand(spec.command, ...args);
|
|
409
|
+
driverRes = await (driver as BaseDriver<any>).executeCommand(spec.command, ...args);
|
|
390
410
|
|
|
391
411
|
// Get the protocol after executeCommand
|
|
392
412
|
currentProtocol = extractProtocol(driver, req.params.sessionId) || currentProtocol;
|
|
@@ -489,24 +509,14 @@ function buildHandler(app, method, path, spec, driver, isSessCmd) {
|
|
|
489
509
|
|
|
490
510
|
// decode the response, which is either a string or json
|
|
491
511
|
if (_.isString(httpResBody)) {
|
|
492
|
-
res.status(httpStatus)
|
|
512
|
+
res.status(httpStatus)
|
|
513
|
+
.setHeader('content-type', 'application/json; charset=utf-8')
|
|
514
|
+
.send(httpResBody);
|
|
493
515
|
} else {
|
|
494
|
-
if (newSessionId) {
|
|
495
|
-
|
|
496
|
-
httpResBody.value.sessionId = newSessionId;
|
|
497
|
-
} else {
|
|
498
|
-
httpResBody.sessionId = newSessionId;
|
|
499
|
-
}
|
|
500
|
-
} else {
|
|
501
|
-
httpResBody.sessionId = req.params.sessionId || null;
|
|
502
|
-
}
|
|
503
|
-
// Don't include sessionId in W3C responses
|
|
504
|
-
if (currentProtocol === PROTOCOLS.W3C) {
|
|
505
|
-
delete httpResBody.sessionId;
|
|
516
|
+
if (newSessionId && currentProtocol === PROTOCOLS.W3C) {
|
|
517
|
+
httpResBody.value.sessionId = newSessionId;
|
|
506
518
|
}
|
|
507
|
-
|
|
508
|
-
httpResBody = formatStatus(httpResBody);
|
|
509
|
-
res.status(httpStatus).json(httpResBody);
|
|
519
|
+
res.status(httpStatus).json(ensureW3cResponse(httpResBody));
|
|
510
520
|
}
|
|
511
521
|
};
|
|
512
522
|
// add the method to the app
|
|
@@ -515,7 +525,7 @@ function buildHandler(app, method, path, spec, driver, isSessCmd) {
|
|
|
515
525
|
});
|
|
516
526
|
}
|
|
517
527
|
|
|
518
|
-
function driverShouldDoJwpProxy(driver
|
|
528
|
+
export function driverShouldDoJwpProxy(driver: Core<any>, req: import('express').Request, command: string): boolean {
|
|
519
529
|
// drivers need to explicitly say when the proxy is active
|
|
520
530
|
if (!driver.proxyActive(req.params.sessionId)) {
|
|
521
531
|
return false;
|
|
@@ -536,7 +546,7 @@ function driverShouldDoJwpProxy(driver, req, command) {
|
|
|
536
546
|
return true;
|
|
537
547
|
}
|
|
538
548
|
|
|
539
|
-
async function doJwpProxy(driver
|
|
549
|
+
async function doJwpProxy(driver: BaseDriver<any>, req: Request, res: Response): Promise<void> {
|
|
540
550
|
getLogger(driver, req.params.sessionId).info(
|
|
541
551
|
'Driver proxy active, passing request on via HTTP proxy'
|
|
542
552
|
);
|
|
@@ -555,12 +565,3 @@ async function doJwpProxy(driver, req, res) {
|
|
|
555
565
|
}
|
|
556
566
|
}
|
|
557
567
|
}
|
|
558
|
-
|
|
559
|
-
export {
|
|
560
|
-
routeConfiguringFunction,
|
|
561
|
-
isSessionCommand,
|
|
562
|
-
driverShouldDoJwpProxy,
|
|
563
|
-
determineProtocol,
|
|
564
|
-
deprecatedCommandsLogged,
|
|
565
|
-
validateExecuteMethodParams,
|
|
566
|
-
};
|