@appium/base-driver 10.2.0 → 10.2.1
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/LICENSE +201 -0
- package/build/lib/basedriver/capabilities.js +7 -7
- package/build/lib/basedriver/capabilities.js.map +1 -1
- package/build/lib/basedriver/commands/event.d.ts +1 -1
- package/build/lib/basedriver/commands/event.d.ts.map +1 -1
- package/build/lib/basedriver/commands/execute.d.ts +1 -1
- package/build/lib/basedriver/commands/execute.d.ts.map +1 -1
- package/build/lib/basedriver/commands/find.d.ts +1 -1
- package/build/lib/basedriver/commands/find.d.ts.map +1 -1
- package/build/lib/basedriver/commands/mixin.d.ts +1 -1
- package/build/lib/basedriver/commands/mixin.d.ts.map +1 -1
- package/build/lib/basedriver/commands/timeout.d.ts +1 -1
- package/build/lib/basedriver/commands/timeout.d.ts.map +1 -1
- package/build/lib/basedriver/device-settings.d.ts +14 -23
- package/build/lib/basedriver/device-settings.d.ts.map +1 -1
- package/build/lib/basedriver/device-settings.js +11 -26
- package/build/lib/basedriver/device-settings.js.map +1 -1
- package/build/lib/basedriver/helpers.d.ts +36 -57
- package/build/lib/basedriver/helpers.d.ts.map +1 -1
- package/build/lib/basedriver/helpers.js +148 -239
- package/build/lib/basedriver/helpers.js.map +1 -1
- package/build/lib/basedriver/logger.d.ts +1 -2
- package/build/lib/basedriver/logger.d.ts.map +1 -1
- package/build/lib/basedriver/logger.js +2 -2
- package/build/lib/basedriver/logger.js.map +1 -1
- package/build/lib/basedriver/validation.d.ts.map +1 -1
- package/build/lib/basedriver/validation.js +3 -3
- package/build/lib/basedriver/validation.js.map +1 -1
- package/build/lib/constants.d.ts +1 -1
- package/build/lib/constants.d.ts.map +1 -1
- package/build/lib/express/crash.d.ts +8 -2
- package/build/lib/express/crash.d.ts.map +1 -1
- package/build/lib/express/crash.js +6 -0
- package/build/lib/express/crash.js.map +1 -1
- package/build/lib/express/express-logging.d.ts +12 -2
- package/build/lib/express/express-logging.d.ts.map +1 -1
- package/build/lib/express/express-logging.js +34 -26
- package/build/lib/express/express-logging.js.map +1 -1
- package/build/lib/express/idempotency.d.ts +4 -10
- package/build/lib/express/idempotency.d.ts.map +1 -1
- package/build/lib/express/idempotency.js +69 -73
- package/build/lib/express/idempotency.js.map +1 -1
- package/build/lib/express/logger.d.ts +1 -2
- package/build/lib/express/logger.d.ts.map +1 -1
- package/build/lib/express/logger.js +2 -2
- package/build/lib/express/logger.js.map +1 -1
- package/build/lib/express/middleware.d.ts +37 -41
- package/build/lib/express/middleware.d.ts.map +1 -1
- package/build/lib/express/middleware.js +48 -60
- package/build/lib/express/middleware.js.map +1 -1
- package/build/lib/express/server.d.ts +57 -101
- package/build/lib/express/server.d.ts.map +1 -1
- package/build/lib/express/server.js +51 -128
- package/build/lib/express/server.js.map +1 -1
- package/build/lib/express/static.d.ts +10 -5
- package/build/lib/express/static.d.ts.map +1 -1
- package/build/lib/express/static.js +32 -42
- package/build/lib/express/static.js.map +1 -1
- package/build/lib/express/websocket.d.ts +22 -6
- package/build/lib/express/websocket.d.ts.map +1 -1
- package/build/lib/express/websocket.js +10 -15
- package/build/lib/express/websocket.js.map +1 -1
- package/build/lib/helpers/capabilities.d.ts +4 -16
- package/build/lib/helpers/capabilities.d.ts.map +1 -1
- package/build/lib/helpers/capabilities.js +36 -48
- package/build/lib/helpers/capabilities.js.map +1 -1
- package/build/lib/jsonwp-status/status.d.ts +113 -158
- package/build/lib/jsonwp-status/status.d.ts.map +1 -1
- package/build/lib/jsonwp-status/status.js +10 -14
- package/build/lib/jsonwp-status/status.js.map +1 -1
- package/build/lib/protocol/bidi-commands.d.ts +31 -36
- package/build/lib/protocol/bidi-commands.d.ts.map +1 -1
- package/build/lib/protocol/bidi-commands.js +5 -5
- package/build/lib/protocol/bidi-commands.js.map +1 -1
- package/build/lib/protocol/errors.d.ts.map +1 -1
- package/build/lib/protocol/helpers.d.ts +7 -11
- package/build/lib/protocol/helpers.d.ts.map +1 -1
- package/build/lib/protocol/helpers.js +5 -9
- package/build/lib/protocol/helpers.js.map +1 -1
- package/build/lib/protocol/index.d.ts +4 -21
- package/build/lib/protocol/index.d.ts.map +1 -1
- package/build/lib/protocol/index.js.map +1 -1
- package/build/lib/protocol/protocol.d.ts +15 -1
- package/build/lib/protocol/protocol.d.ts.map +1 -1
- package/build/lib/protocol/protocol.js +50 -20
- package/build/lib/protocol/protocol.js.map +1 -1
- package/build/lib/protocol/routes.d.ts +8 -15
- package/build/lib/protocol/routes.d.ts.map +1 -1
- package/build/lib/protocol/routes.js +18 -33
- package/build/lib/protocol/routes.js.map +1 -1
- package/lib/basedriver/capabilities.ts +1 -1
- package/lib/basedriver/commands/event.ts +2 -2
- package/lib/basedriver/commands/execute.ts +2 -2
- package/lib/basedriver/commands/find.ts +2 -2
- package/lib/basedriver/commands/mixin.ts +1 -1
- package/lib/basedriver/commands/timeout.ts +2 -2
- package/lib/basedriver/{device-settings.js → device-settings.ts} +24 -35
- package/lib/basedriver/{helpers.js → helpers.ts} +208 -266
- package/lib/basedriver/logger.ts +3 -0
- package/lib/basedriver/validation.ts +2 -2
- package/lib/constants.ts +1 -1
- package/lib/express/crash.ts +15 -0
- package/lib/express/express-logging.ts +84 -0
- package/lib/express/{idempotency.js → idempotency.ts} +105 -89
- package/lib/express/logger.ts +3 -0
- package/lib/express/middleware.ts +187 -0
- package/lib/express/{server.js → server.ts} +175 -167
- package/lib/express/static.ts +77 -0
- package/lib/express/websocket.ts +81 -0
- package/lib/helpers/capabilities.ts +83 -0
- package/lib/jsonwp-status/{status.js → status.ts} +12 -15
- package/lib/protocol/{bidi-commands.js → bidi-commands.ts} +7 -5
- package/lib/protocol/errors.ts +1 -1
- package/lib/protocol/{helpers.js → helpers.ts} +8 -11
- package/lib/protocol/protocol.ts +57 -26
- package/lib/protocol/{routes.js → routes.ts} +29 -40
- package/package.json +11 -11
- package/tsconfig.json +3 -1
- package/lib/basedriver/logger.js +0 -4
- package/lib/express/crash.js +0 -11
- package/lib/express/express-logging.js +0 -60
- package/lib/express/logger.js +0 -4
- package/lib/express/middleware.js +0 -171
- package/lib/express/static.js +0 -76
- package/lib/express/websocket.js +0 -79
- package/lib/helpers/capabilities.js +0 -93
- /package/lib/protocol/{index.js → index.ts} +0 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type {Constraints, W3CCapabilities, Capabilities, AppiumLogger} from '@appium/types';
|
|
2
|
+
import _ from 'lodash';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Determine whether the given argument is valid
|
|
6
|
+
* W3C capabilities instance.
|
|
7
|
+
*/
|
|
8
|
+
export function isW3cCaps(caps: unknown): caps is W3CCapabilities<Constraints> {
|
|
9
|
+
if (!_.isPlainObject(caps)) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const c = caps as Record<string, unknown>;
|
|
14
|
+
const isFirstMatchValid = () =>
|
|
15
|
+
_.isArray(c.firstMatch) &&
|
|
16
|
+
!_.isEmpty(c.firstMatch) &&
|
|
17
|
+
_.every(c.firstMatch, _.isPlainObject);
|
|
18
|
+
const isAlwaysMatchValid = () => _.isPlainObject(c.alwaysMatch);
|
|
19
|
+
if (_.has(c, 'firstMatch') && _.has(c, 'alwaysMatch')) {
|
|
20
|
+
return isFirstMatchValid() && isAlwaysMatchValid();
|
|
21
|
+
}
|
|
22
|
+
if (_.has(c, 'firstMatch')) {
|
|
23
|
+
return isFirstMatchValid();
|
|
24
|
+
}
|
|
25
|
+
if (_.has(c, 'alwaysMatch')) {
|
|
26
|
+
return isAlwaysMatchValid();
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Normalize capability values according to constraints (e.g. string 'true' → boolean).
|
|
33
|
+
*/
|
|
34
|
+
export function fixCaps<C extends Constraints>(
|
|
35
|
+
oldCaps: Record<string, unknown>,
|
|
36
|
+
desiredCapConstraints: C,
|
|
37
|
+
log: AppiumLogger
|
|
38
|
+
): Capabilities<C> {
|
|
39
|
+
const caps = _.clone(oldCaps) as Record<string, unknown>;
|
|
40
|
+
|
|
41
|
+
const logCastWarning = (prefix: string) => log.warn(`${prefix}. This may cause unexpected behavior`);
|
|
42
|
+
|
|
43
|
+
// boolean capabilities can be passed in as strings 'false' and 'true'
|
|
44
|
+
// which we want to translate into boolean values
|
|
45
|
+
const booleanCaps = _.keys(_.pickBy(desiredCapConstraints, (k) => k.isBoolean === true));
|
|
46
|
+
for (const cap of booleanCaps) {
|
|
47
|
+
const value = oldCaps[cap];
|
|
48
|
+
if (!_.isString(value)) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!['true', 'false'].includes(value.toLowerCase())) {
|
|
53
|
+
logCastWarning(`String capability '${cap}' ('${value}') cannot be converted to a boolean`);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
logCastWarning(`Capability '${cap}' changed from string '${value}' to boolean`);
|
|
58
|
+
caps[cap] = value.toLowerCase() === 'true';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// int capabilities are often sent in as strings by frameworks
|
|
62
|
+
const intCaps = _.keys(_.pickBy(desiredCapConstraints, (k) => k.isNumber === true));
|
|
63
|
+
for (const cap of intCaps) {
|
|
64
|
+
const value = oldCaps[cap];
|
|
65
|
+
if (!_.isString(value)) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const intValue = parseInt(value as string, 10);
|
|
70
|
+
const floatValue = parseFloat(value as string);
|
|
71
|
+
const newValue = floatValue !== intValue ? floatValue : intValue;
|
|
72
|
+
|
|
73
|
+
if (Number.isNaN(newValue)) {
|
|
74
|
+
logCastWarning(`String capability '${cap}' ('${value}') cannot be converted to a number`);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
logCastWarning(`Capability '${cap}' changed from string '${value}' to number ${newValue}`);
|
|
79
|
+
caps[cap] = newValue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return caps as Capabilities<C>;
|
|
83
|
+
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const codes = {
|
|
1
|
+
export const codes = {
|
|
4
2
|
Success: {
|
|
5
3
|
code: 0,
|
|
6
4
|
summary: 'The command executed successfully.',
|
|
@@ -112,17 +110,16 @@ const codes = {
|
|
|
112
110
|
code: 35,
|
|
113
111
|
summary: 'No such context found.',
|
|
114
112
|
},
|
|
115
|
-
};
|
|
113
|
+
} as const;
|
|
116
114
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
115
|
+
/**
|
|
116
|
+
* Returns the summary message for a JSONWP status code.
|
|
117
|
+
*
|
|
118
|
+
* @param code - Numeric status code (e.g. 0, 7) or string representation
|
|
119
|
+
* @returns Human-readable summary for the code, or 'An error occurred' if unknown
|
|
120
|
+
*/
|
|
121
|
+
export function getSummaryByCode(code: number | string): string {
|
|
122
|
+
const parsed = parseInt(String(code), 10);
|
|
123
|
+
const match = Object.values(codes).find((obj) => obj.code === parsed);
|
|
124
|
+
return match?.summary ?? 'An error occurred';
|
|
125
125
|
}
|
|
126
|
-
|
|
127
|
-
export default codes;
|
|
128
|
-
export {codes, getSummaryByCode};
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
import type {BidiModuleMap} from '@appium/types';
|
|
2
|
+
|
|
3
|
+
const SUBSCRIPTION_REQUEST_PARAMS = {
|
|
2
4
|
required: ['events'],
|
|
3
5
|
optional: ['contexts'],
|
|
4
|
-
}
|
|
6
|
+
} as const;
|
|
5
7
|
|
|
6
|
-
export const BIDI_COMMANDS =
|
|
8
|
+
export const BIDI_COMMANDS = {
|
|
7
9
|
session: {
|
|
8
10
|
subscribe: {
|
|
9
11
|
command: 'bidiSubscribe',
|
|
@@ -16,7 +18,7 @@ export const BIDI_COMMANDS = /** @type {const} */ ({
|
|
|
16
18
|
status: {
|
|
17
19
|
command: 'bidiStatus',
|
|
18
20
|
params: {},
|
|
19
|
-
}
|
|
21
|
+
},
|
|
20
22
|
},
|
|
21
23
|
browsingContext: {
|
|
22
24
|
navigate: {
|
|
@@ -27,7 +29,7 @@ export const BIDI_COMMANDS = /** @type {const} */ ({
|
|
|
27
29
|
},
|
|
28
30
|
},
|
|
29
31
|
},
|
|
30
|
-
}
|
|
32
|
+
} as const satisfies BidiModuleMap;
|
|
31
33
|
|
|
32
34
|
// TODO add definitions for all bidi commands.
|
|
33
35
|
// spec link: https://w3c.github.io/webdriver-bidi/
|
package/lib/protocol/errors.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import _ from 'lodash';
|
|
2
2
|
import {util, logger} from '@appium/support';
|
|
3
3
|
import {StatusCodes as HTTPStatusCodes} from 'http-status-codes';
|
|
4
|
-
import type {
|
|
4
|
+
import type {ErrorBiDiCommandResponse, Class} from '@appium/types';
|
|
5
5
|
|
|
6
6
|
const mjsonwpLog = logger.getLogger('MJSONWP');
|
|
7
7
|
const w3cLog = logger.getLogger('W3C');
|
|
@@ -5,13 +5,12 @@ import {MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY} from '../constants';
|
|
|
5
5
|
/**
|
|
6
6
|
* Preprocesses the resulting value for API responses,
|
|
7
7
|
* so they have keys for both W3C and JSONWP protocols.
|
|
8
|
-
* The argument value is NOT mutated
|
|
8
|
+
* The argument value is NOT mutated.
|
|
9
9
|
*
|
|
10
|
-
* @param
|
|
11
|
-
* @returns
|
|
12
|
-
* nothing has been modified
|
|
10
|
+
* @param resValue - The actual response value
|
|
11
|
+
* @returns Either modified value or the same one if nothing has been modified
|
|
13
12
|
*/
|
|
14
|
-
export function formatResponseValue(resValue) {
|
|
13
|
+
export function formatResponseValue(resValue: object | undefined): object | null {
|
|
15
14
|
if (_.isUndefined(resValue)) {
|
|
16
15
|
// convert undefined to null
|
|
17
16
|
return null;
|
|
@@ -27,13 +26,11 @@ export function formatResponseValue(resValue) {
|
|
|
27
26
|
* Properly formats the status for API responses,
|
|
28
27
|
* so they are correct for the W3C protocol.
|
|
29
28
|
*
|
|
30
|
-
* @param
|
|
31
|
-
* @returns
|
|
29
|
+
* @param responseBody - The response body
|
|
30
|
+
* @returns The fixed response body
|
|
32
31
|
*/
|
|
33
|
-
export function ensureW3cResponse(responseBody) {
|
|
32
|
+
export function ensureW3cResponse(responseBody: Record<string, unknown>): Record<string, unknown> {
|
|
34
33
|
return _.isPlainObject(responseBody)
|
|
35
|
-
? _.omit(responseBody, ['status', 'sessionId'])
|
|
34
|
+
? (_.omit(responseBody, ['status', 'sessionId']) as Record<string, unknown>)
|
|
36
35
|
: responseBody;
|
|
37
36
|
}
|
|
38
|
-
|
|
39
|
-
export {MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY};
|
package/lib/protocol/protocol.ts
CHANGED
|
@@ -14,13 +14,13 @@ import B from 'bluebird';
|
|
|
14
14
|
import {formatResponseValue, ensureW3cResponse} from './helpers';
|
|
15
15
|
import {MAX_LOG_BODY_LENGTH, PROTOCOLS, DEFAULT_BASE_PATH} from '../constants';
|
|
16
16
|
import {isW3cCaps} from '../helpers/capabilities';
|
|
17
|
-
import log from '../basedriver/logger';
|
|
18
|
-
import {
|
|
19
|
-
import type {
|
|
20
|
-
import type {
|
|
21
|
-
import type {
|
|
22
|
-
import type {
|
|
23
|
-
import type {
|
|
17
|
+
import {log} from '../basedriver/logger';
|
|
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';
|
|
24
24
|
|
|
25
25
|
export const CREATE_SESSION_COMMAND = 'createSession';
|
|
26
26
|
export const DELETE_SESSION_COMMAND = 'deleteSession';
|
|
@@ -34,6 +34,34 @@ export function determineProtocol(createSessionArgs: any[]): keyof typeof PROTOC
|
|
|
34
34
|
return _.some(createSessionArgs, isW3cCaps) ? PROTOCOLS.W3C : PROTOCOLS.MJSONWP;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Extract and validate the sessionId from the Express route parameter.
|
|
39
|
+
* Express may return route params as string | string[] | undefined.
|
|
40
|
+
* Appium uses standard routes (e.g., /session/:sessionId) which should always be strings.
|
|
41
|
+
* Only `*` such as `/session/*sessionId` can return `string[]`.
|
|
42
|
+
* Then, this method will return the first element as the session id.
|
|
43
|
+
* It may break existing appium routing handling also, thus this method will log
|
|
44
|
+
* received parameters as well to help debugging.
|
|
45
|
+
* @param driver Running driver
|
|
46
|
+
* @param req The request in Express
|
|
47
|
+
* @returns The normalized sessionId (string or undefined)
|
|
48
|
+
*/
|
|
49
|
+
export function getSessionId(driver: Core<any>, req: Request): string | undefined {
|
|
50
|
+
if (Array.isArray(req.params.sessionId)) {
|
|
51
|
+
const sessionId = req.params.sessionId[0];
|
|
52
|
+
getLogger(driver, sessionId).warn(
|
|
53
|
+
`Received malformed sessionId as array from the route: ${req.originalUrl}. ` +
|
|
54
|
+
`This indicates the route definition issue. The route should start with '/session/:sessionId' (named parameter) ` +
|
|
55
|
+
`instead of '/session/*sessionId' (wildcard). ` +
|
|
56
|
+
`Using the first element as session id: ${sessionId}. ` +
|
|
57
|
+
`Please fix the route definition to prevent this error.`
|
|
58
|
+
);
|
|
59
|
+
// This is to not log the message multiple times.
|
|
60
|
+
req.params.sessionId = sessionId;
|
|
61
|
+
return sessionId;
|
|
62
|
+
}
|
|
63
|
+
return req.params.sessionId;
|
|
64
|
+
}
|
|
37
65
|
|
|
38
66
|
function extractProtocol(driver: Core<any>, sessionId: string | null = null): keyof typeof PROTOCOLS {
|
|
39
67
|
const dstDriver = _.isFunction(driver.driverForSession) && sessionId
|
|
@@ -310,13 +338,14 @@ function buildHandler(
|
|
|
310
338
|
let httpResBody = {} as any;
|
|
311
339
|
let httpStatus = 200;
|
|
312
340
|
let newSessionId: string | undefined;
|
|
313
|
-
|
|
341
|
+
const sessionId = getSessionId(driver, req);
|
|
342
|
+
let currentProtocol = extractProtocol(driver, sessionId);
|
|
314
343
|
|
|
315
344
|
try {
|
|
316
345
|
// if the route accessed is deprecated, log a warning
|
|
317
346
|
if (spec.deprecated && spec.command && !deprecatedCommandsLogged.has(spec.command)) {
|
|
318
347
|
deprecatedCommandsLogged.add(spec.command);
|
|
319
|
-
getLogger(driver,
|
|
348
|
+
getLogger(driver, sessionId).warn(
|
|
320
349
|
`The ${method} ${path} endpoint has been deprecated and will be removed in a future ` +
|
|
321
350
|
`version of Appium or your driver/plugin. Please use a different endpoint or contact the ` +
|
|
322
351
|
`driver/plugin author to add explicit support for the endpoint before it is removed`
|
|
@@ -325,7 +354,7 @@ function buildHandler(
|
|
|
325
354
|
|
|
326
355
|
// if this is a session command but we don't have a session,
|
|
327
356
|
// error out early (especially before proxying)
|
|
328
|
-
if (isSessCmd && !driver.sessionExists(
|
|
357
|
+
if (isSessCmd && !driver.sessionExists(sessionId)) {
|
|
329
358
|
throw new errors.NoSuchDriverError();
|
|
330
359
|
}
|
|
331
360
|
|
|
@@ -341,12 +370,12 @@ function buildHandler(
|
|
|
341
370
|
if (isSessCmd && !spec.neverProxy && spec.command && driverShouldDoJwpProxy(driver, req, spec.command)) {
|
|
342
371
|
if (
|
|
343
372
|
!('pluginsToHandleCmd' in driver) || !_.isFunction(driver.pluginsToHandleCmd) ||
|
|
344
|
-
driver.pluginsToHandleCmd(spec.command,
|
|
373
|
+
driver.pluginsToHandleCmd(spec.command, sessionId).length === 0
|
|
345
374
|
) {
|
|
346
375
|
await doJwpProxy(driver as BaseDriver<any>, req, res);
|
|
347
376
|
return;
|
|
348
377
|
}
|
|
349
|
-
getLogger(driver,
|
|
378
|
+
getLogger(driver, sessionId).debug(
|
|
350
379
|
`Would have proxied ` +
|
|
351
380
|
`command directly, but a plugin exists which might require its value, so will let ` +
|
|
352
381
|
`its value be collected internally and made part of plugin chain`
|
|
@@ -393,7 +422,7 @@ function buildHandler(
|
|
|
393
422
|
}
|
|
394
423
|
|
|
395
424
|
// run the driver command wrapped inside the argument validators
|
|
396
|
-
getLogger(driver,
|
|
425
|
+
getLogger(driver, sessionId).debug(
|
|
397
426
|
`Calling %s.%s() with args: %s`,
|
|
398
427
|
driver.constructor.name, spec.command,
|
|
399
428
|
logger.markSensitive(_.truncate(JSON.stringify(args), {length: MAX_LOG_BODY_LENGTH}))
|
|
@@ -409,7 +438,7 @@ function buildHandler(
|
|
|
409
438
|
driverRes = await (driver as BaseDriver<any>).executeCommand(spec.command, ...args);
|
|
410
439
|
|
|
411
440
|
// Get the protocol after executeCommand
|
|
412
|
-
currentProtocol = extractProtocol(driver,
|
|
441
|
+
currentProtocol = extractProtocol(driver, sessionId) || currentProtocol;
|
|
413
442
|
|
|
414
443
|
// If `executeCommand` was overridden and the method returns an object
|
|
415
444
|
// with a protocol and value/error property, re-assign the protocol
|
|
@@ -440,12 +469,12 @@ function buildHandler(
|
|
|
440
469
|
|
|
441
470
|
// delete should not return anything even if successful
|
|
442
471
|
if (spec.command === DELETE_SESSION_COMMAND) {
|
|
443
|
-
getLogger(driver,
|
|
472
|
+
getLogger(driver, sessionId).debug(
|
|
444
473
|
`Received response: ${_.truncate(JSON.stringify(driverRes), {
|
|
445
474
|
length: MAX_LOG_BODY_LENGTH,
|
|
446
475
|
})}`
|
|
447
476
|
);
|
|
448
|
-
getLogger(driver,
|
|
477
|
+
getLogger(driver, sessionId).debug('But deleting session, so not returning');
|
|
449
478
|
driverRes = null;
|
|
450
479
|
}
|
|
451
480
|
|
|
@@ -467,7 +496,7 @@ function buildHandler(
|
|
|
467
496
|
}
|
|
468
497
|
|
|
469
498
|
httpResBody.value = driverRes;
|
|
470
|
-
getLogger(driver,
|
|
499
|
+
getLogger(driver, sessionId || newSessionId).debug(
|
|
471
500
|
`Responding ` +
|
|
472
501
|
`to client with driver.${spec.command}() result: ${_.truncate(JSON.stringify(driverRes), {
|
|
473
502
|
length: MAX_LOG_BODY_LENGTH,
|
|
@@ -480,7 +509,7 @@ function buildHandler(
|
|
|
480
509
|
if (err instanceof Error || (_.has(err, 'stack') && _.has(err, 'message'))) {
|
|
481
510
|
actualErr = err;
|
|
482
511
|
} else {
|
|
483
|
-
getLogger(driver,
|
|
512
|
+
getLogger(driver, sessionId || newSessionId).warn(
|
|
484
513
|
'The thrown error object does not seem to be a valid instance of the Error class. This ' +
|
|
485
514
|
'might be a genuine bug of a driver or a plugin.'
|
|
486
515
|
);
|
|
@@ -488,7 +517,7 @@ function buildHandler(
|
|
|
488
517
|
}
|
|
489
518
|
|
|
490
519
|
currentProtocol =
|
|
491
|
-
currentProtocol || extractProtocol(driver,
|
|
520
|
+
currentProtocol || extractProtocol(driver, sessionId || newSessionId);
|
|
492
521
|
|
|
493
522
|
let errMsg = err.stacktrace || err.stack;
|
|
494
523
|
if (!_.includes(errMsg, err.message)) {
|
|
@@ -499,7 +528,7 @@ function buildHandler(
|
|
|
499
528
|
if (isErrorType(err, errors.ProxyRequestError)) {
|
|
500
529
|
actualErr = err.getActualError();
|
|
501
530
|
} else {
|
|
502
|
-
getLogger(driver,
|
|
531
|
+
getLogger(driver, sessionId || newSessionId).debug(
|
|
503
532
|
`Encountered internal error running command: ${errMsg}`
|
|
504
533
|
);
|
|
505
534
|
}
|
|
@@ -525,9 +554,10 @@ function buildHandler(
|
|
|
525
554
|
});
|
|
526
555
|
}
|
|
527
556
|
|
|
528
|
-
export function driverShouldDoJwpProxy(driver: Core<any>, req:
|
|
557
|
+
export function driverShouldDoJwpProxy(driver: Core<any>, req: Request, command: string): boolean {
|
|
558
|
+
const sessionId = getSessionId(driver, req);
|
|
529
559
|
// drivers need to explicitly say when the proxy is active
|
|
530
|
-
if (!driver.proxyActive(
|
|
560
|
+
if (!driver.proxyActive(sessionId)) {
|
|
531
561
|
return false;
|
|
532
562
|
}
|
|
533
563
|
|
|
@@ -539,7 +569,7 @@ export function driverShouldDoJwpProxy(driver: Core<any>, req: import('express')
|
|
|
539
569
|
|
|
540
570
|
// validate avoidance schema, and say we shouldn't proxy if anything in the
|
|
541
571
|
// avoid list matches our req
|
|
542
|
-
if (driver.proxyRouteIsAvoided(
|
|
572
|
+
if (driver.proxyRouteIsAvoided(sessionId as string, req.method, req.originalUrl, req.body)) {
|
|
543
573
|
return false;
|
|
544
574
|
}
|
|
545
575
|
|
|
@@ -547,16 +577,17 @@ export function driverShouldDoJwpProxy(driver: Core<any>, req: import('express')
|
|
|
547
577
|
}
|
|
548
578
|
|
|
549
579
|
async function doJwpProxy(driver: BaseDriver<any>, req: Request, res: Response): Promise<void> {
|
|
550
|
-
|
|
580
|
+
const sessionId = getSessionId(driver, req) as string;
|
|
581
|
+
getLogger(driver, sessionId).info(
|
|
551
582
|
'Driver proxy active, passing request on via HTTP proxy'
|
|
552
583
|
);
|
|
553
584
|
|
|
554
585
|
// check that the inner driver has a proxy function
|
|
555
|
-
if (!driver.canProxy(
|
|
586
|
+
if (!driver.canProxy(sessionId)) {
|
|
556
587
|
throw new Error('Trying to proxy to a server but the driver is unable to proxy');
|
|
557
588
|
}
|
|
558
589
|
try {
|
|
559
|
-
await driver.executeCommand('proxyReqRes', req, res,
|
|
590
|
+
await driver.executeCommand('proxyReqRes', req, res, sessionId);
|
|
560
591
|
} catch (err) {
|
|
561
592
|
if (isErrorType(err, errors.ProxyRequestError)) {
|
|
562
593
|
throw err;
|
|
@@ -1,20 +1,19 @@
|
|
|
1
|
+
import type {Driver, DriverMethodDef, HTTPMethod, MethodMap} from '@appium/types';
|
|
1
2
|
import _ from 'lodash';
|
|
2
3
|
import {DEFAULT_BASE_PATH} from '../constants';
|
|
3
4
|
import {match} from 'path-to-regexp';
|
|
4
5
|
import {LRUCache} from 'lru-cache';
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
const COMMAND_NAMES_CACHE = new LRUCache({
|
|
7
|
+
const COMMAND_NAMES_CACHE = new LRUCache<string, string>({
|
|
8
8
|
max: 1024,
|
|
9
9
|
});
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
13
|
-
* any parameters that are expected in a request
|
|
14
|
-
* `optional
|
|
15
|
-
* @satisfies {import('@appium/types').MethodMap<import('../basedriver/driver').BaseDriver>}
|
|
12
|
+
* Define the routes: mapping of HTTP methods to particular driver commands, and
|
|
13
|
+
* any parameters that are expected in a request. Parameters can be `required` or
|
|
14
|
+
* `optional`.
|
|
16
15
|
*/
|
|
17
|
-
export const METHOD_MAP =
|
|
16
|
+
export const METHOD_MAP = {
|
|
18
17
|
|
|
19
18
|
// #region W3C WebDriver
|
|
20
19
|
// https://www.w3.org/TR/webdriver2/
|
|
@@ -566,42 +565,40 @@ export const METHOD_MAP = /** @type {const} */ ({
|
|
|
566
565
|
DELETE: {command: 'deleteVirtualPressureSource'},
|
|
567
566
|
},
|
|
568
567
|
// #endregion
|
|
569
|
-
}
|
|
568
|
+
} as const satisfies MethodMap<Driver>;
|
|
570
569
|
|
|
571
570
|
// driver command names
|
|
572
571
|
export const ALL_COMMANDS = _.flatMap(_.values(METHOD_MAP).map(_.values))
|
|
573
572
|
.filter((m) => Boolean(m.command))
|
|
574
573
|
.map((m) => m.command);
|
|
575
574
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
let normalizedEndpoint = basePath
|
|
585
|
-
? endpoint.replace(new RegExp(`^${_.escapeRegExp(basePath)}`), '')
|
|
575
|
+
export function routeToCommandName(
|
|
576
|
+
endpoint: string,
|
|
577
|
+
method?: HTTPMethod,
|
|
578
|
+
basePath?: string
|
|
579
|
+
): string | undefined {
|
|
580
|
+
const resolvedBasePath = basePath ?? DEFAULT_BASE_PATH;
|
|
581
|
+
let normalizedEndpoint = resolvedBasePath
|
|
582
|
+
? endpoint.replace(new RegExp(`^${_.escapeRegExp(resolvedBasePath)}`), '')
|
|
586
583
|
: endpoint;
|
|
587
584
|
normalizedEndpoint = `${_.startsWith(normalizedEndpoint, '/') ? '' : '/'}${normalizedEndpoint}`;
|
|
588
|
-
|
|
589
|
-
let normalizedPathname;
|
|
585
|
+
let normalizedPathname: string;
|
|
590
586
|
try {
|
|
591
587
|
// we could use any prefix there as we anyway need to only extract the pathname
|
|
592
588
|
normalizedPathname = new URL(`https://appium.io${normalizedEndpoint}`).pathname;
|
|
593
|
-
} catch (err) {
|
|
594
|
-
|
|
589
|
+
} catch (err: unknown) {
|
|
590
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
591
|
+
throw new Error(`'${endpoint}' cannot be translated to a command name: ${msg}`);
|
|
595
592
|
}
|
|
596
593
|
|
|
597
|
-
const normalizedMethod = _.toUpper(method);
|
|
594
|
+
const normalizedMethod = _.toUpper(method ?? '');
|
|
598
595
|
const cacheKey = toCommandNameCacheKey(normalizedPathname, normalizedMethod);
|
|
599
|
-
|
|
600
|
-
|
|
596
|
+
const cached = COMMAND_NAMES_CACHE.get(cacheKey);
|
|
597
|
+
if (cached !== undefined) {
|
|
598
|
+
return cached || undefined;
|
|
601
599
|
}
|
|
602
600
|
|
|
603
|
-
|
|
604
|
-
const possiblePathnames = [];
|
|
601
|
+
const possiblePathnames: string[] = [];
|
|
605
602
|
if (!normalizedPathname.startsWith('/session/')) {
|
|
606
603
|
possiblePathnames.push(`/session/any-session-id${normalizedPathname}`);
|
|
607
604
|
}
|
|
@@ -609,12 +606,10 @@ export function routeToCommandName(endpoint, method, basePath = DEFAULT_BASE_PAT
|
|
|
609
606
|
for (const [routePath, routeSpec] of _.toPairs(METHOD_MAP)) {
|
|
610
607
|
const routeMatcher = match(routePath);
|
|
611
608
|
if (possiblePathnames.some((pp) => routeMatcher(pp))) {
|
|
612
|
-
const
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
const commandName = normalizedMethod
|
|
616
|
-
? routeSpec?.[normalizedMethod]?.command
|
|
617
|
-
: commandForAnyMethod();
|
|
609
|
+
const spec = routeSpec as Record<string, DriverMethodDef<Driver>>;
|
|
610
|
+
const commandForAnyMethod = () =>
|
|
611
|
+
_.first(_.keys(spec).map((key) => spec[key]?.command));
|
|
612
|
+
const commandName = normalizedMethod ? spec[normalizedMethod]?.command : commandForAnyMethod();
|
|
618
613
|
if (commandName) {
|
|
619
614
|
COMMAND_NAMES_CACHE.set(cacheKey, commandName);
|
|
620
615
|
return commandName;
|
|
@@ -626,13 +621,7 @@ export function routeToCommandName(endpoint, method, basePath = DEFAULT_BASE_PAT
|
|
|
626
621
|
COMMAND_NAMES_CACHE.set(cacheKey, '');
|
|
627
622
|
}
|
|
628
623
|
|
|
629
|
-
|
|
630
|
-
*
|
|
631
|
-
* @param {string} endpoint
|
|
632
|
-
* @param {string} [method]
|
|
633
|
-
* @returns {string}
|
|
634
|
-
*/
|
|
635
|
-
function toCommandNameCacheKey(endpoint, method) {
|
|
624
|
+
function toCommandNameCacheKey(endpoint: string, method?: string): string {
|
|
636
625
|
return `${endpoint}:${method ?? ''}`;
|
|
637
626
|
}
|
|
638
627
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appium/base-driver",
|
|
3
|
-
"version": "10.2.
|
|
3
|
+
"version": "10.2.1",
|
|
4
4
|
"description": "Base driver class for Appium drivers",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"automation",
|
|
@@ -39,30 +39,30 @@
|
|
|
39
39
|
],
|
|
40
40
|
"scripts": {
|
|
41
41
|
"test": "run-p test:unit test:types",
|
|
42
|
-
"test:e2e": "mocha --exit --timeout 20s --slow 10s \"./test/e2e/**/*.spec.
|
|
42
|
+
"test:e2e": "mocha --exit --timeout 20s --slow 10s \"./test/e2e/**/*.spec.ts\"",
|
|
43
43
|
"test:smoke": "node ./index.js",
|
|
44
|
-
"test:unit": "mocha \"./test/unit/**/*.spec.
|
|
45
|
-
"test:types": "tsd"
|
|
44
|
+
"test:unit": "mocha \"./test/unit/**/*.spec.ts\"",
|
|
45
|
+
"test:types": "tsd && tsc -p tsconfig.test.json"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@appium/support": "^7.0.
|
|
49
|
-
"@appium/types": "^1.2.
|
|
48
|
+
"@appium/support": "^7.0.6",
|
|
49
|
+
"@appium/types": "^1.2.1",
|
|
50
50
|
"@colors/colors": "1.6.0",
|
|
51
51
|
"async-lock": "1.4.1",
|
|
52
|
-
"asyncbox": "6.0
|
|
53
|
-
"axios": "1.13.
|
|
52
|
+
"asyncbox": "6.1.0",
|
|
53
|
+
"axios": "1.13.6",
|
|
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
59
|
"lodash": "4.17.23",
|
|
60
|
-
"lru-cache": "11.2.
|
|
60
|
+
"lru-cache": "11.2.6",
|
|
61
61
|
"method-override": "3.0.0",
|
|
62
62
|
"morgan": "1.10.1",
|
|
63
63
|
"path-to-regexp": "8.3.0",
|
|
64
64
|
"serve-favicon": "2.5.1",
|
|
65
|
-
"type-fest": "5.4.
|
|
65
|
+
"type-fest": "5.4.4"
|
|
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": "
|
|
77
|
+
"gitHead": "980a121804ae006db879fb6860f627ac36174c15",
|
|
78
78
|
"tsd": {
|
|
79
79
|
"directory": "test/types"
|
|
80
80
|
}
|
package/tsconfig.json
CHANGED
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
"@appium/types": ["../types"],
|
|
8
8
|
"@appium/driver-test-support": ["../driver-test-support"]
|
|
9
9
|
},
|
|
10
|
-
"checkJs": true
|
|
10
|
+
"checkJs": true,
|
|
11
|
+
"types": ["node", "mocha"]
|
|
11
12
|
},
|
|
12
13
|
"include": ["lib"],
|
|
14
|
+
"exclude": ["build"],
|
|
13
15
|
"references": [{"path": "../support"}, {"path": "../types"}]
|
|
14
16
|
}
|
package/lib/basedriver/logger.js
DELETED
package/lib/express/crash.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import {errors} from '../protocol';
|
|
2
|
-
|
|
3
|
-
function produceError() {
|
|
4
|
-
throw new errors.UnknownCommandError('Produced generic error for testing');
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
function produceCrash() {
|
|
8
|
-
throw new Error('We just tried to crash Appium!');
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export {produceError, produceCrash};
|