webdriver 9.14.0 → 9.16.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/bidi/utils.d.ts +2 -0
- package/build/bidi/utils.d.ts.map +1 -0
- package/build/command.d.ts +1 -1
- package/build/command.d.ts.map +1 -1
- package/build/constants.d.ts +5 -0
- package/build/constants.d.ts.map +1 -1
- package/build/index.js +292 -205
- package/build/node.js +323 -211
- package/build/request/node.d.ts +4 -0
- package/build/request/node.d.ts.map +1 -1
- package/build/request/types.d.ts +1 -0
- package/build/request/types.d.ts.map +1 -1
- package/build/types.d.ts +9 -0
- package/build/types.d.ts.map +1 -1
- package/build/utils.d.ts +23 -1
- package/build/utils.d.ts.map +1 -1
- package/package.json +7 -7
package/build/node.js
CHANGED
|
@@ -8,9 +8,9 @@ import { webdriverMonad, sessionEnvironmentDetector, startWebDriver, isBidi } fr
|
|
|
8
8
|
import { validateConfig } from "@wdio/config";
|
|
9
9
|
|
|
10
10
|
// src/command.ts
|
|
11
|
-
import
|
|
12
|
-
import { commandCallStructure, isValidParameter, getArgumentType } from "@wdio/utils";
|
|
13
|
-
import { WebDriverBidiProtocol } from "@wdio/protocols";
|
|
11
|
+
import logger3 from "@wdio/logger";
|
|
12
|
+
import { commandCallStructure, isValidParameter, getArgumentType, transformCommandLogResult } from "@wdio/utils";
|
|
13
|
+
import { WebDriverBidiProtocol as WebDriverBidiProtocol2 } from "@wdio/protocols";
|
|
14
14
|
|
|
15
15
|
// src/environment.ts
|
|
16
16
|
var isNode = !!(typeof process !== "undefined" && process.version);
|
|
@@ -31,169 +31,32 @@ var environment = {
|
|
|
31
31
|
}
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
-
// src/
|
|
35
|
-
var
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
let endpoint = endpointUri;
|
|
43
|
-
const commandParams = [...variables.map((v) => Object.assign(v, {
|
|
44
|
-
/**
|
|
45
|
-
* url variables are:
|
|
46
|
-
*/
|
|
47
|
-
required: true,
|
|
48
|
-
// always required as they are part of the endpoint
|
|
49
|
-
type: "string"
|
|
50
|
-
// have to be always type of string
|
|
51
|
-
})), ...parameters];
|
|
52
|
-
const commandUsage = `${command}(${commandParams.map((p) => p.name).join(", ")})`;
|
|
53
|
-
const moreInfo = `
|
|
34
|
+
// src/types.ts
|
|
35
|
+
var CommandRuntimeOptions = class {
|
|
36
|
+
// mask the text parameter value of the command
|
|
37
|
+
mask;
|
|
38
|
+
constructor(options) {
|
|
39
|
+
this.mask = options.mask;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
54
42
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (args.length < minAllowedParams || args.length > commandParams.length) {
|
|
70
|
-
const parameterDescription = commandParams.length ? `
|
|
43
|
+
// src/utils.ts
|
|
44
|
+
import { deepmergeCustom } from "deepmerge-ts";
|
|
45
|
+
import logger2, { SENSITIVE_DATA_REPLACER } from "@wdio/logger";
|
|
46
|
+
import {
|
|
47
|
+
WebDriverProtocol,
|
|
48
|
+
MJsonWProtocol,
|
|
49
|
+
AppiumProtocol,
|
|
50
|
+
ChromiumProtocol,
|
|
51
|
+
SauceLabsProtocol,
|
|
52
|
+
SeleniumProtocol,
|
|
53
|
+
GeckoProtocol,
|
|
54
|
+
WebDriverBidiProtocol
|
|
55
|
+
} from "@wdio/protocols";
|
|
56
|
+
import { CAPABILITY_KEYS } from "@wdio/protocols";
|
|
71
57
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
throw new Error(
|
|
75
|
-
`Wrong parameters applied for ${command}
|
|
76
|
-
Usage: ${commandUsage}` + parameterDescription + moreInfo
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
for (const [it, arg] of Object.entries(args)) {
|
|
80
|
-
if (isBidiCommand) {
|
|
81
|
-
break;
|
|
82
|
-
}
|
|
83
|
-
const i = parseInt(it, 10);
|
|
84
|
-
const commandParam = commandParams[i];
|
|
85
|
-
if (!isValidParameter(arg, commandParam.type)) {
|
|
86
|
-
if (typeof arg === "undefined" && !commandParam.required) {
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
const actual = commandParam.type.endsWith("[]") ? `(${(Array.isArray(arg) ? arg : [arg]).map((a) => getArgumentType(a))})[]` : getArgumentType(arg);
|
|
90
|
-
throw new Error(
|
|
91
|
-
`Malformed type for "${commandParam.name}" parameter of command ${command}
|
|
92
|
-
Expected: ${commandParam.type}
|
|
93
|
-
Actual: ${actual}` + moreInfo
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
if (i < variables.length) {
|
|
97
|
-
const encodedArg = doubleEncodeVariables ? encodeURIComponent(encodeURIComponent(arg)) : encodeURIComponent(arg);
|
|
98
|
-
endpoint = endpoint.replace(`:${commandParams[i].name}`, encodedArg);
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
body[commandParams[i].name] = arg;
|
|
102
|
-
}
|
|
103
|
-
const { isAborted, abortSignal, cleanup } = manageSessionAbortions.call(this);
|
|
104
|
-
const requiresSession = endpointUri.includes("/:sessionId/");
|
|
105
|
-
if (isAborted && command !== "deleteSession" && requiresSession) {
|
|
106
|
-
throw new Error(`Trying to run command "${commandCallStructure(command, args)}" after session has been deleted, aborting request without executing it`);
|
|
107
|
-
}
|
|
108
|
-
const request = new environment.value.Request(method, endpoint, body, abortSignal, isHubCommand, {
|
|
109
|
-
onPerformance: (data) => this.emit("request.performance", data),
|
|
110
|
-
onRequest: (data) => this.emit("request.start", data),
|
|
111
|
-
onResponse: (data) => this.emit("request.end", data),
|
|
112
|
-
onRetry: (data) => this.emit("request.retry", data)
|
|
113
|
-
});
|
|
114
|
-
this.emit("command", { command, method, endpoint, body });
|
|
115
|
-
log.info("COMMAND", commandCallStructure(command, args));
|
|
116
|
-
return request.makeRequest(this.options, this.sessionId).then((result) => {
|
|
117
|
-
if (typeof result.value !== "undefined") {
|
|
118
|
-
let resultLog = result.value;
|
|
119
|
-
if (/screenshot|recording/i.test(command) && typeof result.value === "string" && result.value.length > 64) {
|
|
120
|
-
resultLog = `${result.value.slice(0, 61)}...`;
|
|
121
|
-
} else if (command === "executeScript" && typeof body.script === "string" && body.script.includes("(() => window.__wdioEvents__)")) {
|
|
122
|
-
resultLog = `[${result.value.length} framework events captured]`;
|
|
123
|
-
}
|
|
124
|
-
log.info("RESULT", resultLog);
|
|
125
|
-
}
|
|
126
|
-
this.emit("result", { command, method, endpoint, body, result });
|
|
127
|
-
if (command === "deleteSession") {
|
|
128
|
-
const browser = this;
|
|
129
|
-
browser._bidiHandler?.close();
|
|
130
|
-
const shutdownDriver = body.deleteSessionOpts?.shutdownDriver !== false;
|
|
131
|
-
if (shutdownDriver && "wdio:driverPID" in this.capabilities && this.capabilities["wdio:driverPID"]) {
|
|
132
|
-
log.info(`Kill driver process with PID ${this.capabilities["wdio:driverPID"]}`);
|
|
133
|
-
try {
|
|
134
|
-
const killedSuccessfully = process.kill(this.capabilities["wdio:driverPID"], "SIGKILL");
|
|
135
|
-
if (!killedSuccessfully) {
|
|
136
|
-
log.warn("Failed to kill driver process, manually clean-up might be required");
|
|
137
|
-
}
|
|
138
|
-
} catch (err) {
|
|
139
|
-
log.warn("Failed to kill driver process", err);
|
|
140
|
-
}
|
|
141
|
-
setTimeout(() => {
|
|
142
|
-
for (const handle of process._getActiveHandles()) {
|
|
143
|
-
if (handle.servername && handle.servername.includes("edgedl.me")) {
|
|
144
|
-
handle.destroy();
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}, 10);
|
|
148
|
-
}
|
|
149
|
-
if (!process.env.WDIO_WORKER_ID) {
|
|
150
|
-
logger.clearLogger();
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
return result.value;
|
|
154
|
-
}).catch((error) => {
|
|
155
|
-
this.emit("result", { command, method, endpoint, body, result: { error } });
|
|
156
|
-
throw error;
|
|
157
|
-
}).finally(() => {
|
|
158
|
-
cleanup();
|
|
159
|
-
});
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
function manageSessionAbortions() {
|
|
163
|
-
const abort = new AbortController();
|
|
164
|
-
const abortOnSessionEnd = (result) => {
|
|
165
|
-
if (result.command !== "deleteSession") {
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
const abortListeners = sessionAbortListeners.get(this.sessionId);
|
|
169
|
-
if (abortListeners) {
|
|
170
|
-
for (const abortListener of abortListeners) {
|
|
171
|
-
abortListener.abort();
|
|
172
|
-
}
|
|
173
|
-
abortListeners.clear();
|
|
174
|
-
sessionAbortListeners.set(this.sessionId, null);
|
|
175
|
-
}
|
|
176
|
-
};
|
|
177
|
-
let abortListenerForCurrentSession = sessionAbortListeners.get(this.sessionId);
|
|
178
|
-
if (typeof abortListenerForCurrentSession === "undefined") {
|
|
179
|
-
abortListenerForCurrentSession = /* @__PURE__ */ new Set();
|
|
180
|
-
sessionAbortListeners.set(this.sessionId, abortListenerForCurrentSession);
|
|
181
|
-
this.on("result", abortOnSessionEnd);
|
|
182
|
-
}
|
|
183
|
-
if (abortListenerForCurrentSession === null) {
|
|
184
|
-
return { isAborted: true, abortSignal: void 0, cleanup: () => {
|
|
185
|
-
} };
|
|
186
|
-
}
|
|
187
|
-
abortListenerForCurrentSession.add(abort);
|
|
188
|
-
return {
|
|
189
|
-
isAborted: false,
|
|
190
|
-
abortSignal: abort.signal,
|
|
191
|
-
cleanup: () => {
|
|
192
|
-
this.off("result", abortOnSessionEnd);
|
|
193
|
-
abortListenerForCurrentSession?.delete(abort);
|
|
194
|
-
}
|
|
195
|
-
};
|
|
196
|
-
}
|
|
58
|
+
// src/bidi/core.ts
|
|
59
|
+
import logger from "@wdio/logger";
|
|
197
60
|
|
|
198
61
|
// src/constants.ts
|
|
199
62
|
var DEFAULTS = {
|
|
@@ -333,32 +196,51 @@ var DEFAULTS = {
|
|
|
333
196
|
cacheDir: {
|
|
334
197
|
type: "string",
|
|
335
198
|
default: environment.value.variables.WEBDRIVER_CACHE_DIR
|
|
199
|
+
},
|
|
200
|
+
/**
|
|
201
|
+
* Mask sensitive data in logs by replacing matching string or all captured groups for the provided regular expressions as string
|
|
202
|
+
*/
|
|
203
|
+
maskingPatterns: {
|
|
204
|
+
type: "string",
|
|
205
|
+
default: void 0
|
|
336
206
|
}
|
|
337
207
|
};
|
|
338
208
|
var ELEMENT_KEY = "element-6066-11e4-a52e-4f735466cecf";
|
|
339
209
|
var SHADOW_ELEMENT_KEY = "shadow-6066-11e4-a52e-4f735466cecf";
|
|
210
|
+
var BASE_64_REGEX = /^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$/;
|
|
211
|
+
var BASE_64_SAFE_STRING_TO_PROCESS_LENGTH = 2e5;
|
|
212
|
+
var APPIUM_MASKING_HEADER = { "x-appium-is-sensitive": "true" };
|
|
340
213
|
|
|
341
|
-
// src/utils.ts
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
214
|
+
// src/bidi/utils.ts
|
|
215
|
+
function isBase64Safe(str) {
|
|
216
|
+
if (typeof str !== "string") {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
if (str.length === 0) {
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
if (str.length % 4 !== 0) {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
const length = str.length;
|
|
226
|
+
const digitCount = length.toString().length;
|
|
227
|
+
if (length > BASE_64_SAFE_STRING_TO_PROCESS_LENGTH) {
|
|
228
|
+
const chunkSize = Math.floor(length / digitCount / 4) * 4;
|
|
229
|
+
for (let i = 0; i < length; i += chunkSize) {
|
|
230
|
+
const chunk = str.slice(i, i + chunkSize);
|
|
231
|
+
if (!BASE_64_REGEX.test(chunk)) {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
return BASE_64_REGEX.test(str);
|
|
238
|
+
}
|
|
355
239
|
|
|
356
240
|
// src/bidi/core.ts
|
|
357
|
-
import logger2 from "@wdio/logger";
|
|
358
241
|
var SCRIPT_PREFIX = "/* __wdio script__ */";
|
|
359
242
|
var SCRIPT_SUFFIX = "/* __wdio script end__ */";
|
|
360
|
-
var
|
|
361
|
-
var log2 = logger2("webdriver");
|
|
243
|
+
var log = logger("webdriver");
|
|
362
244
|
var RESPONSE_TIMEOUT = 1e3 * 60;
|
|
363
245
|
var BidiCore = class {
|
|
364
246
|
#id = 0;
|
|
@@ -391,7 +273,7 @@ var BidiCore = class {
|
|
|
391
273
|
this.client = client;
|
|
392
274
|
}
|
|
393
275
|
async connect() {
|
|
394
|
-
|
|
276
|
+
log.info(`Connecting to webSocketUrl ${this.#webSocketUrl}`);
|
|
395
277
|
this.#ws = await environment.value.createBidiConnection(this.#webSocketUrl, this.#clientOptions);
|
|
396
278
|
this._isConnected = Boolean(this.#ws);
|
|
397
279
|
this.#resolveWaitForConnected(this._isConnected);
|
|
@@ -404,7 +286,7 @@ var BidiCore = class {
|
|
|
404
286
|
if (!this._isConnected) {
|
|
405
287
|
return;
|
|
406
288
|
}
|
|
407
|
-
|
|
289
|
+
log.info(`Close Bidi connection to ${this.#webSocketUrl}`);
|
|
408
290
|
this._isConnected = false;
|
|
409
291
|
if (this.#ws) {
|
|
410
292
|
this.#ws.off("message", this.#handleResponse.bind(this));
|
|
@@ -414,7 +296,7 @@ var BidiCore = class {
|
|
|
414
296
|
}
|
|
415
297
|
}
|
|
416
298
|
reconnect(webSocketUrl, opts) {
|
|
417
|
-
|
|
299
|
+
log.info(`Reconnect to new Bidi session at ${webSocketUrl}`);
|
|
418
300
|
this.close();
|
|
419
301
|
this.#webSocketUrl = webSocketUrl;
|
|
420
302
|
this.#clientOptions = opts;
|
|
@@ -447,24 +329,24 @@ var BidiCore = class {
|
|
|
447
329
|
return;
|
|
448
330
|
}
|
|
449
331
|
let resultLog = data.toString();
|
|
450
|
-
if (typeof payload.result === "object" && payload.result && "data" in payload.result && typeof payload.result.data === "string" &&
|
|
332
|
+
if (typeof payload.result === "object" && payload.result && "data" in payload.result && typeof payload.result.data === "string" && isBase64Safe(payload.result.data)) {
|
|
451
333
|
resultLog = JSON.stringify({
|
|
452
334
|
...payload.result,
|
|
453
335
|
data: `Base64 string [${payload.result.data.length} chars]`
|
|
454
336
|
});
|
|
455
337
|
}
|
|
456
|
-
|
|
338
|
+
log.info("BIDI RESULT", resultLog);
|
|
457
339
|
this.client?.emit("bidiResult", payload);
|
|
458
340
|
const resolve = this.#pendingCommands.get(payload.id);
|
|
459
341
|
if (!resolve) {
|
|
460
|
-
|
|
342
|
+
log.error(`Couldn't resolve command with id ${payload.id}`);
|
|
461
343
|
return;
|
|
462
344
|
}
|
|
463
345
|
this.#pendingCommands.delete(payload.id);
|
|
464
346
|
resolve(payload);
|
|
465
347
|
} catch (err) {
|
|
466
348
|
const error = err instanceof Error ? err : new Error(`Failed parse message: ${String(err)}`);
|
|
467
|
-
|
|
349
|
+
log.error(`Failed parse message: ${error.message}`);
|
|
468
350
|
}
|
|
469
351
|
}
|
|
470
352
|
async send(params) {
|
|
@@ -498,7 +380,7 @@ ${driverStack}`;
|
|
|
498
380
|
if (!this.#ws || !this._isConnected) {
|
|
499
381
|
throw new Error("No connection to WebDriver Bidi was established");
|
|
500
382
|
}
|
|
501
|
-
|
|
383
|
+
log.info("BIDI COMMAND", ...parseBidiCommand(params));
|
|
502
384
|
const id = ++this.#id;
|
|
503
385
|
this.client?.emit("bidiCommand", params);
|
|
504
386
|
this.#ws.send(JSON.stringify({ id, ...params }));
|
|
@@ -1140,7 +1022,7 @@ var BidiHandler = class extends BidiCore {
|
|
|
1140
1022
|
};
|
|
1141
1023
|
|
|
1142
1024
|
// src/utils.ts
|
|
1143
|
-
var
|
|
1025
|
+
var log2 = logger2("webdriver");
|
|
1144
1026
|
var deepmerge = deepmergeCustom({ mergeArrays: false });
|
|
1145
1027
|
var BROWSER_DRIVER_ERRORS = [
|
|
1146
1028
|
"unknown command: wd/hub/session",
|
|
@@ -1176,7 +1058,7 @@ async function startWebDriverSession(params) {
|
|
|
1176
1058
|
try {
|
|
1177
1059
|
response = await sessionRequest.makeRequest(params);
|
|
1178
1060
|
} catch (err) {
|
|
1179
|
-
|
|
1061
|
+
log2.error(err);
|
|
1180
1062
|
const message = getSessionError(err, params);
|
|
1181
1063
|
throw new Error(message);
|
|
1182
1064
|
}
|
|
@@ -1203,7 +1085,7 @@ function validateCapabilities(capabilities) {
|
|
|
1203
1085
|
}
|
|
1204
1086
|
function isSuccessfulResponse(statusCode, body) {
|
|
1205
1087
|
if (!body || typeof body !== "object" || !("value" in body) || typeof body.value === "undefined") {
|
|
1206
|
-
|
|
1088
|
+
log2.debug("request failed due to missing body");
|
|
1207
1089
|
return false;
|
|
1208
1090
|
}
|
|
1209
1091
|
if ("status" in body && body.status === 7 && body.value && typeof body.value === "object" && "message" in body.value && body.value.message && typeof body.value.message === "string" && (body.value.message.toLowerCase().startsWith("no such element") || // Appium
|
|
@@ -1212,7 +1094,7 @@ function isSuccessfulResponse(statusCode, body) {
|
|
|
1212
1094
|
return true;
|
|
1213
1095
|
}
|
|
1214
1096
|
if ("status" in body && body.status && body.status !== 0) {
|
|
1215
|
-
|
|
1097
|
+
log2.debug(`request failed due to status ${body.status}`);
|
|
1216
1098
|
return false;
|
|
1217
1099
|
}
|
|
1218
1100
|
const hasErrorResponse = body.value && (typeof body.value === "object" && "error" in body.value && body.value.error || typeof body.value === "object" && "stackTrace" in body.value && body.value.stackTrace || typeof body.value === "object" && "stacktrace" in body.value && body.value.stacktrace);
|
|
@@ -1224,7 +1106,7 @@ function isSuccessfulResponse(statusCode, body) {
|
|
|
1224
1106
|
}
|
|
1225
1107
|
if (hasErrorResponse) {
|
|
1226
1108
|
const errMsg = typeof body.value === "object" && body.value && "error" in body.value ? body.value.error : body.value;
|
|
1227
|
-
|
|
1109
|
+
log2.debug("request failed due to response error:", errMsg);
|
|
1228
1110
|
return false;
|
|
1229
1111
|
}
|
|
1230
1112
|
return true;
|
|
@@ -1241,7 +1123,7 @@ function getPrototype({ isW3C, isChromium, isFirefox, isMobile, isSauce, isSelen
|
|
|
1241
1123
|
/**
|
|
1242
1124
|
* enable Bidi protocol for W3C sessions
|
|
1243
1125
|
*/
|
|
1244
|
-
isW3C ?
|
|
1126
|
+
isW3C ? WebDriverBidiProtocol : {},
|
|
1245
1127
|
/**
|
|
1246
1128
|
* only apply mobile protocol if session is actually for mobile
|
|
1247
1129
|
*/
|
|
@@ -1304,7 +1186,7 @@ function setupDirectConnect(client) {
|
|
|
1304
1186
|
const directConnectPath = capabilities["appium:directConnectPath"];
|
|
1305
1187
|
const directConnectPort = capabilities["appium:directConnectPort"];
|
|
1306
1188
|
if (directConnectProtocol && directConnectHost && directConnectPort && (directConnectPath || directConnectPath === "")) {
|
|
1307
|
-
|
|
1189
|
+
log2.info(`Found direct connect information in new session response. Will connect to server at ${directConnectProtocol}://${directConnectHost}:${directConnectPort}${directConnectPath}`);
|
|
1308
1190
|
client.options.protocol = directConnectProtocol;
|
|
1309
1191
|
client.options.hostname = directConnectHost;
|
|
1310
1192
|
client.options.port = directConnectPort;
|
|
@@ -1346,7 +1228,7 @@ It seems like the service failed to start or is rejecting any connections.`;
|
|
|
1346
1228
|
function initiateBidi(socketUrl, strictSSL = true, userHeaders) {
|
|
1347
1229
|
const isUnitTesting = process.env.WDIO_UNIT_TESTS;
|
|
1348
1230
|
if (isUnitTesting) {
|
|
1349
|
-
|
|
1231
|
+
log2.info("Skip connecting to WebDriver Bidi interface due to unit tests");
|
|
1350
1232
|
return {
|
|
1351
1233
|
_bidiHandler: {
|
|
1352
1234
|
value: {
|
|
@@ -1365,10 +1247,10 @@ function initiateBidi(socketUrl, strictSSL = true, userHeaders) {
|
|
|
1365
1247
|
bidiReqOpts.headers = userHeaders;
|
|
1366
1248
|
}
|
|
1367
1249
|
const handler = new BidiHandler(socketUrl, bidiReqOpts);
|
|
1368
|
-
handler.connect().then((isConnected) => isConnected &&
|
|
1250
|
+
handler.connect().then((isConnected) => isConnected && log2.info(`Connected to WebDriver Bidi interface at ${socketUrl}`));
|
|
1369
1251
|
return {
|
|
1370
1252
|
_bidiHandler: { value: handler },
|
|
1371
|
-
...Object.values(
|
|
1253
|
+
...Object.values(WebDriverBidiProtocol).map((def) => def.socket).reduce((acc, cur) => {
|
|
1372
1254
|
acc[cur.command] = {
|
|
1373
1255
|
value: function(...args) {
|
|
1374
1256
|
const bidiFn = handler[cur.command];
|
|
@@ -1389,8 +1271,208 @@ function parseBidiMessage(data) {
|
|
|
1389
1271
|
}
|
|
1390
1272
|
this.emit(payload.method, payload.params);
|
|
1391
1273
|
} catch (err) {
|
|
1392
|
-
|
|
1274
|
+
log2.error(`Failed parse WebDriver Bidi message: ${err.message}`);
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
function mask(commandInfo, options, body, args) {
|
|
1278
|
+
const unmaskedResult = { maskedBody: body, maskedArgs: args, isMasked: false };
|
|
1279
|
+
if (!options.mask) {
|
|
1280
|
+
return unmaskedResult;
|
|
1281
|
+
}
|
|
1282
|
+
const textValueParamIndex = commandInfo.parameters.findIndex((param) => param.name === "text");
|
|
1283
|
+
if (textValueParamIndex === -1) {
|
|
1284
|
+
return unmaskedResult;
|
|
1285
|
+
}
|
|
1286
|
+
const textValueIndexInArgs = (commandInfo.variables?.length ?? 0) + textValueParamIndex;
|
|
1287
|
+
const text = args[textValueIndexInArgs];
|
|
1288
|
+
if (typeof text !== "string" || !text) {
|
|
1289
|
+
return unmaskedResult;
|
|
1290
|
+
}
|
|
1291
|
+
const maskedBody = {
|
|
1292
|
+
...body,
|
|
1293
|
+
text: SENSITIVE_DATA_REPLACER
|
|
1294
|
+
};
|
|
1295
|
+
const textValueArgsIndex = textValueParamIndex + (commandInfo.variables?.length ?? 0);
|
|
1296
|
+
const maskedArgs = args.slice(0, textValueArgsIndex).concat(SENSITIVE_DATA_REPLACER).concat(args.slice(textValueArgsIndex + 1));
|
|
1297
|
+
return {
|
|
1298
|
+
maskedBody,
|
|
1299
|
+
maskedArgs,
|
|
1300
|
+
isMasked: true
|
|
1301
|
+
};
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
// src/command.ts
|
|
1305
|
+
var log3 = logger3("webdriver");
|
|
1306
|
+
var BIDI_COMMANDS = Object.values(WebDriverBidiProtocol2).map((def) => def.socket.command);
|
|
1307
|
+
var sessionAbortListeners = /* @__PURE__ */ new Map();
|
|
1308
|
+
function command_default(method, endpointUri, commandInfo, doubleEncodeVariables = false) {
|
|
1309
|
+
const { command, deprecated, ref, parameters, variables = [], isHubCommand = false } = commandInfo;
|
|
1310
|
+
return async function protocolCommand(...unmaskedArgs) {
|
|
1311
|
+
let runtimeOptions = {};
|
|
1312
|
+
if (unmaskedArgs.length > 0 && unmaskedArgs[unmaskedArgs.length - 1] instanceof CommandRuntimeOptions) {
|
|
1313
|
+
runtimeOptions = unmaskedArgs.pop();
|
|
1314
|
+
}
|
|
1315
|
+
const isBidiCommand = BIDI_COMMANDS.includes(command);
|
|
1316
|
+
let endpoint = endpointUri;
|
|
1317
|
+
const commandParams = [...variables.map((v) => Object.assign(v, {
|
|
1318
|
+
/**
|
|
1319
|
+
* url variables are:
|
|
1320
|
+
*/
|
|
1321
|
+
required: true,
|
|
1322
|
+
// always required as they are part of the endpoint
|
|
1323
|
+
type: "string"
|
|
1324
|
+
// have to be always type of string
|
|
1325
|
+
})), ...parameters];
|
|
1326
|
+
const commandUsage = `${command}(${commandParams.map((p) => p.name).join(", ")})`;
|
|
1327
|
+
const moreInfo = `
|
|
1328
|
+
|
|
1329
|
+
For more info see ${ref}
|
|
1330
|
+
`;
|
|
1331
|
+
if (typeof deprecated === "string" && !process.env.DISABLE_WEBDRIVERIO_DEPRECATION_WARNINGS) {
|
|
1332
|
+
const warning = deprecated.replace("This command", `The "${command}" command`);
|
|
1333
|
+
log3.warn(warning);
|
|
1334
|
+
console.warn(`\u26A0\uFE0F [WEBDRIVERIO DEPRECATION NOTICE] ${warning}`);
|
|
1335
|
+
}
|
|
1336
|
+
if (isBidiCommand) {
|
|
1337
|
+
throw new Error(
|
|
1338
|
+
`Failed to execute WebDriver Bidi command "${command}" as no Bidi session was established. Make sure you enable it by setting "webSocketUrl: true" in your capabilities and verify that your environment and browser supports it.`
|
|
1339
|
+
);
|
|
1340
|
+
}
|
|
1341
|
+
const minAllowedParams = commandParams.filter((param) => param.required).length;
|
|
1342
|
+
if (unmaskedArgs.length < minAllowedParams || unmaskedArgs.length > commandParams.length) {
|
|
1343
|
+
const parameterDescription = commandParams.length ? `
|
|
1344
|
+
|
|
1345
|
+
Property Description:
|
|
1346
|
+
${commandParams.map((p) => ` "${p.name}" (${p.type}): ${p.description}`).join("\n")}` : "";
|
|
1347
|
+
throw new Error(
|
|
1348
|
+
`Wrong parameters applied for ${command}
|
|
1349
|
+
Usage: ${commandUsage}` + parameterDescription + moreInfo
|
|
1350
|
+
);
|
|
1351
|
+
}
|
|
1352
|
+
const unmaskedBody = {};
|
|
1353
|
+
for (const [it, arg] of Object.entries(unmaskedArgs)) {
|
|
1354
|
+
if (isBidiCommand) {
|
|
1355
|
+
break;
|
|
1356
|
+
}
|
|
1357
|
+
const i = parseInt(it, 10);
|
|
1358
|
+
const commandParam = commandParams[i];
|
|
1359
|
+
if (!isValidParameter(arg, commandParam.type)) {
|
|
1360
|
+
if (typeof arg === "undefined" && !commandParam.required) {
|
|
1361
|
+
continue;
|
|
1362
|
+
}
|
|
1363
|
+
const actual = commandParam.type.endsWith("[]") ? `(${(Array.isArray(arg) ? arg : [arg]).map((a) => getArgumentType(a))})[]` : getArgumentType(arg);
|
|
1364
|
+
throw new Error(
|
|
1365
|
+
`Malformed type for "${commandParam.name}" parameter of command ${command}
|
|
1366
|
+
Expected: ${commandParam.type}
|
|
1367
|
+
Actual: ${actual}` + moreInfo
|
|
1368
|
+
);
|
|
1369
|
+
}
|
|
1370
|
+
if (i < variables.length) {
|
|
1371
|
+
const encodedArg = doubleEncodeVariables ? encodeURIComponent(encodeURIComponent(arg)) : encodeURIComponent(arg);
|
|
1372
|
+
endpoint = endpoint.replace(`:${commandParams[i].name}`, encodedArg);
|
|
1373
|
+
continue;
|
|
1374
|
+
}
|
|
1375
|
+
unmaskedBody[commandParams[i].name] = arg;
|
|
1376
|
+
}
|
|
1377
|
+
const { maskedBody, maskedArgs, isMasked } = mask(commandInfo, runtimeOptions, unmaskedBody, unmaskedArgs);
|
|
1378
|
+
const { isAborted, abortSignal, cleanup } = manageSessionAbortions.call(this);
|
|
1379
|
+
const requiresSession = endpointUri.includes("/:sessionId/");
|
|
1380
|
+
if (isAborted && command !== "deleteSession" && requiresSession) {
|
|
1381
|
+
throw new Error(`Trying to run command "${commandCallStructure(command, maskedArgs)}" after session has been deleted, aborting request without executing it`);
|
|
1382
|
+
}
|
|
1383
|
+
const request = new environment.value.Request(method, endpoint, unmaskedBody, abortSignal, isHubCommand, {
|
|
1384
|
+
onPerformance: (data) => this.emit("request.performance", { ...data, request: {
|
|
1385
|
+
...data.request,
|
|
1386
|
+
body: isMasked ? maskedBody : data.request.body
|
|
1387
|
+
} }),
|
|
1388
|
+
onRequest: (data) => this.emit("request.start", { ...data, body: isMasked ? maskedBody : data.body }),
|
|
1389
|
+
onResponse: (data) => this.emit("request.end", data),
|
|
1390
|
+
onRetry: (data) => this.emit("request.retry", data),
|
|
1391
|
+
onLogData: (data) => log3.info("DATA", transformCommandLogResult(isMasked ? maskedBody : data))
|
|
1392
|
+
});
|
|
1393
|
+
this.emit("command", { command, method, endpoint, body: maskedBody });
|
|
1394
|
+
log3.info("COMMAND", commandCallStructure(command, maskedArgs));
|
|
1395
|
+
const options = isMasked ? { ...this.options, headers: { ...this.options.headers, ...APPIUM_MASKING_HEADER } } : this.options;
|
|
1396
|
+
return request.makeRequest(options, this.sessionId).then((result) => {
|
|
1397
|
+
if (typeof result.value !== "undefined") {
|
|
1398
|
+
let resultLog = result.value;
|
|
1399
|
+
if (/screenshot|recording/i.test(command) && typeof result.value === "string" && result.value.length > 64) {
|
|
1400
|
+
resultLog = `${result.value.slice(0, 61)}...`;
|
|
1401
|
+
} else if (command === "executeScript" && typeof maskedBody.script === "string" && maskedBody.script.includes("(() => window.__wdioEvents__)")) {
|
|
1402
|
+
resultLog = `[${result.value.length} framework events captured]`;
|
|
1403
|
+
}
|
|
1404
|
+
log3.info("RESULT", resultLog);
|
|
1405
|
+
}
|
|
1406
|
+
this.emit("result", { command, method, endpoint, body: maskedBody, result });
|
|
1407
|
+
if (command === "deleteSession") {
|
|
1408
|
+
const browser = this;
|
|
1409
|
+
browser._bidiHandler?.close();
|
|
1410
|
+
const shutdownDriver = maskedBody.deleteSessionOpts?.shutdownDriver !== false;
|
|
1411
|
+
if (shutdownDriver && "wdio:driverPID" in this.capabilities && this.capabilities["wdio:driverPID"]) {
|
|
1412
|
+
log3.info(`Kill driver process with PID ${this.capabilities["wdio:driverPID"]}`);
|
|
1413
|
+
try {
|
|
1414
|
+
const killedSuccessfully = process.kill(this.capabilities["wdio:driverPID"], "SIGKILL");
|
|
1415
|
+
if (!killedSuccessfully) {
|
|
1416
|
+
log3.warn("Failed to kill driver process, manually clean-up might be required");
|
|
1417
|
+
}
|
|
1418
|
+
} catch (err) {
|
|
1419
|
+
log3.warn("Failed to kill driver process", err);
|
|
1420
|
+
}
|
|
1421
|
+
setTimeout(() => {
|
|
1422
|
+
for (const handle of process._getActiveHandles()) {
|
|
1423
|
+
if (handle.servername && handle.servername.includes("edgedl.me")) {
|
|
1424
|
+
handle.destroy();
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
}, 10);
|
|
1428
|
+
}
|
|
1429
|
+
if (!process.env.WDIO_WORKER_ID) {
|
|
1430
|
+
logger3.clearLogger();
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
return result.value;
|
|
1434
|
+
}).catch((error) => {
|
|
1435
|
+
this.emit("result", { command, method, endpoint, body: maskedBody, result: { error } });
|
|
1436
|
+
throw error;
|
|
1437
|
+
}).finally(() => {
|
|
1438
|
+
cleanup();
|
|
1439
|
+
});
|
|
1440
|
+
};
|
|
1441
|
+
}
|
|
1442
|
+
function manageSessionAbortions() {
|
|
1443
|
+
const abort = new AbortController();
|
|
1444
|
+
const abortOnSessionEnd = (result) => {
|
|
1445
|
+
if (result.command !== "deleteSession") {
|
|
1446
|
+
return;
|
|
1447
|
+
}
|
|
1448
|
+
const abortListeners = sessionAbortListeners.get(this.sessionId);
|
|
1449
|
+
if (abortListeners) {
|
|
1450
|
+
for (const abortListener of abortListeners) {
|
|
1451
|
+
abortListener.abort();
|
|
1452
|
+
}
|
|
1453
|
+
abortListeners.clear();
|
|
1454
|
+
sessionAbortListeners.set(this.sessionId, null);
|
|
1455
|
+
}
|
|
1456
|
+
};
|
|
1457
|
+
let abortListenerForCurrentSession = sessionAbortListeners.get(this.sessionId);
|
|
1458
|
+
if (typeof abortListenerForCurrentSession === "undefined") {
|
|
1459
|
+
abortListenerForCurrentSession = /* @__PURE__ */ new Set();
|
|
1460
|
+
sessionAbortListeners.set(this.sessionId, abortListenerForCurrentSession);
|
|
1461
|
+
this.on("result", abortOnSessionEnd);
|
|
1462
|
+
}
|
|
1463
|
+
if (abortListenerForCurrentSession === null) {
|
|
1464
|
+
return { isAborted: true, abortSignal: void 0, cleanup: () => {
|
|
1465
|
+
} };
|
|
1393
1466
|
}
|
|
1467
|
+
abortListenerForCurrentSession.add(abort);
|
|
1468
|
+
return {
|
|
1469
|
+
isAborted: false,
|
|
1470
|
+
abortSignal: abort.signal,
|
|
1471
|
+
cleanup: () => {
|
|
1472
|
+
this.off("result", abortOnSessionEnd);
|
|
1473
|
+
abortListenerForCurrentSession?.delete(abort);
|
|
1474
|
+
}
|
|
1475
|
+
};
|
|
1394
1476
|
}
|
|
1395
1477
|
|
|
1396
1478
|
// src/bidi/localTypes.ts
|
|
@@ -1546,10 +1628,10 @@ import { fetch as fetch2, Agent, ProxyAgent } from "undici";
|
|
|
1546
1628
|
|
|
1547
1629
|
// src/request/request.ts
|
|
1548
1630
|
import logger5 from "@wdio/logger";
|
|
1549
|
-
import {
|
|
1631
|
+
import { sleep } from "@wdio/utils";
|
|
1550
1632
|
|
|
1551
1633
|
// src/request/error.ts
|
|
1552
|
-
import { transformCommandLogResult } from "@wdio/utils";
|
|
1634
|
+
import { transformCommandLogResult as transformCommandLogResult2 } from "@wdio/utils";
|
|
1553
1635
|
|
|
1554
1636
|
// src/request/constants.ts
|
|
1555
1637
|
var RETRYABLE_STATUS_CODES = [408, 413, 429, 500, 502, 503, 504];
|
|
@@ -1593,7 +1675,7 @@ var WebDriverError = class extends Error {
|
|
|
1593
1675
|
if (typeof cmdJson !== "object") {
|
|
1594
1676
|
return "";
|
|
1595
1677
|
}
|
|
1596
|
-
const transformedRes =
|
|
1678
|
+
const transformedRes = transformCommandLogResult2(cmdJson);
|
|
1597
1679
|
if (typeof transformedRes === "string") {
|
|
1598
1680
|
return transformedRes;
|
|
1599
1681
|
}
|
|
@@ -1654,7 +1736,7 @@ var WebDriverResponseError = class _WebDriverResponseError extends WebDriverErro
|
|
|
1654
1736
|
// package.json
|
|
1655
1737
|
var package_default = {
|
|
1656
1738
|
name: "webdriver",
|
|
1657
|
-
version: "9.
|
|
1739
|
+
version: "9.15.0",
|
|
1658
1740
|
description: "A Node.js bindings implementation for the W3C WebDriver and Mobile JSONWire Protocol",
|
|
1659
1741
|
author: "Christian Bromann <mail@bromann.dev>",
|
|
1660
1742
|
homepage: "https://github.com/webdriverio/webdriverio/tree/main/packages/webdriver",
|
|
@@ -1839,7 +1921,7 @@ var WebDriverRequest = class {
|
|
|
1839
1921
|
async _request(url, fullRequestOptions, transformResponse, totalRetryCount = 0, retryCount = 0) {
|
|
1840
1922
|
log5.info(`[${fullRequestOptions.method}] ${url.href}`);
|
|
1841
1923
|
if (fullRequestOptions.body && Object.keys(fullRequestOptions.body).length) {
|
|
1842
|
-
|
|
1924
|
+
this.eventHandler.onLogData?.(fullRequestOptions.body);
|
|
1843
1925
|
}
|
|
1844
1926
|
const { ...requestLibOptions } = fullRequestOptions;
|
|
1845
1927
|
const startTime = performance.now();
|
|
@@ -1901,14 +1983,26 @@ var WebDriverRequest = class {
|
|
|
1901
1983
|
|
|
1902
1984
|
// src/request/node.ts
|
|
1903
1985
|
dns.setDefaultResultOrder("ipv4first");
|
|
1986
|
+
var SESSION_DISPATCHERS = /* @__PURE__ */ new Map();
|
|
1904
1987
|
var FetchRequest = class extends WebDriverRequest {
|
|
1905
|
-
fetch(url, opts) {
|
|
1906
|
-
|
|
1988
|
+
async fetch(url, opts) {
|
|
1989
|
+
const response = await fetch2(url, opts);
|
|
1990
|
+
if (opts.method === "DELETE") {
|
|
1991
|
+
const match = url.pathname.match(/\/session\/([^/]+)$/);
|
|
1992
|
+
const sessionId = match?.[1];
|
|
1993
|
+
if (sessionId) {
|
|
1994
|
+
this.cleanupSessionDispatcher(sessionId);
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
return response;
|
|
1907
1998
|
}
|
|
1908
|
-
|
|
1909
|
-
|
|
1999
|
+
getDispatcher(url, options, sessionId) {
|
|
2000
|
+
if (sessionId && SESSION_DISPATCHERS.has(sessionId)) {
|
|
2001
|
+
return SESSION_DISPATCHERS.get(sessionId);
|
|
2002
|
+
}
|
|
1910
2003
|
const { PROXY_URL, NO_PROXY } = environment.value.variables;
|
|
1911
|
-
const
|
|
2004
|
+
const shouldUseProxy = PROXY_URL && !NO_PROXY?.some((str) => url.hostname.endsWith(str));
|
|
2005
|
+
const dispatcher = shouldUseProxy ? new ProxyAgent({
|
|
1912
2006
|
uri: PROXY_URL,
|
|
1913
2007
|
connectTimeout: options.connectionRetryTimeout,
|
|
1914
2008
|
headersTimeout: options.connectionRetryTimeout,
|
|
@@ -1918,7 +2012,21 @@ var FetchRequest = class extends WebDriverRequest {
|
|
|
1918
2012
|
headersTimeout: options.connectionRetryTimeout,
|
|
1919
2013
|
bodyTimeout: options.connectionRetryTimeout
|
|
1920
2014
|
});
|
|
1921
|
-
|
|
2015
|
+
if (sessionId) {
|
|
2016
|
+
SESSION_DISPATCHERS.set(sessionId, dispatcher);
|
|
2017
|
+
}
|
|
2018
|
+
return dispatcher;
|
|
2019
|
+
}
|
|
2020
|
+
cleanupSessionDispatcher(sessionId) {
|
|
2021
|
+
const dispatcher = SESSION_DISPATCHERS.get(sessionId);
|
|
2022
|
+
if (dispatcher && typeof dispatcher.close === "function") {
|
|
2023
|
+
dispatcher.close();
|
|
2024
|
+
}
|
|
2025
|
+
SESSION_DISPATCHERS.delete(sessionId);
|
|
2026
|
+
}
|
|
2027
|
+
async createOptions(options, sessionId, isBrowser = false) {
|
|
2028
|
+
const { url, requestOptions } = await super.createOptions(options, sessionId, isBrowser);
|
|
2029
|
+
requestOptions.dispatcher = this.getDispatcher(url, options, sessionId);
|
|
1922
2030
|
return { url, requestOptions };
|
|
1923
2031
|
}
|
|
1924
2032
|
};
|
|
@@ -2048,7 +2156,11 @@ environment.value = {
|
|
|
2048
2156
|
}
|
|
2049
2157
|
};
|
|
2050
2158
|
export {
|
|
2159
|
+
APPIUM_MASKING_HEADER,
|
|
2160
|
+
BASE_64_REGEX,
|
|
2161
|
+
BASE_64_SAFE_STRING_TO_PROCESS_LENGTH,
|
|
2051
2162
|
BidiHandler,
|
|
2163
|
+
CommandRuntimeOptions,
|
|
2052
2164
|
DEFAULTS,
|
|
2053
2165
|
ELEMENT_KEY,
|
|
2054
2166
|
SHADOW_ELEMENT_KEY,
|