@appium/base-driver 10.2.0 → 10.2.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/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-proxy/protocol-converter.d.ts +42 -78
- package/build/lib/jsonwp-proxy/protocol-converter.d.ts.map +1 -1
- package/build/lib/jsonwp-proxy/protocol-converter.js +87 -139
- package/build/lib/jsonwp-proxy/protocol-converter.js.map +1 -1
- package/build/lib/jsonwp-proxy/proxy.d.ts +1 -1
- package/build/lib/jsonwp-proxy/proxy.d.ts.map +1 -1
- package/build/lib/jsonwp-proxy/proxy.js +2 -2
- package/build/lib/jsonwp-proxy/proxy.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-proxy/protocol-converter.ts +284 -0
- package/lib/jsonwp-proxy/proxy.js +1 -1
- 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/jsonwp-proxy/protocol-converter.js +0 -317
- /package/lib/protocol/{index.js → index.ts} +0 -0
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.2",
|
|
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": "c745352c6500937a4590d1ef6ef19785143a8870",
|
|
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};
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import _ from 'lodash';
|
|
2
|
-
import '@colors/colors';
|
|
3
|
-
import morgan from 'morgan';
|
|
4
|
-
import log from './logger';
|
|
5
|
-
import {MAX_LOG_BODY_LENGTH} from '../constants';
|
|
6
|
-
import {logger} from '@appium/support';
|
|
7
|
-
|
|
8
|
-
// Copied the morgan compile function over so that cooler formats
|
|
9
|
-
// may be configured
|
|
10
|
-
function compile(fmt) {
|
|
11
|
-
// escape quotes
|
|
12
|
-
fmt = fmt.replace(/"/g, '\\"');
|
|
13
|
-
fmt = fmt.replace(/:([-\w]{2,})(?:\[([^\]]+)\])?/g, function replace(_, name, arg) {
|
|
14
|
-
return `"\n + (tokens["${name}"](req, res, "${arg}") || "-") + "`;
|
|
15
|
-
});
|
|
16
|
-
let js = ` return "${fmt}";`;
|
|
17
|
-
return new Function('tokens, req, res', js);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function requestEndLoggingFormat(tokens, req, res) {
|
|
21
|
-
let status = res.statusCode;
|
|
22
|
-
let statusStr = ':status';
|
|
23
|
-
if (status >= 500) {
|
|
24
|
-
statusStr = statusStr.red;
|
|
25
|
-
} else if (status >= 400) {
|
|
26
|
-
statusStr = statusStr.yellow;
|
|
27
|
-
} else if (status >= 300) {
|
|
28
|
-
statusStr = statusStr.cyan;
|
|
29
|
-
} else {
|
|
30
|
-
statusStr = statusStr.green;
|
|
31
|
-
}
|
|
32
|
-
let fn = compile(
|
|
33
|
-
`${'<-- :method :url '.white}${statusStr} ${':response-time ms - :res[content-length]'.grey}`
|
|
34
|
-
);
|
|
35
|
-
return fn(tokens, req, res);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const endLogFormatter = morgan((tokens, req, res) => {
|
|
39
|
-
log.info(requestEndLoggingFormat(tokens, req, res), (res.jsonResp || '').grey);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
const requestStartLoggingFormat = compile(`${'-->'.white} ${':method'.white} ${':url'.white}`);
|
|
43
|
-
|
|
44
|
-
const startLogFormatter = morgan(
|
|
45
|
-
(tokens, req, res) => {
|
|
46
|
-
// morgan output is redirected straight to winston
|
|
47
|
-
let reqBody = '';
|
|
48
|
-
if (req.body) {
|
|
49
|
-
try {
|
|
50
|
-
reqBody = _.truncate(_.isString(req.body) ? req.body : JSON.stringify(req.body), {
|
|
51
|
-
length: MAX_LOG_BODY_LENGTH,
|
|
52
|
-
});
|
|
53
|
-
} catch {}
|
|
54
|
-
}
|
|
55
|
-
log.info(requestStartLoggingFormat(tokens, req, res), logger.markSensitive(reqBody.grey));
|
|
56
|
-
},
|
|
57
|
-
{immediate: true}
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
export {endLogFormatter, startLogFormatter};
|
package/lib/express/logger.js
DELETED
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
import _ from 'lodash';
|
|
2
|
-
import log from './logger';
|
|
3
|
-
import {errors} from '../protocol';
|
|
4
|
-
export {handleIdempotency} from './idempotency';
|
|
5
|
-
import {match} from 'path-to-regexp';
|
|
6
|
-
import {util} from '@appium/support';
|
|
7
|
-
import {calcSignature} from '../helpers/session';
|
|
8
|
-
import {getResponseForW3CError} from '../protocol/errors';
|
|
9
|
-
|
|
10
|
-
const SESSION_ID_PATTERN = /\/session\/([^/]+)/;
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
*
|
|
14
|
-
* @param {import('express').Request} req
|
|
15
|
-
* @param {import('express').Response} res
|
|
16
|
-
* @param {import('express').NextFunction} next
|
|
17
|
-
* @returns {any}
|
|
18
|
-
*/
|
|
19
|
-
export function allowCrossDomain(req, res, next) {
|
|
20
|
-
res.header('Access-Control-Allow-Origin', '*');
|
|
21
|
-
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, OPTIONS, DELETE');
|
|
22
|
-
res.header(
|
|
23
|
-
'Access-Control-Allow-Headers',
|
|
24
|
-
'Cache-Control, Pragma, Origin, X-Requested-With, Content-Type, Accept, User-Agent'
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
// need to respond 200 to OPTIONS
|
|
28
|
-
if ('OPTIONS' === req.method) {
|
|
29
|
-
return res.sendStatus(200);
|
|
30
|
-
}
|
|
31
|
-
next();
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* @param {string} basePath
|
|
36
|
-
* @returns {import('express').RequestHandler}
|
|
37
|
-
*/
|
|
38
|
-
export function allowCrossDomainAsyncExecute(basePath) {
|
|
39
|
-
return (req, res, next) => {
|
|
40
|
-
// there are two paths for async responses, so cover both
|
|
41
|
-
// https://regex101.com/r/txYiEz/1
|
|
42
|
-
const receiveAsyncResponseRegExp = new RegExp(
|
|
43
|
-
`${_.escapeRegExp(basePath)}/session/[a-f0-9-]+/(appium/)?receive_async_response`
|
|
44
|
-
);
|
|
45
|
-
if (!receiveAsyncResponseRegExp.test(req.url)) {
|
|
46
|
-
return next();
|
|
47
|
-
}
|
|
48
|
-
allowCrossDomain(req, res, next);
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
*
|
|
54
|
-
* @param {import('express').Request} req
|
|
55
|
-
* @param {import('express').Response} res
|
|
56
|
-
* @param {import('express').NextFunction} next
|
|
57
|
-
* @returns {any}
|
|
58
|
-
*/
|
|
59
|
-
export function handleLogContext(req, res, next) {
|
|
60
|
-
const requestId = fetchHeaderValue(req, 'x-request-id') || util.uuidV4();
|
|
61
|
-
|
|
62
|
-
const sessionId = SESSION_ID_PATTERN.exec(req.url)?.[1];
|
|
63
|
-
const sessionInfo = sessionId ? {sessionId, sessionSignature: calcSignature(sessionId)} : {};
|
|
64
|
-
const isSensitiveHeaderValue = fetchHeaderValue(req, 'x-appium-is-sensitive');
|
|
65
|
-
|
|
66
|
-
log.updateAsyncContext({
|
|
67
|
-
requestId,
|
|
68
|
-
...sessionInfo,
|
|
69
|
-
isSensitive: ['true', '1', 'yes'].includes(_.toLower(isSensitiveHeaderValue)),
|
|
70
|
-
}, true);
|
|
71
|
-
|
|
72
|
-
return next();
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
*
|
|
77
|
-
* @param {import('express').Request} req
|
|
78
|
-
* @param {import('express').Response} res
|
|
79
|
-
* @param {import('express').NextFunction} next
|
|
80
|
-
* @returns {any}
|
|
81
|
-
*/
|
|
82
|
-
export function defaultToJSONContentType(req, res, next) {
|
|
83
|
-
if (!req.headers['content-type']) {
|
|
84
|
-
req.headers['content-type'] = 'application/json; charset=utf-8';
|
|
85
|
-
}
|
|
86
|
-
next();
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Core function to handle WebSocket upgrade requests by matching the request path
|
|
91
|
-
* against registered WebSocket handlers in the webSocketsMapping.
|
|
92
|
-
*
|
|
93
|
-
* @param {import('http').IncomingMessage} req - The HTTP request
|
|
94
|
-
* @param {import('stream').Duplex} socket - The network socket
|
|
95
|
-
* @param {Buffer} head - The first packet of the upgraded stream
|
|
96
|
-
* @param {import('@appium/types').StringRecord<import('@appium/types').WSServer>} webSocketsMapping - Mapping of paths to WebSocket servers
|
|
97
|
-
* @returns {boolean} - Returns true if the upgrade was handled, false otherwise
|
|
98
|
-
*/
|
|
99
|
-
export function tryHandleWebSocketUpgrade(req, socket, head, webSocketsMapping) {
|
|
100
|
-
if (_.toLower(req.headers?.upgrade) !== 'websocket') {
|
|
101
|
-
return false;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
let currentPathname;
|
|
105
|
-
try {
|
|
106
|
-
currentPathname = new URL(req.url ?? '', 'http://localhost').pathname;
|
|
107
|
-
} catch {
|
|
108
|
-
currentPathname = req.url ?? '';
|
|
109
|
-
}
|
|
110
|
-
for (const [pathname, wsServer] of _.toPairs(webSocketsMapping)) {
|
|
111
|
-
if (match(pathname)(currentPathname)) {
|
|
112
|
-
wsServer.handleUpgrade(req, socket, head, (ws) => {
|
|
113
|
-
wsServer.emit('connection', ws, req);
|
|
114
|
-
});
|
|
115
|
-
return true;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
log.info(`Did not match the websocket upgrade request at ${currentPathname} to any known route`);
|
|
119
|
-
return false;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
*
|
|
124
|
-
* @param {import('@appium/types').StringRecord<import('@appium/types').WSServer>} webSocketsMapping
|
|
125
|
-
* @returns {import('express').RequestHandler}
|
|
126
|
-
*/
|
|
127
|
-
export function handleUpgrade(webSocketsMapping) {
|
|
128
|
-
return (req, res, next) => {
|
|
129
|
-
if (tryHandleWebSocketUpgrade(req, req.socket, Buffer.from(''), webSocketsMapping)) {
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
next();
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* @param {Error} err
|
|
138
|
-
* @param {import('express').Request} req
|
|
139
|
-
* @param {import('express').Response} res
|
|
140
|
-
* @param {import('express').NextFunction} next
|
|
141
|
-
*/
|
|
142
|
-
export function catchAllHandler(err, req, res, next) {
|
|
143
|
-
if (res.headersSent) {
|
|
144
|
-
return next(err);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
log.error(`Uncaught error: ${err.message}`);
|
|
148
|
-
const [status, body] = getResponseForW3CError(err);
|
|
149
|
-
res.status(status).json(body);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* @param {import('express').Request} req
|
|
154
|
-
* @param {import('express').Response} res
|
|
155
|
-
*/
|
|
156
|
-
export function catch404Handler(req, res) {
|
|
157
|
-
log.debug(`No route found for ${req.url}`);
|
|
158
|
-
const [status, body] = getResponseForW3CError(new errors.UnknownCommandError());
|
|
159
|
-
res.status(status).json(body);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* @param {import('express').Request} req
|
|
164
|
-
* @param {string} name
|
|
165
|
-
* @returns {string | undefined}
|
|
166
|
-
*/
|
|
167
|
-
function fetchHeaderValue(req, name) {
|
|
168
|
-
return _.isArray(req.headers[name])
|
|
169
|
-
? req.headers[name][0]
|
|
170
|
-
: req.headers[name];
|
|
171
|
-
}
|