@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/express/static.js
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import log from './logger';
|
|
3
|
-
import _ from 'lodash';
|
|
4
|
-
import {fs} from '@appium/support';
|
|
5
|
-
import B from 'bluebird';
|
|
6
|
-
|
|
7
|
-
let STATIC_DIR = path.resolve(__dirname, '..', '..', '..', 'static');
|
|
8
|
-
if (_.isNull(path.resolve(__dirname).match(/build[/\\]lib[/\\]express$/))) {
|
|
9
|
-
// in some contexts we are not in the build directory,
|
|
10
|
-
// so we don't want to go back the extra level
|
|
11
|
-
STATIC_DIR = path.resolve(__dirname, '..', '..', 'static');
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
async function guineaPigTemplate(req, res, page) {
|
|
15
|
-
const delay = parseInt(req.params.delay || req.query.delay || 0, 10);
|
|
16
|
-
const throwError = req.params.throwError || req.query.throwError || '';
|
|
17
|
-
let params = {
|
|
18
|
-
throwError,
|
|
19
|
-
serverTime: new Date(),
|
|
20
|
-
userAgent: req.headers['user-agent'],
|
|
21
|
-
comment: 'None',
|
|
22
|
-
};
|
|
23
|
-
if (req.method === 'POST') {
|
|
24
|
-
params.comment = req.body.comments || params.comment;
|
|
25
|
-
}
|
|
26
|
-
log.debug(`Sending guinea pig response with params: ${JSON.stringify(params)}`);
|
|
27
|
-
if (delay) {
|
|
28
|
-
log.debug(`Waiting ${delay}ms before responding`);
|
|
29
|
-
await B.delay(delay);
|
|
30
|
-
}
|
|
31
|
-
res.set('content-type', 'text/html');
|
|
32
|
-
res.cookie('guineacookie1', 'i am a cookie value', {path: '/'});
|
|
33
|
-
res.cookie('guineacookie2', 'cookié2', {path: '/'});
|
|
34
|
-
res.cookie('guineacookie3', 'cant access this', {
|
|
35
|
-
domain: '.blargimarg.com',
|
|
36
|
-
path: '/',
|
|
37
|
-
});
|
|
38
|
-
res.send((await getTemplate(page))(params));
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/*
|
|
42
|
-
* Dynamic page mapped to /test/guinea-pig
|
|
43
|
-
*/
|
|
44
|
-
async function guineaPig(req, res) {
|
|
45
|
-
return await guineaPigTemplate(req, res, 'guinea-pig.html');
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/*
|
|
49
|
-
* Dynamic page mapped to /test/guinea-pig-scrollable
|
|
50
|
-
*/
|
|
51
|
-
async function guineaPigScrollable(req, res) {
|
|
52
|
-
return await guineaPigTemplate(req, res, 'guinea-pig-scrollable.html');
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/*
|
|
56
|
-
* Dynamic page mapped to /test/guinea-pig-app-banner
|
|
57
|
-
*/
|
|
58
|
-
async function guineaPigAppBanner(req, res) {
|
|
59
|
-
return await guineaPigTemplate(req, res, 'guinea-pig-app-banner.html');
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/*
|
|
63
|
-
* Dynamic page mapped to /welcome
|
|
64
|
-
*/
|
|
65
|
-
async function welcome(req, res) {
|
|
66
|
-
let params = {message: "Let's browse!"};
|
|
67
|
-
log.debug(`Sending welcome response with params: ${JSON.stringify(params)}`);
|
|
68
|
-
res.send((await getTemplate('welcome.html'))(params));
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
async function getTemplate(templateName) {
|
|
72
|
-
let content = await fs.readFile(path.resolve(STATIC_DIR, 'test', templateName));
|
|
73
|
-
return _.template(content.toString());
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export {guineaPig, guineaPigScrollable, guineaPigAppBanner, welcome, STATIC_DIR};
|
package/lib/express/websocket.js
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import _ from 'lodash';
|
|
3
|
-
import B from 'bluebird';
|
|
4
|
-
|
|
5
|
-
const DEFAULT_WS_PATHNAME_PREFIX = '/ws';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* @this {AppiumServer}
|
|
9
|
-
* @type {AppiumServer['addWebSocketHandler']}
|
|
10
|
-
*/
|
|
11
|
-
async function addWebSocketHandler(handlerPathname, handlerServer) {
|
|
12
|
-
this.webSocketsMapping[handlerPathname] = handlerServer;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* @this {AppiumServer}
|
|
17
|
-
* @type {AppiumServer['getWebSocketHandlers']}
|
|
18
|
-
*/
|
|
19
|
-
async function getWebSocketHandlers(keysFilter = null) {
|
|
20
|
-
return _.toPairs(this.webSocketsMapping).reduce((acc, [pathname, wsServer]) => {
|
|
21
|
-
if (!_.isString(keysFilter) || pathname.includes(keysFilter)) {
|
|
22
|
-
acc[pathname] = wsServer;
|
|
23
|
-
}
|
|
24
|
-
return acc;
|
|
25
|
-
}, {});
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* @this {AppiumServer}
|
|
30
|
-
* @type {AppiumServer['removeWebSocketHandler']}
|
|
31
|
-
*/
|
|
32
|
-
async function removeWebSocketHandler(handlerPathname) {
|
|
33
|
-
const wsServer = this.webSocketsMapping?.[handlerPathname];
|
|
34
|
-
if (!wsServer) {
|
|
35
|
-
return false;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
try {
|
|
39
|
-
wsServer.close();
|
|
40
|
-
for (const client of wsServer.clients || []) {
|
|
41
|
-
client.terminate();
|
|
42
|
-
}
|
|
43
|
-
return true;
|
|
44
|
-
} catch {
|
|
45
|
-
// ignore
|
|
46
|
-
} finally {
|
|
47
|
-
delete this.webSocketsMapping[handlerPathname];
|
|
48
|
-
}
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
*
|
|
54
|
-
* @this {AppiumServer}
|
|
55
|
-
* @type {AppiumServer['removeAllWebSocketHandlers']}
|
|
56
|
-
*/
|
|
57
|
-
async function removeAllWebSocketHandlers() {
|
|
58
|
-
if (_.isEmpty(this.webSocketsMapping)) {
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return _.some(
|
|
63
|
-
await B.all(
|
|
64
|
-
_.keys(this.webSocketsMapping).map((pathname) => this.removeWebSocketHandler(pathname))
|
|
65
|
-
)
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export {
|
|
70
|
-
addWebSocketHandler,
|
|
71
|
-
removeWebSocketHandler,
|
|
72
|
-
removeAllWebSocketHandlers,
|
|
73
|
-
getWebSocketHandlers,
|
|
74
|
-
DEFAULT_WS_PATHNAME_PREFIX,
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* @typedef {import('@appium/types').AppiumServer} AppiumServer
|
|
79
|
-
*/
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
|
|
3
|
-
import _ from 'lodash';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Determine whether the given argument is valid
|
|
7
|
-
* W3C capabilities instance.
|
|
8
|
-
*
|
|
9
|
-
* @param {any} caps
|
|
10
|
-
* @returns {caps is import('@appium/types').W3CCapabilities}
|
|
11
|
-
*/
|
|
12
|
-
export function isW3cCaps(caps) {
|
|
13
|
-
if (!_.isPlainObject(caps)) {
|
|
14
|
-
return false;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const isFirstMatchValid = () =>
|
|
18
|
-
_.isArray(caps.firstMatch) &&
|
|
19
|
-
!_.isEmpty(caps.firstMatch) &&
|
|
20
|
-
_.every(caps.firstMatch, _.isPlainObject);
|
|
21
|
-
const isAlwaysMatchValid = () => _.isPlainObject(caps.alwaysMatch);
|
|
22
|
-
if (_.has(caps, 'firstMatch') && _.has(caps, 'alwaysMatch')) {
|
|
23
|
-
return isFirstMatchValid() && isAlwaysMatchValid();
|
|
24
|
-
}
|
|
25
|
-
if (_.has(caps, 'firstMatch')) {
|
|
26
|
-
return isFirstMatchValid();
|
|
27
|
-
}
|
|
28
|
-
if (_.has(caps, 'alwaysMatch')) {
|
|
29
|
-
return isAlwaysMatchValid();
|
|
30
|
-
}
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
*
|
|
36
|
-
* @template {Constraints} C
|
|
37
|
-
* @param {any} oldCaps
|
|
38
|
-
* @param {C} desiredCapConstraints
|
|
39
|
-
* @param {AppiumLogger} log
|
|
40
|
-
* @returns {Capabilities<C>}
|
|
41
|
-
*/
|
|
42
|
-
export function fixCaps(oldCaps, desiredCapConstraints, log) {
|
|
43
|
-
let caps = _.clone(oldCaps);
|
|
44
|
-
|
|
45
|
-
// boolean capabilities can be passed in as strings 'false' and 'true'
|
|
46
|
-
// which we want to translate into boolean values
|
|
47
|
-
let booleanCaps = _.keys(_.pickBy(desiredCapConstraints, (k) => k.isBoolean === true));
|
|
48
|
-
for (let cap of booleanCaps) {
|
|
49
|
-
let value = oldCaps[cap];
|
|
50
|
-
if (_.isString(value)) {
|
|
51
|
-
value = value.toLowerCase();
|
|
52
|
-
if (value === 'true' || value === 'false') {
|
|
53
|
-
log.warn(
|
|
54
|
-
`Capability '${cap}' changed from string to boolean. This may cause unexpected behavior`
|
|
55
|
-
);
|
|
56
|
-
caps[cap] = value === 'true';
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// int capabilities are often sent in as strings by frameworks
|
|
62
|
-
let intCaps = /** @type {import('type-fest').KeyAsString<typeof caps>[]} */ (
|
|
63
|
-
_.keys(_.pickBy(desiredCapConstraints, (k) => k.isNumber === true))
|
|
64
|
-
);
|
|
65
|
-
for (let cap of intCaps) {
|
|
66
|
-
let value = oldCaps[cap];
|
|
67
|
-
if (_.isString(value)) {
|
|
68
|
-
value = value.trim();
|
|
69
|
-
let newValue = parseInt(value, 10);
|
|
70
|
-
if (value !== `${newValue}`) {
|
|
71
|
-
newValue = parseFloat(value);
|
|
72
|
-
}
|
|
73
|
-
log.warn(
|
|
74
|
-
`Capability '${cap}' changed from string ('${value}') to integer (${newValue}). This may cause unexpected behavior`
|
|
75
|
-
);
|
|
76
|
-
caps[cap] = newValue;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return caps;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* @typedef {import('@appium/types').Constraints} Constraints
|
|
85
|
-
* @typedef {import('@appium/types').AppiumLogger} AppiumLogger
|
|
86
|
-
* @typedef {import('@appium/types').StringRecord} StringRecord
|
|
87
|
-
* @typedef {import('@appium/types').BaseDriverCapConstraints} BaseDriverCapConstraints
|
|
88
|
-
*/
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* @template {Constraints} C
|
|
92
|
-
* @typedef {import('@appium/types').Capabilities<C>} Capabilities
|
|
93
|
-
*/
|
|
@@ -1,317 +0,0 @@
|
|
|
1
|
-
import _ from 'lodash';
|
|
2
|
-
import {logger, util} from '@appium/support';
|
|
3
|
-
import {duplicateKeys} from '../basedriver/helpers';
|
|
4
|
-
import {MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY, PROTOCOLS} from '../constants';
|
|
5
|
-
|
|
6
|
-
export const COMMAND_URLS_CONFLICTS = [
|
|
7
|
-
{
|
|
8
|
-
commandNames: ['execute', 'executeAsync'],
|
|
9
|
-
jsonwpConverter: (url) =>
|
|
10
|
-
url.replace(/\/execute.*/, url.includes('async') ? '/execute_async' : '/execute'),
|
|
11
|
-
w3cConverter: (url) =>
|
|
12
|
-
url.replace(/\/execute.*/, url.includes('async') ? '/execute/async' : '/execute/sync'),
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
commandNames: ['getElementScreenshot'],
|
|
16
|
-
jsonwpConverter: (url) => url.replace(/\/element\/([^/]+)\/screenshot$/, '/screenshot/$1'),
|
|
17
|
-
w3cConverter: (url) => url.replace(/\/screenshot\/([^/]+)/, '/element/$1/screenshot'),
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
commandNames: ['getWindowHandles', 'getWindowHandle'],
|
|
21
|
-
jsonwpConverter(url) {
|
|
22
|
-
return url.endsWith('/window')
|
|
23
|
-
? url.replace(/\/window$/, '/window_handle')
|
|
24
|
-
: url.replace(/\/window\/handle(s?)$/, '/window_handle$1');
|
|
25
|
-
},
|
|
26
|
-
w3cConverter(url) {
|
|
27
|
-
return url.endsWith('/window_handle')
|
|
28
|
-
? url.replace(/\/window_handle$/, '/window')
|
|
29
|
-
: url.replace(/\/window_handles$/, '/window/handles');
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
commandNames: ['getProperty'],
|
|
34
|
-
jsonwpConverter: (w3cUrl) => {
|
|
35
|
-
const w3cPropertyRegex = /\/element\/([^/]+)\/property\/([^/]+)/;
|
|
36
|
-
const jsonwpUrl = w3cUrl.replace(w3cPropertyRegex, '/element/$1/attribute/$2');
|
|
37
|
-
return jsonwpUrl;
|
|
38
|
-
},
|
|
39
|
-
w3cConverter: (jsonwpUrl) => jsonwpUrl, // Don't convert JSONWP URL to W3C. W3C accepts /attribute and /property
|
|
40
|
-
},
|
|
41
|
-
];
|
|
42
|
-
const {MJSONWP, W3C} = PROTOCOLS;
|
|
43
|
-
const DEFAULT_LOG = logger.getLogger('Protocol Converter');
|
|
44
|
-
|
|
45
|
-
class ProtocolConverter {
|
|
46
|
-
/**
|
|
47
|
-
*
|
|
48
|
-
* @param {ProxyFunction} proxyFunc
|
|
49
|
-
* @param {import('@appium/types').AppiumLogger | null} [log=null]
|
|
50
|
-
*/
|
|
51
|
-
constructor(proxyFunc, log = null) {
|
|
52
|
-
this.proxyFunc = proxyFunc;
|
|
53
|
-
this._downstreamProtocol = null;
|
|
54
|
-
this._log = log;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
get log() {
|
|
58
|
-
return this._log ?? DEFAULT_LOG;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
set downstreamProtocol(value) {
|
|
62
|
-
this._downstreamProtocol = value;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
get downstreamProtocol() {
|
|
66
|
-
return this._downstreamProtocol;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* W3C /timeouts can take as many as 3 timeout types at once, MJSONWP /timeouts only takes one
|
|
71
|
-
* at a time. So if we're using W3C and proxying to MJSONWP and there's more than one timeout type
|
|
72
|
-
* provided in the request, we need to do 3 proxies and combine the result
|
|
73
|
-
*
|
|
74
|
-
* @param {Object} body Request body
|
|
75
|
-
* @return {Object[]} Array of W3C + MJSONWP compatible timeout objects
|
|
76
|
-
*/
|
|
77
|
-
getTimeoutRequestObjects(body) {
|
|
78
|
-
if (this.downstreamProtocol === W3C && _.has(body, 'ms') && _.has(body, 'type')) {
|
|
79
|
-
const typeToW3C = (x) => (x === 'page load' ? 'pageLoad' : x);
|
|
80
|
-
return [
|
|
81
|
-
{
|
|
82
|
-
[typeToW3C(body.type)]: body.ms,
|
|
83
|
-
},
|
|
84
|
-
];
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (this.downstreamProtocol === MJSONWP && (!_.has(body, 'ms') || !_.has(body, 'type'))) {
|
|
88
|
-
const typeToJSONWP = (x) => (x === 'pageLoad' ? 'page load' : x);
|
|
89
|
-
return (
|
|
90
|
-
_.toPairs(body)
|
|
91
|
-
// Only transform the entry if ms value is a valid positive float number
|
|
92
|
-
.filter((pair) => /^\d+(?:[.,]\d*?)?$/.test(`${pair[1]}`))
|
|
93
|
-
.map(function (pair) {
|
|
94
|
-
return {
|
|
95
|
-
type: typeToJSONWP(pair[0]),
|
|
96
|
-
ms: pair[1],
|
|
97
|
-
};
|
|
98
|
-
})
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return [body];
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Proxy an array of timeout objects and merge the result
|
|
107
|
-
* @param {string} url Endpoint url
|
|
108
|
-
* @param {string} method Endpoint method
|
|
109
|
-
* @param {import('@appium/types').HTTPBody} body Request body
|
|
110
|
-
* @returns {Promise<[import('@appium/types').ProxyResponse, import('@appium/types').HTTPBody]>}
|
|
111
|
-
*/
|
|
112
|
-
async proxySetTimeouts(url, method, body) {
|
|
113
|
-
let response, resBody;
|
|
114
|
-
|
|
115
|
-
const timeoutRequestObjects = this.getTimeoutRequestObjects(body);
|
|
116
|
-
this.log.debug(
|
|
117
|
-
`Will send the following request bodies to /timeouts: ${JSON.stringify(
|
|
118
|
-
timeoutRequestObjects
|
|
119
|
-
)}`
|
|
120
|
-
);
|
|
121
|
-
for (const timeoutObj of timeoutRequestObjects) {
|
|
122
|
-
[response, resBody] = await this.proxyFunc(url, method, timeoutObj);
|
|
123
|
-
|
|
124
|
-
// If we got a non-MJSONWP response, return the result, nothing left to do
|
|
125
|
-
if (this.downstreamProtocol !== MJSONWP) {
|
|
126
|
-
return [response, resBody];
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// If we got an error, return the error right away
|
|
130
|
-
if (response.statusCode >= 400) {
|
|
131
|
-
return [response, resBody];
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// ...Otherwise, continue to the next timeouts call
|
|
135
|
-
}
|
|
136
|
-
return [/** @type {import('@appium/types').ProxyResponse} */(response), resBody];
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
*
|
|
141
|
-
* @param {string} url
|
|
142
|
-
* @param {string} method
|
|
143
|
-
* @param {import('@appium/types').HTTPBody} body
|
|
144
|
-
* @returns {Promise<[import('@appium/types').ProxyResponse, import('@appium/types').HTTPBody]>}
|
|
145
|
-
*/
|
|
146
|
-
async proxySetWindow(url, method, body) {
|
|
147
|
-
const bodyObj = util.safeJsonParse(body);
|
|
148
|
-
if (_.isPlainObject(bodyObj)) {
|
|
149
|
-
if (this.downstreamProtocol === W3C && _.has(bodyObj, 'name') && !_.has(bodyObj, 'handle')) {
|
|
150
|
-
this.log.debug(
|
|
151
|
-
`Copied 'name' value '${/** @type {import('@appium/types').StringRecord} */ (bodyObj).name}' to 'handle' as per W3C spec`
|
|
152
|
-
);
|
|
153
|
-
return await this.proxyFunc(url, method, {
|
|
154
|
-
.../** @type {import('@appium/types').StringRecord} */ (bodyObj),
|
|
155
|
-
handle: /** @type {import('@appium/types').StringRecord} */ (bodyObj).name,
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
if (
|
|
159
|
-
this.downstreamProtocol === MJSONWP &&
|
|
160
|
-
_.has(bodyObj, 'handle') &&
|
|
161
|
-
!_.has(bodyObj, 'name')
|
|
162
|
-
) {
|
|
163
|
-
this.log.debug(
|
|
164
|
-
`Copied 'handle' value '${/** @type {import('@appium/types').StringRecord} */ (bodyObj).handle}' to 'name' as per JSONWP spec`
|
|
165
|
-
);
|
|
166
|
-
return await this.proxyFunc(url, method, {
|
|
167
|
-
.../** @type {import('@appium/types').StringRecord} */ (bodyObj),
|
|
168
|
-
name: /** @type {import('@appium/types').StringRecord} */ (bodyObj).handle,
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return await this.proxyFunc(url, method, body);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
*
|
|
178
|
-
* @param {string} url
|
|
179
|
-
* @param {string} method
|
|
180
|
-
* @param {import('@appium/types').HTTPBody} body
|
|
181
|
-
* @returns {Promise<[import('@appium/types').ProxyResponse, import('@appium/types').HTTPBody]>}
|
|
182
|
-
*/
|
|
183
|
-
async proxySetValue(url, method, body) {
|
|
184
|
-
const bodyObj = util.safeJsonParse(body);
|
|
185
|
-
if (_.isPlainObject(bodyObj) && (util.hasValue(bodyObj.text) || util.hasValue(bodyObj.value))) {
|
|
186
|
-
let {text, value} = bodyObj;
|
|
187
|
-
if (util.hasValue(text) && !util.hasValue(value)) {
|
|
188
|
-
value = _.isString(text) ? [...text] : _.isArray(text) ? text : [];
|
|
189
|
-
this.log.debug(`Added 'value' property to 'setValue' request body`);
|
|
190
|
-
} else if (!util.hasValue(text) && util.hasValue(value)) {
|
|
191
|
-
text = _.isArray(value) ? value.join('') : _.isString(value) ? value : '';
|
|
192
|
-
this.log.debug(`Added 'text' property to 'setValue' request body`);
|
|
193
|
-
}
|
|
194
|
-
return await this.proxyFunc(
|
|
195
|
-
url,
|
|
196
|
-
method,
|
|
197
|
-
{
|
|
198
|
-
...bodyObj,
|
|
199
|
-
text,
|
|
200
|
-
value,
|
|
201
|
-
}
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return await this.proxyFunc(url, method, body);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
*
|
|
210
|
-
* @param {string} url
|
|
211
|
-
* @param {string} method
|
|
212
|
-
* @param {import('@appium/types').HTTPBody} body
|
|
213
|
-
* @returns {Promise<[import('@appium/types').ProxyResponse, import('@appium/types').HTTPBody]>}
|
|
214
|
-
*/
|
|
215
|
-
async proxySetFrame(url, method, body) {
|
|
216
|
-
const bodyObj = util.safeJsonParse(body);
|
|
217
|
-
return _.has(bodyObj, 'id') && _.isPlainObject(bodyObj.id)
|
|
218
|
-
? await this.proxyFunc(url, method, {
|
|
219
|
-
...bodyObj,
|
|
220
|
-
id: duplicateKeys(bodyObj.id, MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY),
|
|
221
|
-
})
|
|
222
|
-
: await this.proxyFunc(url, method, body);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
*
|
|
227
|
-
* @param {string} url
|
|
228
|
-
* @param {string} method
|
|
229
|
-
* @param {import('@appium/types').HTTPBody} body
|
|
230
|
-
* @returns {Promise<[import('@appium/types').ProxyResponse, import('@appium/types').HTTPBody]>}
|
|
231
|
-
*/
|
|
232
|
-
async proxyPerformActions(url, method, body) {
|
|
233
|
-
const bodyObj = util.safeJsonParse(body);
|
|
234
|
-
return _.isPlainObject(bodyObj)
|
|
235
|
-
? await this.proxyFunc(
|
|
236
|
-
url,
|
|
237
|
-
method,
|
|
238
|
-
duplicateKeys(bodyObj, MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY)
|
|
239
|
-
)
|
|
240
|
-
: await this.proxyFunc(url, method, body);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
*
|
|
245
|
-
* @param {string} url
|
|
246
|
-
* @param {string} method
|
|
247
|
-
* @returns {Promise<[import('@appium/types').ProxyResponse, import('@appium/types').HTTPBody]>}
|
|
248
|
-
*/
|
|
249
|
-
async proxyReleaseActions(url, method) {
|
|
250
|
-
return await this.proxyFunc(url, method);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Handle "crossing" endpoints for the case
|
|
255
|
-
* when upstream and downstream drivers operate different protocols
|
|
256
|
-
*
|
|
257
|
-
* @param {string} commandName
|
|
258
|
-
* @param {string} url
|
|
259
|
-
* @param {string} method
|
|
260
|
-
* @param {import('@appium/types').HTTPBody} [body]
|
|
261
|
-
* @returns {Promise<[import('@appium/types').ProxyResponse, import('@appium/types').HTTPBody]>}
|
|
262
|
-
*/
|
|
263
|
-
async convertAndProxy(commandName, url, method, body) {
|
|
264
|
-
if (!this.downstreamProtocol) {
|
|
265
|
-
return await this.proxyFunc(url, method, body);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// Same url, but different arguments
|
|
269
|
-
switch (commandName) {
|
|
270
|
-
case 'timeouts':
|
|
271
|
-
return await this.proxySetTimeouts(url, method, body);
|
|
272
|
-
case 'setWindow':
|
|
273
|
-
return await this.proxySetWindow(url, method, body);
|
|
274
|
-
case 'setValue':
|
|
275
|
-
return await this.proxySetValue(url, method, body);
|
|
276
|
-
case 'performActions':
|
|
277
|
-
return await this.proxyPerformActions(url, method, body);
|
|
278
|
-
case 'releaseActions':
|
|
279
|
-
return await this.proxyReleaseActions(url, method);
|
|
280
|
-
case 'setFrame':
|
|
281
|
-
return await this.proxySetFrame(url, method, body);
|
|
282
|
-
default:
|
|
283
|
-
break;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// Same arguments, but different URLs
|
|
287
|
-
for (const {commandNames, jsonwpConverter, w3cConverter} of COMMAND_URLS_CONFLICTS) {
|
|
288
|
-
if (!commandNames.includes(commandName)) {
|
|
289
|
-
continue;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
const rewrittenUrl =
|
|
293
|
-
this.downstreamProtocol === MJSONWP ? jsonwpConverter(url) : w3cConverter(url);
|
|
294
|
-
if (rewrittenUrl === url) {
|
|
295
|
-
this.log.debug(
|
|
296
|
-
`Did not know how to rewrite the original URL '${url}' ` +
|
|
297
|
-
`for ${this.downstreamProtocol} protocol`
|
|
298
|
-
);
|
|
299
|
-
break;
|
|
300
|
-
}
|
|
301
|
-
this.log.info(
|
|
302
|
-
`Rewrote the original URL '${url}' to '${rewrittenUrl}' ` +
|
|
303
|
-
`for ${this.downstreamProtocol} protocol`
|
|
304
|
-
);
|
|
305
|
-
return await this.proxyFunc(rewrittenUrl, method, body);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// No matches found. Proceed normally
|
|
309
|
-
return await this.proxyFunc(url, method, body);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
export default ProtocolConverter;
|
|
314
|
-
|
|
315
|
-
/**
|
|
316
|
-
* @typedef {(url: string, method: string, body?: import('@appium/types').HTTPBody) => Promise<[import('@appium/types').ProxyResponse, import('@appium/types').HTTPBody]>} ProxyFunction
|
|
317
|
-
*/
|
|
File without changes
|