@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
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import type {AppiumLogger, HTTPBody, ProxyResponse} from '@appium/types';
|
|
2
|
+
import _ from 'lodash';
|
|
3
|
+
import {logger, util} from '@appium/support';
|
|
4
|
+
import {duplicateKeys} from '../basedriver/helpers';
|
|
5
|
+
import {MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY, PROTOCOLS} from '../constants';
|
|
6
|
+
|
|
7
|
+
export type ProxyFunction = (
|
|
8
|
+
url: string,
|
|
9
|
+
method: string,
|
|
10
|
+
body?: HTTPBody
|
|
11
|
+
) => Promise<[ProxyResponse, HTTPBody]>;
|
|
12
|
+
|
|
13
|
+
export const COMMAND_URLS_CONFLICTS = [
|
|
14
|
+
{
|
|
15
|
+
commandNames: ['execute', 'executeAsync'],
|
|
16
|
+
jsonwpConverter: (url: string) =>
|
|
17
|
+
url.replace(/\/execute.*/, url.includes('async') ? '/execute_async' : '/execute'),
|
|
18
|
+
w3cConverter: (url: string) =>
|
|
19
|
+
url.replace(/\/execute.*/, url.includes('async') ? '/execute/async' : '/execute/sync'),
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
commandNames: ['getElementScreenshot'],
|
|
23
|
+
jsonwpConverter: (url: string) => url.replace(/\/element\/([^/]+)\/screenshot$/, '/screenshot/$1'),
|
|
24
|
+
w3cConverter: (url: string) => url.replace(/\/screenshot\/([^/]+)/, '/element/$1/screenshot'),
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
commandNames: ['getWindowHandles', 'getWindowHandle'],
|
|
28
|
+
jsonwpConverter(url: string) {
|
|
29
|
+
return url.endsWith('/window')
|
|
30
|
+
? url.replace(/\/window$/, '/window_handle')
|
|
31
|
+
: url.replace(/\/window\/handle(s?)$/, '/window_handle$1');
|
|
32
|
+
},
|
|
33
|
+
w3cConverter(url: string) {
|
|
34
|
+
return url.endsWith('/window_handle')
|
|
35
|
+
? url.replace(/\/window_handle$/, '/window')
|
|
36
|
+
: url.replace(/\/window_handles$/, '/window/handles');
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
commandNames: ['getProperty'],
|
|
41
|
+
jsonwpConverter: (w3cUrl: string) => {
|
|
42
|
+
const w3cPropertyRegex = /\/element\/([^/]+)\/property\/([^/]+)/;
|
|
43
|
+
return w3cUrl.replace(w3cPropertyRegex, '/element/$1/attribute/$2');
|
|
44
|
+
},
|
|
45
|
+
// Don't convert JSONWP URL to W3C. W3C accepts /attribute and /property
|
|
46
|
+
w3cConverter: (jsonwpUrl: string) => jsonwpUrl,
|
|
47
|
+
},
|
|
48
|
+
] as const;
|
|
49
|
+
|
|
50
|
+
const {MJSONWP, W3C} = PROTOCOLS;
|
|
51
|
+
const DEFAULT_LOG = logger.getLogger('Protocol Converter');
|
|
52
|
+
|
|
53
|
+
export class ProtocolConverter {
|
|
54
|
+
private _downstreamProtocol: string | null | undefined = null;
|
|
55
|
+
private readonly _log: AppiumLogger | null;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @param proxyFunc - Function to perform the actual proxy request
|
|
59
|
+
* @param log - Logger instance, or null to use the default
|
|
60
|
+
*/
|
|
61
|
+
constructor(
|
|
62
|
+
public proxyFunc: ProxyFunction,
|
|
63
|
+
log: AppiumLogger | null = null
|
|
64
|
+
) {
|
|
65
|
+
this._log = log;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
get log(): AppiumLogger {
|
|
69
|
+
return this._log ?? DEFAULT_LOG;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
set downstreamProtocol(value: string | null | undefined) {
|
|
73
|
+
this._downstreamProtocol = value;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
get downstreamProtocol(): string | null | undefined {
|
|
77
|
+
return this._downstreamProtocol;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Handle "crossing" endpoints for the case when upstream and downstream
|
|
82
|
+
* drivers operate different protocols.
|
|
83
|
+
*/
|
|
84
|
+
async convertAndProxy(
|
|
85
|
+
commandName: string,
|
|
86
|
+
url: string,
|
|
87
|
+
method: string,
|
|
88
|
+
body?: HTTPBody
|
|
89
|
+
): Promise<[ProxyResponse, HTTPBody]> {
|
|
90
|
+
if (!this.downstreamProtocol) {
|
|
91
|
+
return await this.proxyFunc(url, method, body);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Same url, but different arguments
|
|
95
|
+
switch (commandName) {
|
|
96
|
+
case 'timeouts':
|
|
97
|
+
return await this.proxySetTimeouts(url, method, body);
|
|
98
|
+
case 'setWindow':
|
|
99
|
+
return await this.proxySetWindow(url, method, body);
|
|
100
|
+
case 'setValue':
|
|
101
|
+
return await this.proxySetValue(url, method, body);
|
|
102
|
+
case 'performActions':
|
|
103
|
+
return await this.proxyPerformActions(url, method, body);
|
|
104
|
+
case 'releaseActions':
|
|
105
|
+
return await this.proxyReleaseActions(url, method);
|
|
106
|
+
case 'setFrame':
|
|
107
|
+
return await this.proxySetFrame(url, method, body);
|
|
108
|
+
default:
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Same arguments, but different URLs
|
|
113
|
+
for (const {commandNames, jsonwpConverter, w3cConverter} of COMMAND_URLS_CONFLICTS) {
|
|
114
|
+
if (!(commandNames as readonly string[]).includes(commandName)) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const rewrittenUrl =
|
|
118
|
+
this.downstreamProtocol === MJSONWP ? jsonwpConverter(url) : w3cConverter(url);
|
|
119
|
+
if (rewrittenUrl === url) {
|
|
120
|
+
this.log.debug(
|
|
121
|
+
`Did not know how to rewrite the original URL '${url}' for ${this.downstreamProtocol} protocol`
|
|
122
|
+
);
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
this.log.info(
|
|
126
|
+
`Rewrote the original URL '${url}' to '${rewrittenUrl}' for ${this.downstreamProtocol} protocol`
|
|
127
|
+
);
|
|
128
|
+
return await this.proxyFunc(rewrittenUrl, method, body);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// No matches found. Proceed normally
|
|
132
|
+
return await this.proxyFunc(url, method, body);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* W3C /timeouts can take as many as 3 timeout types at once, MJSONWP /timeouts only takes one
|
|
137
|
+
* at a time. So if we're using W3C and proxying to MJSONWP and there's more than one timeout type
|
|
138
|
+
* provided in the request, we need to do 3 proxies and combine the result.
|
|
139
|
+
*/
|
|
140
|
+
private getTimeoutRequestObjects(body: HTTPBody): Record<string, unknown>[] {
|
|
141
|
+
if (_.isNil(body)) {
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const bodyObj = (util.safeJsonParse(body) as Record<string, unknown>) ?? {};
|
|
146
|
+
if (this.downstreamProtocol === W3C && _.has(bodyObj, 'ms') && _.has(bodyObj, 'type')) {
|
|
147
|
+
const typeToW3C = (x: string) => (x === 'page load' ? 'pageLoad' : x);
|
|
148
|
+
return [
|
|
149
|
+
{
|
|
150
|
+
[typeToW3C(bodyObj.type as string)]: bodyObj.ms,
|
|
151
|
+
},
|
|
152
|
+
];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (this.downstreamProtocol === MJSONWP && (!_.has(bodyObj, 'ms') || !_.has(bodyObj, 'type'))) {
|
|
156
|
+
const typeToJSONWP = (x: string) => (x === 'pageLoad' ? 'page load' : x);
|
|
157
|
+
return _.toPairs(bodyObj)
|
|
158
|
+
// Only transform the entry if ms value is a valid positive float number
|
|
159
|
+
.filter((pair) => /^\d+(?:[.,]\d*?)?$/.test(`${pair[1]}`))
|
|
160
|
+
.map((pair) => ({
|
|
161
|
+
type: typeToJSONWP(pair[0]),
|
|
162
|
+
ms: pair[1],
|
|
163
|
+
}));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return [bodyObj];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Proxy an array of timeout objects and merge the result.
|
|
171
|
+
*/
|
|
172
|
+
private async proxySetTimeouts(
|
|
173
|
+
url: string,
|
|
174
|
+
method: string,
|
|
175
|
+
body?: HTTPBody
|
|
176
|
+
): Promise<[ProxyResponse, HTTPBody]> {
|
|
177
|
+
const timeoutRequestObjects = this.getTimeoutRequestObjects(body);
|
|
178
|
+
if (timeoutRequestObjects.length === 0) {
|
|
179
|
+
return await this.proxyFunc(url, method, body);
|
|
180
|
+
}
|
|
181
|
+
this.log.debug(
|
|
182
|
+
`Will send the following request bodies to /timeouts: ${JSON.stringify(timeoutRequestObjects)}`
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
let response!: ProxyResponse;
|
|
186
|
+
let resBody!: HTTPBody;
|
|
187
|
+
for (const timeoutObj of timeoutRequestObjects) {
|
|
188
|
+
[response, resBody] = await this.proxyFunc(url, method, timeoutObj as HTTPBody);
|
|
189
|
+
|
|
190
|
+
// If we got a non-MJSONWP response, return the result, nothing left to do
|
|
191
|
+
if (this.downstreamProtocol !== MJSONWP) {
|
|
192
|
+
return [response, resBody];
|
|
193
|
+
}
|
|
194
|
+
// If we got an error, return the error right away
|
|
195
|
+
if (response.statusCode >= 400) {
|
|
196
|
+
return [response, resBody];
|
|
197
|
+
}
|
|
198
|
+
// ...Otherwise, continue to the next timeouts call
|
|
199
|
+
}
|
|
200
|
+
return [response, resBody];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private async proxySetWindow(
|
|
204
|
+
url: string,
|
|
205
|
+
method: string,
|
|
206
|
+
body: HTTPBody
|
|
207
|
+
): Promise<[ProxyResponse, HTTPBody]> {
|
|
208
|
+
const bodyObj = util.safeJsonParse(body);
|
|
209
|
+
if (_.isPlainObject(bodyObj)) {
|
|
210
|
+
const obj = bodyObj as Record<string, unknown>;
|
|
211
|
+
if (this.downstreamProtocol === W3C && _.has(bodyObj, 'name') && !_.has(bodyObj, 'handle')) {
|
|
212
|
+
this.log.debug(`Copied 'name' value '${obj.name}' to 'handle' as per W3C spec`);
|
|
213
|
+
return await this.proxyFunc(url, method, {...obj, handle: obj.name});
|
|
214
|
+
}
|
|
215
|
+
if (
|
|
216
|
+
this.downstreamProtocol === MJSONWP &&
|
|
217
|
+
_.has(bodyObj, 'handle') &&
|
|
218
|
+
!_.has(bodyObj, 'name')
|
|
219
|
+
) {
|
|
220
|
+
this.log.debug(`Copied 'handle' value '${obj.handle}' to 'name' as per JSONWP spec`);
|
|
221
|
+
return await this.proxyFunc(url, method, {...obj, name: obj.handle});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return await this.proxyFunc(url, method, body);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private async proxySetValue(
|
|
228
|
+
url: string,
|
|
229
|
+
method: string,
|
|
230
|
+
body: HTTPBody
|
|
231
|
+
): Promise<[ProxyResponse, HTTPBody]> {
|
|
232
|
+
const bodyObj = util.safeJsonParse(body) as Record<string, unknown> | undefined;
|
|
233
|
+
if (_.isPlainObject(bodyObj) && (util.hasValue(bodyObj?.text) || util.hasValue(bodyObj?.value))) {
|
|
234
|
+
let {text, value} = bodyObj;
|
|
235
|
+
if (util.hasValue(text) && !util.hasValue(value)) {
|
|
236
|
+
value = _.isString(text) ? [...text] : _.isArray(text) ? text : [];
|
|
237
|
+
this.log.debug(`Added 'value' property to 'setValue' request body`);
|
|
238
|
+
} else if (!util.hasValue(text) && util.hasValue(value)) {
|
|
239
|
+
text = _.isArray(value) ? value.join('') : _.isString(value) ? value : '';
|
|
240
|
+
this.log.debug(`Added 'text' property to 'setValue' request body`);
|
|
241
|
+
}
|
|
242
|
+
return await this.proxyFunc(url, method, {...bodyObj, text, value});
|
|
243
|
+
}
|
|
244
|
+
return await this.proxyFunc(url, method, body);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private async proxySetFrame(
|
|
248
|
+
url: string,
|
|
249
|
+
method: string,
|
|
250
|
+
body: HTTPBody
|
|
251
|
+
): Promise<[ProxyResponse, HTTPBody]> {
|
|
252
|
+
const bodyObj = util.safeJsonParse(body);
|
|
253
|
+
if (_.has(bodyObj, 'id') && _.isPlainObject(bodyObj.id)) {
|
|
254
|
+
return await this.proxyFunc(url, method, {
|
|
255
|
+
...(bodyObj as object),
|
|
256
|
+
id: duplicateKeys(bodyObj.id as object, MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY),
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
return await this.proxyFunc(url, method, body);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private async proxyPerformActions(
|
|
263
|
+
url: string,
|
|
264
|
+
method: string,
|
|
265
|
+
body: HTTPBody
|
|
266
|
+
): Promise<[ProxyResponse, HTTPBody]> {
|
|
267
|
+
const bodyObj = util.safeJsonParse(body);
|
|
268
|
+
if (_.isPlainObject(bodyObj)) {
|
|
269
|
+
return await this.proxyFunc(
|
|
270
|
+
url,
|
|
271
|
+
method,
|
|
272
|
+
duplicateKeys(bodyObj as object, MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY)
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
return await this.proxyFunc(url, method, body);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
private async proxyReleaseActions(
|
|
279
|
+
url: string,
|
|
280
|
+
method: string
|
|
281
|
+
): Promise<[ProxyResponse, HTTPBody]> {
|
|
282
|
+
return await this.proxyFunc(url, method);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from '../protocol/errors';
|
|
11
11
|
import {isSessionCommand, routeToCommandName} from '../protocol';
|
|
12
12
|
import {MAX_LOG_BODY_LENGTH, DEFAULT_BASE_PATH, PROTOCOLS} from '../constants';
|
|
13
|
-
import ProtocolConverter from './protocol-converter';
|
|
13
|
+
import {ProtocolConverter} from './protocol-converter';
|
|
14
14
|
import {formatResponseValue, ensureW3cResponse} from '../protocol/helpers';
|
|
15
15
|
import http from 'node:http';
|
|
16
16
|
import https from 'node:https';
|
|
@@ -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};
|