@appium/base-driver 10.6.0 → 10.7.0
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/build/lib/basedriver/capabilities.d.ts +1 -1
- package/build/lib/basedriver/capabilities.d.ts.map +1 -1
- package/build/lib/basedriver/capabilities.js +15 -7
- package/build/lib/basedriver/capabilities.js.map +1 -1
- package/build/lib/basedriver/commands/bidi.js +1 -1
- package/build/lib/basedriver/commands/event.js.map +1 -1
- package/build/lib/basedriver/commands/execute.js.map +1 -1
- package/build/lib/basedriver/commands/find.d.ts.map +1 -1
- package/build/lib/basedriver/commands/find.js +2 -1
- package/build/lib/basedriver/commands/find.js.map +1 -1
- package/build/lib/basedriver/commands/timeout.js +4 -4
- package/build/lib/basedriver/commands/timeout.js.map +1 -1
- package/build/lib/basedriver/core.d.ts.map +1 -1
- package/build/lib/basedriver/core.js +5 -2
- package/build/lib/basedriver/core.js.map +1 -1
- package/build/lib/basedriver/device-settings.d.ts.map +1 -1
- package/build/lib/basedriver/device-settings.js.map +1 -1
- package/build/lib/basedriver/driver.d.ts.map +1 -1
- package/build/lib/basedriver/driver.js +23 -24
- package/build/lib/basedriver/driver.js.map +1 -1
- package/build/lib/basedriver/extension-core.d.ts.map +1 -1
- package/build/lib/basedriver/extension-core.js +11 -5
- package/build/lib/basedriver/extension-core.js.map +1 -1
- package/build/lib/basedriver/helpers.d.ts.map +1 -1
- package/build/lib/basedriver/helpers.js +20 -4
- package/build/lib/basedriver/helpers.js.map +1 -1
- package/build/lib/basedriver/ipc.d.ts.map +1 -1
- package/build/lib/basedriver/ipc.js +6 -4
- package/build/lib/basedriver/ipc.js.map +1 -1
- package/build/lib/basedriver/validation.d.ts.map +1 -1
- package/build/lib/basedriver/validation.js +3 -2
- package/build/lib/basedriver/validation.js.map +1 -1
- package/build/lib/express/express-logging.d.ts +0 -1
- package/build/lib/express/express-logging.d.ts.map +1 -1
- package/build/lib/express/express-logging.js +9 -8
- package/build/lib/express/express-logging.js.map +1 -1
- package/build/lib/express/idempotency.js.map +1 -1
- package/build/lib/express/middleware.d.ts.map +1 -1
- package/build/lib/express/middleware.js.map +1 -1
- package/build/lib/express/server.d.ts +1 -1
- package/build/lib/express/server.d.ts.map +1 -1
- package/build/lib/express/server.js +19 -20
- package/build/lib/express/server.js.map +1 -1
- package/build/lib/express/websocket.d.ts.map +1 -1
- package/build/lib/express/websocket.js.map +1 -1
- package/build/lib/helpers/capabilities.d.ts.map +1 -1
- package/build/lib/helpers/capabilities.js.map +1 -1
- package/build/lib/helpers/levenshtein-match.d.ts.map +1 -1
- package/build/lib/helpers/levenshtein-match.js +4 -1
- package/build/lib/helpers/levenshtein-match.js.map +1 -1
- package/build/lib/index.d.ts +1 -1
- package/build/lib/index.d.ts.map +1 -1
- package/build/lib/index.js +3 -2
- package/build/lib/index.js.map +1 -1
- package/build/lib/jsonwp-proxy/protocol-converter.d.ts.map +1 -1
- package/build/lib/jsonwp-proxy/protocol-converter.js +14 -7
- package/build/lib/jsonwp-proxy/protocol-converter.js.map +1 -1
- package/build/lib/jsonwp-proxy/proxy.d.ts.map +1 -1
- package/build/lib/jsonwp-proxy/proxy.js +17 -11
- package/build/lib/jsonwp-proxy/proxy.js.map +1 -1
- package/build/lib/protocol/errors.d.ts.map +1 -1
- package/build/lib/protocol/errors.js +13 -13
- package/build/lib/protocol/errors.js.map +1 -1
- package/build/lib/protocol/protocol.d.ts +1 -1
- package/build/lib/protocol/protocol.d.ts.map +1 -1
- package/build/lib/protocol/protocol.js +35 -18
- package/build/lib/protocol/protocol.js.map +1 -1
- package/build/lib/protocol/routes.d.ts.map +1 -1
- package/build/lib/protocol/routes.js +7 -5
- package/build/lib/protocol/routes.js.map +1 -1
- package/build/lib/test-pages/crash.d.ts.map +1 -0
- package/build/lib/test-pages/crash.js.map +1 -0
- package/build/lib/test-pages/env.d.ts +5 -0
- package/build/lib/test-pages/env.d.ts.map +1 -0
- package/build/lib/test-pages/env.js +12 -0
- package/build/lib/test-pages/env.js.map +1 -0
- package/build/lib/{express/static.d.ts → test-pages/handlers.d.ts} +1 -2
- package/build/lib/test-pages/handlers.d.ts.map +1 -0
- package/build/lib/{express/static.js → test-pages/handlers.js} +7 -17
- package/build/lib/test-pages/handlers.js.map +1 -0
- package/build/lib/test-pages/index.d.ts +6 -0
- package/build/lib/test-pages/index.d.ts.map +1 -0
- package/build/lib/test-pages/index.js +35 -0
- package/build/lib/test-pages/index.js.map +1 -0
- package/build/lib/test-pages/static-dir.d.ts +8 -0
- package/build/lib/test-pages/static-dir.d.ts.map +1 -0
- package/build/lib/test-pages/static-dir.js +24 -0
- package/build/lib/test-pages/static-dir.js.map +1 -0
- package/build/lib/test-pages/template.d.ts +3 -0
- package/build/lib/test-pages/template.d.ts.map +1 -0
- package/build/lib/test-pages/template.js +19 -0
- package/build/lib/test-pages/template.js.map +1 -0
- package/build/lib/utils.d.ts +0 -2
- package/build/lib/utils.d.ts.map +1 -1
- package/build/lib/utils.js +0 -16
- package/build/lib/utils.js.map +1 -1
- package/lib/basedriver/capabilities.ts +72 -66
- package/lib/basedriver/commands/bidi.ts +1 -1
- package/lib/basedriver/commands/event.ts +10 -5
- package/lib/basedriver/commands/execute.ts +12 -9
- package/lib/basedriver/commands/find.ts +20 -12
- package/lib/basedriver/commands/log.ts +2 -2
- package/lib/basedriver/commands/timeout.ts +17 -8
- package/lib/basedriver/core.ts +14 -14
- package/lib/basedriver/device-settings.ts +4 -8
- package/lib/basedriver/driver.ts +50 -40
- package/lib/basedriver/extension-core.ts +33 -17
- package/lib/basedriver/helpers.ts +57 -26
- package/lib/basedriver/ipc.ts +37 -18
- package/lib/basedriver/validation.ts +13 -6
- package/lib/express/express-logging.ts +14 -17
- package/lib/express/idempotency.ts +6 -6
- package/lib/express/middleware.ts +10 -12
- package/lib/express/server.ts +53 -61
- package/lib/express/websocket.ts +5 -7
- package/lib/helpers/capabilities.ts +5 -4
- package/lib/helpers/extension-command-name.ts +1 -1
- package/lib/helpers/levenshtein-match.ts +20 -11
- package/lib/index.js +2 -1
- package/lib/jsonwp-proxy/protocol-converter.ts +51 -27
- package/lib/jsonwp-proxy/proxy.ts +42 -42
- package/lib/protocol/errors.ts +47 -67
- package/lib/protocol/protocol.ts +116 -72
- package/lib/protocol/routes.ts +9 -9
- package/lib/test-pages/env.ts +9 -0
- package/lib/{express/static.ts → test-pages/handlers.ts} +7 -27
- package/lib/test-pages/index.ts +34 -0
- package/lib/test-pages/static-dir.ts +19 -0
- package/lib/test-pages/template.ts +17 -0
- package/lib/utils.ts +3 -23
- package/package.json +9 -10
- package/tsconfig.json +1 -0
- package/build/lib/express/crash.d.ts.map +0 -1
- package/build/lib/express/crash.js.map +0 -1
- package/build/lib/express/static.d.ts.map +0 -1
- package/build/lib/express/static.js.map +0 -1
- /package/build/lib/{express → test-pages}/crash.d.ts +0 -0
- /package/build/lib/{express → test-pages}/crash.js +0 -0
- /package/lib/{express → test-pages}/crash.ts +0 -0
- /package/{static → test-fixtures/static}/appium.png +0 -0
- /package/{static → test-fixtures/static}/favicon.ico +0 -0
- /package/{static → test-fixtures/static}/js/jquery.min.js +0 -0
- /package/{static → test-fixtures/static}/test/frameset.html +0 -0
- /package/{static → test-fixtures/static}/test/guinea-pig-app-banner.html +0 -0
- /package/{static → test-fixtures/static}/test/guinea-pig-scrollable.html +0 -0
- /package/{static → test-fixtures/static}/test/guinea-pig.html +0 -0
- /package/{static → test-fixtures/static}/test/guinea-pig2.html +0 -0
- /package/{static → test-fixtures/static}/test/guinea-pig3.html +0 -0
- /package/{static → test-fixtures/static}/test/guinea-pig4.html +0 -0
- /package/{static → test-fixtures/static}/test/guinea-pig5.html +0 -0
- /package/{static → test-fixtures/static}/test/iframes.html +0 -0
- /package/{static → test-fixtures/static}/test/shadow-dom.html +0 -0
- /package/{static → test-fixtures/static}/test/subframe1.html +0 -0
- /package/{static → test-fixtures/static}/test/subframe2.html +0 -0
- /package/{static → test-fixtures/static}/test/subframe3.html +0 -0
- /package/{static → test-fixtures/static}/test/touch.html +0 -0
- /package/{static → test-fixtures/static}/test/welcome.html +0 -0
|
@@ -34,16 +34,17 @@ export function isW3cCaps(caps: unknown): caps is W3CCapabilities<Constraints> {
|
|
|
34
34
|
export function fixCaps<C extends Constraints>(
|
|
35
35
|
oldCaps: Record<string, unknown>,
|
|
36
36
|
desiredCapConstraints: C,
|
|
37
|
-
log: AppiumLogger
|
|
37
|
+
log: AppiumLogger,
|
|
38
38
|
): Capabilities<C> {
|
|
39
39
|
const caps = {...oldCaps} as Record<string, unknown>;
|
|
40
40
|
|
|
41
|
-
const logCastWarning = (prefix: string) =>
|
|
41
|
+
const logCastWarning = (prefix: string) =>
|
|
42
|
+
log.warn(`${prefix}. This may cause unexpected behavior`);
|
|
42
43
|
|
|
43
44
|
// boolean capabilities can be passed in as strings 'false' and 'true'
|
|
44
45
|
// which we want to translate into boolean values
|
|
45
46
|
const booleanCaps = Object.keys(desiredCapConstraints).filter(
|
|
46
|
-
(key) => desiredCapConstraints[key as keyof C]?.isBoolean === true
|
|
47
|
+
(key) => desiredCapConstraints[key as keyof C]?.isBoolean === true,
|
|
47
48
|
);
|
|
48
49
|
for (const cap of booleanCaps) {
|
|
49
50
|
const value = oldCaps[cap];
|
|
@@ -62,7 +63,7 @@ export function fixCaps<C extends Constraints>(
|
|
|
62
63
|
|
|
63
64
|
// int capabilities are often sent in as strings by frameworks
|
|
64
65
|
const intCaps = Object.keys(desiredCapConstraints).filter(
|
|
65
|
-
(key) => desiredCapConstraints[key as keyof C]?.isNumber === true
|
|
66
|
+
(key) => desiredCapConstraints[key as keyof C]?.isNumber === true,
|
|
66
67
|
);
|
|
67
68
|
for (const cap of intCaps) {
|
|
68
69
|
const value = oldCaps[cap];
|
|
@@ -11,7 +11,7 @@ import type {BaseDriver} from '../basedriver/driver';
|
|
|
11
11
|
*/
|
|
12
12
|
export function resolveExecuteExtensionName<C extends Constraints>(
|
|
13
13
|
this: BaseDriver<C>,
|
|
14
|
-
commandName: string
|
|
14
|
+
commandName: string,
|
|
15
15
|
): string {
|
|
16
16
|
const Driver = this.constructor as DriverClass<Driver<C>>;
|
|
17
17
|
const methodMap = Driver.executeMethodMap;
|
|
@@ -29,22 +29,31 @@ export function rankLevenshteinCandidates(
|
|
|
29
29
|
|
|
30
30
|
const matchesMap: StringRecord<string[]> = candidates
|
|
31
31
|
.map((name) => [distance(target, name), name] as const)
|
|
32
|
-
.reduce(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
acc
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
32
|
+
.reduce(
|
|
33
|
+
(acc, [dist, name]) => {
|
|
34
|
+
const key = String(dist);
|
|
35
|
+
if (key in acc) {
|
|
36
|
+
acc[key].push(name);
|
|
37
|
+
} else {
|
|
38
|
+
acc[key] = [name];
|
|
39
|
+
}
|
|
40
|
+
return acc;
|
|
41
|
+
},
|
|
42
|
+
{} as StringRecord<string[]>,
|
|
43
|
+
);
|
|
44
|
+
const sortedDistanceKeys = Object.keys(matchesMap).sort(
|
|
45
|
+
(a, b) => parseInt(a, 10) - parseInt(b, 10),
|
|
46
|
+
);
|
|
42
47
|
const sorted = sortedDistanceKeys.flatMap((k) => (matchesMap[k] ?? []).sort());
|
|
43
48
|
|
|
44
49
|
const best = sorted[0];
|
|
45
50
|
const firstDistanceKey = sortedDistanceKeys[0];
|
|
46
51
|
const minDist = firstDistanceKey !== undefined ? parseInt(firstDistanceKey, 10) : NaN;
|
|
47
|
-
const suggestion =
|
|
52
|
+
const suggestion =
|
|
53
|
+
maxEditDistance >= 0 &&
|
|
54
|
+
best !== undefined &&
|
|
55
|
+
!Number.isNaN(minDist) &&
|
|
56
|
+
minDist <= maxEditDistance
|
|
48
57
|
? best
|
|
49
58
|
: undefined;
|
|
50
59
|
return {sorted, suggestion};
|
package/lib/index.js
CHANGED
|
@@ -14,7 +14,8 @@ export * from './protocol';
|
|
|
14
14
|
export {errorFromMJSONWPStatusCode as errorFromCode} from './protocol';
|
|
15
15
|
|
|
16
16
|
// Express exports
|
|
17
|
-
|
|
17
|
+
/** @deprecated Removed in Appium 4. Use hard-copied test fixtures in driver CI instead. */
|
|
18
|
+
export {TEST_FIXTURES_DIR as STATIC_DIR} from './test-pages';
|
|
18
19
|
export {server, normalizeBasePath} from './express/server';
|
|
19
20
|
|
|
20
21
|
// jsonwp-proxy exports
|
|
@@ -6,7 +6,7 @@ import {MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY, PROTOCOLS} from '../constants';
|
|
|
6
6
|
export type ProxyFunction = (
|
|
7
7
|
url: string,
|
|
8
8
|
method: string,
|
|
9
|
-
body?: HTTPBody
|
|
9
|
+
body?: HTTPBody,
|
|
10
10
|
) => Promise<[ProxyResponse, HTTPBody]>;
|
|
11
11
|
|
|
12
12
|
export const COMMAND_URLS_CONFLICTS = [
|
|
@@ -19,7 +19,8 @@ export const COMMAND_URLS_CONFLICTS = [
|
|
|
19
19
|
},
|
|
20
20
|
{
|
|
21
21
|
commandNames: ['getElementScreenshot'],
|
|
22
|
-
jsonwpConverter: (url: string) =>
|
|
22
|
+
jsonwpConverter: (url: string) =>
|
|
23
|
+
url.replace(/\/element\/([^/]+)\/screenshot$/, '/screenshot/$1'),
|
|
23
24
|
w3cConverter: (url: string) => url.replace(/\/screenshot\/([^/]+)/, '/element/$1/screenshot'),
|
|
24
25
|
},
|
|
25
26
|
{
|
|
@@ -59,7 +60,7 @@ export class ProtocolConverter {
|
|
|
59
60
|
*/
|
|
60
61
|
constructor(
|
|
61
62
|
public proxyFunc: ProxyFunction,
|
|
62
|
-
log: AppiumLogger | null = null
|
|
63
|
+
log: AppiumLogger | null = null,
|
|
63
64
|
) {
|
|
64
65
|
this._log = log;
|
|
65
66
|
}
|
|
@@ -84,7 +85,7 @@ export class ProtocolConverter {
|
|
|
84
85
|
commandName: string,
|
|
85
86
|
url: string,
|
|
86
87
|
method: string,
|
|
87
|
-
body?: HTTPBody
|
|
88
|
+
body?: HTTPBody,
|
|
88
89
|
): Promise<[ProxyResponse, HTTPBody]> {
|
|
89
90
|
if (!this.downstreamProtocol) {
|
|
90
91
|
return await this.proxyFunc(url, method, body);
|
|
@@ -117,12 +118,12 @@ export class ProtocolConverter {
|
|
|
117
118
|
this.downstreamProtocol === MJSONWP ? jsonwpConverter(url) : w3cConverter(url);
|
|
118
119
|
if (rewrittenUrl === url) {
|
|
119
120
|
this.log.debug(
|
|
120
|
-
`Did not know how to rewrite the original URL '${url}' for ${this.downstreamProtocol} protocol
|
|
121
|
+
`Did not know how to rewrite the original URL '${url}' for ${this.downstreamProtocol} protocol`,
|
|
121
122
|
);
|
|
122
123
|
break;
|
|
123
124
|
}
|
|
124
125
|
this.log.info(
|
|
125
|
-
`Rewrote the original URL '${url}' to '${rewrittenUrl}' for ${this.downstreamProtocol} protocol
|
|
126
|
+
`Rewrote the original URL '${url}' to '${rewrittenUrl}' for ${this.downstreamProtocol} protocol`,
|
|
126
127
|
);
|
|
127
128
|
return await this.proxyFunc(rewrittenUrl, method, body);
|
|
128
129
|
}
|
|
@@ -142,7 +143,11 @@ export class ProtocolConverter {
|
|
|
142
143
|
}
|
|
143
144
|
|
|
144
145
|
const bodyObj = (util.safeJsonParse(body) as Record<string, unknown>) ?? {};
|
|
145
|
-
if (
|
|
146
|
+
if (
|
|
147
|
+
this.downstreamProtocol === W3C &&
|
|
148
|
+
Object.hasOwn(bodyObj, 'ms') &&
|
|
149
|
+
Object.hasOwn(bodyObj, 'type')
|
|
150
|
+
) {
|
|
146
151
|
const typeToW3C = (x: string) => (x === 'page load' ? 'pageLoad' : x);
|
|
147
152
|
return [
|
|
148
153
|
{
|
|
@@ -151,15 +156,20 @@ export class ProtocolConverter {
|
|
|
151
156
|
];
|
|
152
157
|
}
|
|
153
158
|
|
|
154
|
-
if (
|
|
159
|
+
if (
|
|
160
|
+
this.downstreamProtocol === MJSONWP &&
|
|
161
|
+
(!Object.hasOwn(bodyObj, 'ms') || !Object.hasOwn(bodyObj, 'type'))
|
|
162
|
+
) {
|
|
155
163
|
const typeToJSONWP = (x: string) => (x === 'pageLoad' ? 'page load' : x);
|
|
156
|
-
return
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
164
|
+
return (
|
|
165
|
+
Object.entries(bodyObj)
|
|
166
|
+
// Only transform the entry if ms value is a valid positive float number
|
|
167
|
+
.filter((pair) => /^\d+(?:[.,]\d*?)?$/.test(`${pair[1]}`))
|
|
168
|
+
.map((pair) => ({
|
|
169
|
+
type: typeToJSONWP(pair[0]),
|
|
170
|
+
ms: pair[1],
|
|
171
|
+
}))
|
|
172
|
+
);
|
|
163
173
|
}
|
|
164
174
|
|
|
165
175
|
return [bodyObj];
|
|
@@ -171,14 +181,14 @@ export class ProtocolConverter {
|
|
|
171
181
|
private async proxySetTimeouts(
|
|
172
182
|
url: string,
|
|
173
183
|
method: string,
|
|
174
|
-
body?: HTTPBody
|
|
184
|
+
body?: HTTPBody,
|
|
175
185
|
): Promise<[ProxyResponse, HTTPBody]> {
|
|
176
186
|
const timeoutRequestObjects = this.getTimeoutRequestObjects(body);
|
|
177
187
|
if (timeoutRequestObjects.length === 0) {
|
|
178
188
|
return await this.proxyFunc(url, method, body);
|
|
179
189
|
}
|
|
180
190
|
this.log.debug(
|
|
181
|
-
`Will send the following request bodies to /timeouts: ${JSON.stringify(timeoutRequestObjects)}
|
|
191
|
+
`Will send the following request bodies to /timeouts: ${JSON.stringify(timeoutRequestObjects)}`,
|
|
182
192
|
);
|
|
183
193
|
|
|
184
194
|
let response!: ProxyResponse;
|
|
@@ -202,12 +212,16 @@ export class ProtocolConverter {
|
|
|
202
212
|
private async proxySetWindow(
|
|
203
213
|
url: string,
|
|
204
214
|
method: string,
|
|
205
|
-
body: HTTPBody
|
|
215
|
+
body: HTTPBody,
|
|
206
216
|
): Promise<[ProxyResponse, HTTPBody]> {
|
|
207
217
|
const bodyObj = util.safeJsonParse(body);
|
|
208
218
|
if (util.isPlainObject(bodyObj)) {
|
|
209
219
|
const obj = bodyObj as Record<string, unknown>;
|
|
210
|
-
if (
|
|
220
|
+
if (
|
|
221
|
+
this.downstreamProtocol === W3C &&
|
|
222
|
+
Object.hasOwn(bodyObj, 'name') &&
|
|
223
|
+
!Object.hasOwn(bodyObj, 'handle')
|
|
224
|
+
) {
|
|
211
225
|
this.log.debug(`Copied 'name' value '${obj.name}' to 'handle' as per W3C spec`);
|
|
212
226
|
return await this.proxyFunc(url, method, {...obj, handle: obj.name});
|
|
213
227
|
}
|
|
@@ -226,10 +240,13 @@ export class ProtocolConverter {
|
|
|
226
240
|
private async proxySetValue(
|
|
227
241
|
url: string,
|
|
228
242
|
method: string,
|
|
229
|
-
body: HTTPBody
|
|
243
|
+
body: HTTPBody,
|
|
230
244
|
): Promise<[ProxyResponse, HTTPBody]> {
|
|
231
245
|
const bodyObj = util.safeJsonParse(body) as Record<string, unknown> | undefined;
|
|
232
|
-
if (
|
|
246
|
+
if (
|
|
247
|
+
util.isPlainObject(bodyObj) &&
|
|
248
|
+
(util.hasValue(bodyObj?.text) || util.hasValue(bodyObj?.value))
|
|
249
|
+
) {
|
|
233
250
|
let {text, value} = bodyObj;
|
|
234
251
|
if (util.hasValue(text) && !util.hasValue(value)) {
|
|
235
252
|
value = typeof text === 'string' ? [...text] : Array.isArray(text) ? text : [];
|
|
@@ -246,13 +263,20 @@ export class ProtocolConverter {
|
|
|
246
263
|
private async proxySetFrame(
|
|
247
264
|
url: string,
|
|
248
265
|
method: string,
|
|
249
|
-
body: HTTPBody
|
|
266
|
+
body: HTTPBody,
|
|
250
267
|
): Promise<[ProxyResponse, HTTPBody]> {
|
|
251
268
|
const bodyObj = util.safeJsonParse(body);
|
|
252
|
-
if (
|
|
269
|
+
if (
|
|
270
|
+
Object.hasOwn(bodyObj ?? {}, 'id') &&
|
|
271
|
+
util.isPlainObject((bodyObj as Record<string, unknown>).id)
|
|
272
|
+
) {
|
|
253
273
|
return await this.proxyFunc(url, method, {
|
|
254
274
|
...(bodyObj as object),
|
|
255
|
-
id: duplicateKeys(
|
|
275
|
+
id: duplicateKeys(
|
|
276
|
+
(bodyObj as Record<string, unknown>).id as object,
|
|
277
|
+
MJSONWP_ELEMENT_KEY,
|
|
278
|
+
W3C_ELEMENT_KEY,
|
|
279
|
+
),
|
|
256
280
|
});
|
|
257
281
|
}
|
|
258
282
|
return await this.proxyFunc(url, method, body);
|
|
@@ -261,14 +285,14 @@ export class ProtocolConverter {
|
|
|
261
285
|
private async proxyPerformActions(
|
|
262
286
|
url: string,
|
|
263
287
|
method: string,
|
|
264
|
-
body: HTTPBody
|
|
288
|
+
body: HTTPBody,
|
|
265
289
|
): Promise<[ProxyResponse, HTTPBody]> {
|
|
266
290
|
const bodyObj = util.safeJsonParse(body);
|
|
267
291
|
if (util.isPlainObject(bodyObj)) {
|
|
268
292
|
return await this.proxyFunc(
|
|
269
293
|
url,
|
|
270
294
|
method,
|
|
271
|
-
duplicateKeys(bodyObj as object, MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY)
|
|
295
|
+
duplicateKeys(bodyObj as object, MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY),
|
|
272
296
|
);
|
|
273
297
|
}
|
|
274
298
|
return await this.proxyFunc(url, method, body);
|
|
@@ -276,7 +300,7 @@ export class ProtocolConverter {
|
|
|
276
300
|
|
|
277
301
|
private async proxyReleaseActions(
|
|
278
302
|
url: string,
|
|
279
|
-
method: string
|
|
303
|
+
method: string,
|
|
280
304
|
): Promise<[ProxyResponse, HTTPBody]> {
|
|
281
305
|
return await this.proxyFunc(url, method);
|
|
282
306
|
}
|
|
@@ -31,7 +31,7 @@ import type {AxiosError, AxiosResponse, RawAxiosRequestConfig} from 'axios';
|
|
|
31
31
|
const DEFAULT_LOG = logger.getLogger('WD Proxy');
|
|
32
32
|
const DEFAULT_REQUEST_TIMEOUT = 240000;
|
|
33
33
|
const COMMAND_WITH_SESSION_ID_MATCHER = pathToRegexMatch(
|
|
34
|
-
'{/*prefix}/session/:sessionId{/*command}'
|
|
34
|
+
'{/*prefix}/session/:sessionId{/*command}',
|
|
35
35
|
);
|
|
36
36
|
|
|
37
37
|
const {MJSONWP, W3C} = PROTOCOLS;
|
|
@@ -71,7 +71,7 @@ export class JWProxy {
|
|
|
71
71
|
constructor(opts: ProxyOptions = {}) {
|
|
72
72
|
const filteredOptsWithoutLog = omit(
|
|
73
73
|
pick(opts as Record<string, unknown>, ALLOWED_OPTS),
|
|
74
|
-
'log'
|
|
74
|
+
'log',
|
|
75
75
|
) as Omit<ProxyOptions, 'log'>;
|
|
76
76
|
const options = {
|
|
77
77
|
scheme: 'http',
|
|
@@ -92,7 +92,14 @@ export class JWProxy {
|
|
|
92
92
|
timeout: number;
|
|
93
93
|
};
|
|
94
94
|
options.scheme = options.scheme.toLowerCase();
|
|
95
|
-
|
|
95
|
+
this.scheme = options.scheme;
|
|
96
|
+
this.server = options.server;
|
|
97
|
+
this.port = options.port;
|
|
98
|
+
this.base = options.base;
|
|
99
|
+
this.reqBasePath = options.reqBasePath;
|
|
100
|
+
this.sessionId = options.sessionId;
|
|
101
|
+
this.timeout = options.timeout;
|
|
102
|
+
this.headers = options.headers;
|
|
96
103
|
|
|
97
104
|
this._activeRequests = [];
|
|
98
105
|
this._downstreamProtocol = null;
|
|
@@ -151,11 +158,8 @@ export class JWProxy {
|
|
|
151
158
|
getUrlForProxy(url: string, method?: HTTPMethod): string {
|
|
152
159
|
const parsedUrl = this._parseUrl(url);
|
|
153
160
|
const normalizedPathname = this._toNormalizedPathname(parsedUrl);
|
|
154
|
-
const commandName = normalizedPathname
|
|
155
|
-
|
|
156
|
-
: '';
|
|
157
|
-
const requiresSessionId =
|
|
158
|
-
!commandName || (commandName && isSessionCommand(commandName));
|
|
161
|
+
const commandName = normalizedPathname ? routeToCommandName(normalizedPathname, method) : '';
|
|
162
|
+
const requiresSessionId = !commandName || (commandName && isSessionCommand(commandName));
|
|
159
163
|
const proxyPrefix = `${this.scheme}://${this.server}:${this.port}${this.base}`;
|
|
160
164
|
let proxySuffix = normalizedPathname ? `/${normalizedPathname.replace(/^\/+/, '')}` : '';
|
|
161
165
|
if (parsedUrl.search) {
|
|
@@ -165,9 +169,7 @@ export class JWProxy {
|
|
|
165
169
|
return `${proxyPrefix}${proxySuffix}`;
|
|
166
170
|
}
|
|
167
171
|
if (!this.sessionId) {
|
|
168
|
-
throw new ReferenceError(
|
|
169
|
-
`Session ID is not set, but saw a URL that requires it (${url})`
|
|
170
|
-
);
|
|
172
|
+
throw new ReferenceError(`Session ID is not set, but saw a URL that requires it (${url})`);
|
|
171
173
|
}
|
|
172
174
|
return `${proxyPrefix}/session/${this.sessionId}${proxySuffix}`;
|
|
173
175
|
}
|
|
@@ -178,7 +180,7 @@ export class JWProxy {
|
|
|
178
180
|
async proxy(
|
|
179
181
|
url: string,
|
|
180
182
|
method: string,
|
|
181
|
-
body: HTTPBody = null
|
|
183
|
+
body: HTTPBody = null,
|
|
182
184
|
): Promise<[ProxyResponse, HTTPBody]> {
|
|
183
185
|
method = method.toUpperCase();
|
|
184
186
|
const newUrl = this.getUrlForProxy(url, method as HTTPMethod);
|
|
@@ -210,11 +212,11 @@ export class JWProxy {
|
|
|
210
212
|
this.log.warn(
|
|
211
213
|
'Invalid body payload (%s): %s',
|
|
212
214
|
(error as Error).message,
|
|
213
|
-
logger.markSensitive(truncateBody(body))
|
|
215
|
+
logger.markSensitive(truncateBody(body)),
|
|
214
216
|
);
|
|
215
217
|
throw new Error(
|
|
216
218
|
'Cannot interpret the request body as valid JSON. Check the server log for more details.',
|
|
217
|
-
{cause: error}
|
|
219
|
+
{cause: error},
|
|
218
220
|
);
|
|
219
221
|
}
|
|
220
222
|
} else {
|
|
@@ -228,7 +230,7 @@ export class JWProxy {
|
|
|
228
230
|
url || '/',
|
|
229
231
|
method,
|
|
230
232
|
newUrl,
|
|
231
|
-
reqOpts.data ? logger.markSensitive(truncateBody(reqOpts.data)) : 'no'
|
|
233
|
+
reqOpts.data ? logger.markSensitive(truncateBody(reqOpts.data)) : 'no',
|
|
232
234
|
);
|
|
233
235
|
|
|
234
236
|
const throwProxyError = (error: unknown): never => {
|
|
@@ -258,8 +260,7 @@ export class JWProxy {
|
|
|
258
260
|
if (status === 200) {
|
|
259
261
|
const value = data.value as Record<string, unknown> | undefined;
|
|
260
262
|
const raw = data.sessionId ?? value?.sessionId;
|
|
261
|
-
this.sessionId =
|
|
262
|
-
typeof raw === 'string' ? raw : raw != null ? String(raw) : null;
|
|
263
|
+
this.sessionId = typeof raw === 'string' ? raw : raw != null ? String(raw) : null;
|
|
263
264
|
}
|
|
264
265
|
this.downstreamProtocol = this.getProtocolFromResBody(data) ?? this.downstreamProtocol;
|
|
265
266
|
this.log.info(`Determined the downstream protocol as '${this.downstreamProtocol}'`);
|
|
@@ -284,18 +285,14 @@ export class JWProxy {
|
|
|
284
285
|
this.log.info(
|
|
285
286
|
util.hasValue(err.response.status)
|
|
286
287
|
? `Got response with status ${err.response.status}: ${error}`
|
|
287
|
-
: `Got response with unknown status: ${error}
|
|
288
|
+
: `Got response with unknown status: ${error}`,
|
|
288
289
|
);
|
|
289
290
|
}
|
|
290
291
|
} else {
|
|
291
292
|
proxyErrorMsg = `Could not proxy command to the remote server. Original error: ${err.message}`;
|
|
292
293
|
this.log.info(err.message);
|
|
293
294
|
}
|
|
294
|
-
throw new errors.ProxyRequestError(
|
|
295
|
-
proxyErrorMsg,
|
|
296
|
-
err.response?.data,
|
|
297
|
-
err.response?.status
|
|
298
|
-
);
|
|
295
|
+
throw new errors.ProxyRequestError(proxyErrorMsg, err.response?.data, err.response?.status);
|
|
299
296
|
}
|
|
300
297
|
}
|
|
301
298
|
|
|
@@ -317,13 +314,11 @@ export class JWProxy {
|
|
|
317
314
|
async proxyCommand(
|
|
318
315
|
url: string,
|
|
319
316
|
method: HTTPMethod,
|
|
320
|
-
body: HTTPBody = null
|
|
317
|
+
body: HTTPBody = null,
|
|
321
318
|
): Promise<[ProxyResponse, HTTPBody]> {
|
|
322
319
|
const parsedUrl = this._parseUrl(url);
|
|
323
320
|
const normalizedPathname = this._toNormalizedPathname(parsedUrl);
|
|
324
|
-
const commandName = normalizedPathname
|
|
325
|
-
? routeToCommandName(normalizedPathname, method)
|
|
326
|
-
: '';
|
|
321
|
+
const commandName = normalizedPathname ? routeToCommandName(normalizedPathname, method) : '';
|
|
327
322
|
if (!commandName) {
|
|
328
323
|
return await this.proxy(url, method, body);
|
|
329
324
|
}
|
|
@@ -335,11 +330,7 @@ export class JWProxy {
|
|
|
335
330
|
/**
|
|
336
331
|
* Executes a WebDriver command and returns the unwrapped `value` field (or throws).
|
|
337
332
|
*/
|
|
338
|
-
async command(
|
|
339
|
-
url: string,
|
|
340
|
-
method: HTTPMethod,
|
|
341
|
-
body: HTTPBody = null
|
|
342
|
-
): Promise<HTTPBody> {
|
|
333
|
+
async command(url: string, method: HTTPMethod, body: HTTPBody = null): Promise<HTTPBody> {
|
|
343
334
|
let response: ProxyResponse;
|
|
344
335
|
let resBodyObj: HTTPBody;
|
|
345
336
|
try {
|
|
@@ -362,9 +353,12 @@ export class JWProxy {
|
|
|
362
353
|
if (util.isPlainObject(message) && Object.hasOwn(message, 'message')) {
|
|
363
354
|
message = (message as Record<string, unknown>).message;
|
|
364
355
|
}
|
|
365
|
-
throw errorFromMJSONWPStatusCode(
|
|
366
|
-
|
|
367
|
-
|
|
356
|
+
throw errorFromMJSONWPStatusCode(
|
|
357
|
+
status,
|
|
358
|
+
util.isEmpty(message)
|
|
359
|
+
? getSummaryByCode(status)
|
|
360
|
+
: (message as string | {message: string}),
|
|
361
|
+
);
|
|
368
362
|
}
|
|
369
363
|
} else if (protocol === W3C) {
|
|
370
364
|
if (response.statusCode < 300) {
|
|
@@ -375,7 +369,7 @@ export class JWProxy {
|
|
|
375
369
|
throw errorFromW3CJsonCode(
|
|
376
370
|
value.error as string,
|
|
377
371
|
(value.message as string) ?? '',
|
|
378
|
-
value.stacktrace as string | undefined
|
|
372
|
+
value.stacktrace as string | undefined,
|
|
379
373
|
);
|
|
380
374
|
}
|
|
381
375
|
} else if (response.statusCode === 200) {
|
|
@@ -385,7 +379,7 @@ export class JWProxy {
|
|
|
385
379
|
`Did not know what to do with response code '${response.statusCode}' ` +
|
|
386
380
|
`and response body '${util.truncateString(JSON.stringify(resBodyObj), {
|
|
387
381
|
length: 300,
|
|
388
|
-
})}'
|
|
382
|
+
})}'`,
|
|
389
383
|
);
|
|
390
384
|
}
|
|
391
385
|
|
|
@@ -410,20 +404,22 @@ export class JWProxy {
|
|
|
410
404
|
const [response, body] = await this.proxyCommand(
|
|
411
405
|
req.originalUrl,
|
|
412
406
|
req.method as HTTPMethod,
|
|
413
|
-
req.body
|
|
407
|
+
req.body,
|
|
414
408
|
);
|
|
415
409
|
statusCode = response.statusCode;
|
|
416
410
|
resBodyObj = body;
|
|
417
411
|
} catch (err: unknown) {
|
|
418
412
|
[statusCode, resBodyObj] = getResponseForW3CError(
|
|
419
|
-
isErrorType(err, errors.ProxyRequestError)
|
|
413
|
+
isErrorType(err, errors.ProxyRequestError)
|
|
414
|
+
? (err as InstanceType<typeof errors.ProxyRequestError>).getActualError()
|
|
415
|
+
: err,
|
|
420
416
|
);
|
|
421
417
|
}
|
|
422
418
|
res.setHeader('content-type', 'application/json; charset=utf-8');
|
|
423
419
|
if (!util.isPlainObject(resBodyObj)) {
|
|
424
420
|
const error = new errors.UnknownError(
|
|
425
421
|
`The downstream server response with the status code ${statusCode} is not a valid JSON object: ` +
|
|
426
|
-
util.truncateString(`${resBodyObj}`, {length: 300})
|
|
422
|
+
util.truncateString(`${resBodyObj}`, {length: 300}),
|
|
427
423
|
);
|
|
428
424
|
[statusCode, resBodyObj] = getResponseForW3CError(error);
|
|
429
425
|
}
|
|
@@ -487,10 +483,14 @@ export class JWProxy {
|
|
|
487
483
|
// This is needed for the backward compatibility
|
|
488
484
|
// if drivers don't set reqBasePath properly
|
|
489
485
|
if (!this.reqBasePath) {
|
|
490
|
-
if (
|
|
486
|
+
if (
|
|
487
|
+
match &&
|
|
488
|
+
match.params &&
|
|
489
|
+
Array.isArray((match.params as Record<string, unknown>).prefix)
|
|
490
|
+
) {
|
|
491
491
|
pathname = pathname.replace(
|
|
492
492
|
`/${((match.params as Record<string, unknown>).prefix as string[]).join('/')}`,
|
|
493
|
-
''
|
|
493
|
+
'',
|
|
494
494
|
);
|
|
495
495
|
} else if (pathname.startsWith('/wd/hub')) {
|
|
496
496
|
pathname = pathname.replace('/wd/hub', '');
|