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/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 logger from "@wdio/logger";
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/command.ts
35
- var log = logger("webdriver");
36
- var BIDI_COMMANDS = Object.values(WebDriverBidiProtocol).map((def) => def.socket.command);
37
- var sessionAbortListeners = /* @__PURE__ */ new Map();
38
- function command_default(method, endpointUri, commandInfo, doubleEncodeVariables = false) {
39
- const { command, deprecated, ref, parameters, variables = [], isHubCommand = false } = commandInfo;
40
- return async function protocolCommand(...args) {
41
- const isBidiCommand = BIDI_COMMANDS.includes(command);
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
- For more info see ${ref}
56
- `;
57
- const body = {};
58
- if (typeof deprecated === "string" && !process.env.DISABLE_WEBDRIVERIO_DEPRECATION_WARNINGS) {
59
- const warning = deprecated.replace("This command", `The "${command}" command`);
60
- log.warn(warning);
61
- console.warn(`\u26A0\uFE0F [WEBDRIVERIO DEPRECATION NOTICE] ${warning}`);
62
- }
63
- if (isBidiCommand) {
64
- throw new Error(
65
- `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.`
66
- );
67
- }
68
- const minAllowedParams = commandParams.filter((param) => param.required).length;
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
- Property Description:
73
- ${commandParams.map((p) => ` "${p.name}" (${p.type}): ${p.description}`).join("\n")}` : "";
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
- import { deepmergeCustom } from "deepmerge-ts";
343
- import logger3 from "@wdio/logger";
344
- import {
345
- WebDriverProtocol,
346
- MJsonWProtocol,
347
- AppiumProtocol,
348
- ChromiumProtocol,
349
- SauceLabsProtocol,
350
- SeleniumProtocol,
351
- GeckoProtocol,
352
- WebDriverBidiProtocol as WebDriverBidiProtocol2
353
- } from "@wdio/protocols";
354
- import { CAPABILITY_KEYS } from "@wdio/protocols";
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 base64Regex = /^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$/;
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
- log2.info(`Connecting to webSocketUrl ${this.#webSocketUrl}`);
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
- log2.info(`Close Bidi connection to ${this.#webSocketUrl}`);
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
- log2.info(`Reconnect to new Bidi session at ${webSocketUrl}`);
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" && base64Regex.test(payload.result.data)) {
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
- log2.info("BIDI RESULT", resultLog);
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
- log2.error(`Couldn't resolve command with id ${payload.id}`);
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
- log2.error(`Failed parse message: ${error.message}`);
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
- log2.info("BIDI COMMAND", ...parseBidiCommand(params));
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 log3 = logger3("webdriver");
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
- log3.error(err);
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
- log3.debug("request failed due to missing body");
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
- log3.debug(`request failed due to status ${body.status}`);
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
- log3.debug("request failed due to response error:", errMsg);
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 ? WebDriverBidiProtocol2 : {},
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
- log3.info(`Found direct connect information in new session response. Will connect to server at ${directConnectProtocol}://${directConnectHost}:${directConnectPort}${directConnectPath}`);
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
- log3.info("Skip connecting to WebDriver Bidi interface due to unit tests");
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 && log3.info(`Connected to WebDriver Bidi interface at ${socketUrl}`));
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(WebDriverBidiProtocol2).map((def) => def.socket).reduce((acc, cur) => {
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
- log3.error(`Failed parse WebDriver Bidi message: ${err.message}`);
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 { transformCommandLogResult as transformCommandLogResult2, sleep } from "@wdio/utils";
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 = transformCommandLogResult(cmdJson);
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.13.0",
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
- log5.info("DATA", transformCommandLogResult2(fullRequestOptions.body));
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
- return fetch2(url, opts);
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
- async createOptions(options, sessionId, isBrowser = false) {
1909
- const { url, requestOptions } = await super.createOptions(options, sessionId, isBrowser);
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 dispatcher = PROXY_URL && !NO_PROXY?.some((str) => url.hostname.endsWith(str)) ? new ProxyAgent({
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
- requestOptions.dispatcher = dispatcher;
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,