tftsr-devops_investigation/node_modules/webdriver/build/index.js
Shaun Arman 8839075805 feat: initial implementation of TFTSR IT Triage & RCA application
Implements Phases 1-8 of the TFTSR implementation plan.

Rust backend (Tauri 2.x, src-tauri/):
- Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama
- PII detection engine: 11 regex patterns with overlap resolution
- SQLCipher AES-256 encrypted database with 10 versioned migrations
- 28 Tauri IPC commands for triage, analysis, document, and system ops
- Ollama: hardware probe, model recommendations, pull/delete with events
- RCA and blameless post-mortem Markdown document generators
- PDF export via printpdf
- Audit log: SHA-256 hash of every external data send
- Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2)

Frontend (React 18 + TypeScript + Vite, src/):
- 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings
- 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives
- 3 Zustand stores: session, settings (persisted), history
- Type-safe tauriCommands.ts matching Rust backend types exactly
- 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs)

DevOps:
- .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push
- .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload

Verified:
- cargo check: zero errors
- tsc --noEmit: zero errors
- vitest run: 13/13 unit tests passing

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 22:36:25 -05:00

2152 lines
84 KiB
JavaScript

var __defProp = Object.defineProperty;
var __typeError = (msg) => {
throw TypeError(msg);
};
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
var __privateWrapper = (obj, member, setter, getter) => ({
set _(value) {
__privateSet(obj, member, value, setter);
},
get _() {
return __privateGet(obj, member, getter);
}
});
// src/browser.ts
import logger6 from "@wdio/logger";
// src/index.ts
import logger4 from "@wdio/logger";
import { webdriverMonad, sessionEnvironmentDetector, startWebDriver, isBidi } from "@wdio/utils";
import { validateConfig } from "@wdio/config";
// src/command.ts
import logger3 from "@wdio/logger";
import { commandCallStructure, isValidParameter, getArgumentType, transformCommandLogResult } from "@wdio/utils";
import { WebDriverBidiProtocol as WebDriverBidiProtocol2 } from "@wdio/protocols";
// src/environment.ts
var isNode = !!(typeof process !== "undefined" && process.version);
var environment = {
value: {
get Request() {
throw new Error("Request is not available in this environment");
},
get Socket() {
throw new Error("Socket is not available in this environment");
},
get createBidiConnection() {
throw new Error("createBidiConnection is not available in this environment");
},
get killDriverProcess() {
throw new Error("killDriverProcess is not available in this environment");
},
get variables() {
return {};
}
}
};
// src/types.ts
var CommandRuntimeOptions = class {
constructor(options) {
// mask the text parameter value of the command
__publicField(this, "mask");
this.mask = options.mask;
}
};
// src/utils.ts
import { deepmergeCustom } from "deepmerge-ts";
import logger2, { SENSITIVE_DATA_REPLACER } from "@wdio/logger";
import {
WebDriverProtocol,
MJsonWProtocol,
AppiumProtocol,
ChromiumProtocol,
SauceLabsProtocol,
SeleniumProtocol,
GeckoProtocol,
WebDriverBidiProtocol
} from "@wdio/protocols";
import { CAPABILITY_KEYS } from "@wdio/protocols";
// src/bidi/core.ts
import logger from "@wdio/logger";
// src/constants.ts
var DEFAULTS = {
/**
* protocol of automation driver
*/
protocol: {
type: "string",
default: "http",
match: /(http|https)/
},
/**
* hostname of automation driver
*/
hostname: {
type: "string",
default: "localhost"
},
/**
* port of automation driver
*/
port: {
type: "number"
},
/**
* path to WebDriver endpoints
*/
path: {
type: "string",
validate: (path) => {
if (!path.startsWith("/")) {
throw new TypeError('The option "path" needs to start with a "/"');
}
return true;
},
default: "/"
},
/**
* A key-value store of query parameters to be added to every selenium request
*/
queryParams: {
type: "object"
},
/**
* cloud user if applicable
*/
user: {
type: "string"
},
/**
* access key to user
*/
key: {
type: "string"
},
/**
* capability of WebDriver session
*/
capabilities: {
type: "object",
required: true
},
/**
* Level of logging verbosity
*/
logLevel: {
type: "string",
default: "info",
match: /(trace|debug|info|warn|error|silent)/
},
/**
* directory for log files
*/
outputDir: {
type: "string"
},
/**
* Timeout for any WebDriver request to a driver or grid
*/
connectionRetryTimeout: {
type: "number",
default: 12e4
},
/**
* Count of request retries to the Selenium server
*/
connectionRetryCount: {
type: "number",
default: 3
},
/**
* Override default agent
*/
logLevels: {
type: "object"
},
/**
* Pass custom headers
*/
headers: {
type: "object"
},
/**
* Function transforming the request options before the request is made
*/
transformRequest: {
type: "function",
default: (requestOptions) => requestOptions
},
/**
* Function transforming the response object after it is received
*/
transformResponse: {
type: "function",
default: (response) => response
},
/**
* Appium direct connect options server (https://appiumpro.com/editions/86-connecting-directly-to-appium-hosts-in-distributed-environments)
* Whether to allow direct connect caps to adjust endpoint details (Appium only)
*/
enableDirectConnect: {
type: "boolean",
default: true
},
/**
* Whether it requires SSL certificates to be valid in HTTP/s requests
* for an environment which cannot get process environment well.
*/
strictSSL: {
type: "boolean",
default: true
},
/**
* The path to the root of the cache directory. This directory is used to store all drivers that are downloaded
* when attempting to start a session.
*/
cacheDir: {
type: "string",
default: environment.value.variables.WEBDRIVER_CACHE_DIR
},
/**
* Mask sensitive data in logs by replacing matching string or all captured groups for the provided regular expressions as string
*/
maskingPatterns: {
type: "string",
default: void 0
}
};
var ELEMENT_KEY = "element-6066-11e4-a52e-4f735466cecf";
var SHADOW_ELEMENT_KEY = "shadow-6066-11e4-a52e-4f735466cecf";
var BASE_64_REGEX = /^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$/;
var BASE_64_SAFE_STRING_TO_PROCESS_LENGTH = 2e5;
var APPIUM_MASKING_HEADER = { "x-appium-is-sensitive": "true" };
// src/bidi/utils.ts
function isBase64Safe(str) {
if (typeof str !== "string") {
return false;
}
if (str.length === 0) {
return true;
}
if (str.length % 4 !== 0) {
return false;
}
const length = str.length;
const digitCount = length.toString().length;
if (length > BASE_64_SAFE_STRING_TO_PROCESS_LENGTH) {
const chunkSize = Math.floor(length / digitCount / 4) * 4;
for (let i = 0; i < length; i += chunkSize) {
const chunk = str.slice(i, i + chunkSize);
if (!BASE_64_REGEX.test(chunk)) {
return false;
}
}
return true;
}
return BASE_64_REGEX.test(str);
}
// src/bidi/core.ts
var SCRIPT_PREFIX = "/* __wdio script__ */";
var SCRIPT_SUFFIX = "/* __wdio script end__ */";
var log = logger("webdriver");
var RESPONSE_TIMEOUT = 1e3 * 60;
var _id, _ws, _waitForConnected, _resolveWaitForConnected, _webSocketUrl, _clientOptions, _pendingCommands, _BidiCore_instances, handleResponse_fn;
var BidiCore = class {
constructor(webSocketUrl, opts) {
__privateAdd(this, _BidiCore_instances);
__privateAdd(this, _id, 0);
__privateAdd(this, _ws);
__privateAdd(this, _waitForConnected);
__privateAdd(this, _resolveWaitForConnected);
__privateAdd(this, _webSocketUrl);
__privateAdd(this, _clientOptions);
__privateAdd(this, _pendingCommands, /* @__PURE__ */ new Map());
__publicField(this, "client");
/**
* @private
*/
__publicField(this, "_isConnected", false);
__privateSet(this, _webSocketUrl, webSocketUrl);
__privateSet(this, _clientOptions, opts);
__privateSet(this, _resolveWaitForConnected, () => {
});
__privateSet(this, _waitForConnected, new Promise((resolve) => {
__privateSet(this, _resolveWaitForConnected, resolve);
}));
}
/**
* We initiate the Bidi instance before a WebdriverIO instance is created.
* In order to emit Bidi events we have to attach the WebdriverIO instance
* to the Bidi instance afterwards.
*/
attachClient(client) {
this.client = client;
}
async connect() {
log.info("Connecting to webSocketUrl ".concat(__privateGet(this, _webSocketUrl)));
__privateSet(this, _ws, await environment.value.createBidiConnection(__privateGet(this, _webSocketUrl), __privateGet(this, _clientOptions)));
this._isConnected = Boolean(__privateGet(this, _ws));
__privateGet(this, _resolveWaitForConnected).call(this, this._isConnected);
if (__privateGet(this, _ws)) {
__privateGet(this, _ws).on("message", __privateMethod(this, _BidiCore_instances, handleResponse_fn).bind(this));
}
return this._isConnected;
}
close() {
if (!this._isConnected) {
return;
}
log.info("Close Bidi connection to ".concat(__privateGet(this, _webSocketUrl)));
this._isConnected = false;
if (__privateGet(this, _ws)) {
__privateGet(this, _ws).off("message", __privateMethod(this, _BidiCore_instances, handleResponse_fn).bind(this));
__privateGet(this, _ws).close();
__privateGet(this, _ws).terminate();
__privateSet(this, _ws, void 0);
}
}
reconnect(webSocketUrl, opts) {
log.info("Reconnect to new Bidi session at ".concat(webSocketUrl));
this.close();
__privateSet(this, _webSocketUrl, webSocketUrl);
__privateSet(this, _clientOptions, opts);
return this.connect();
}
/**
* Helper function that allows to wait until Bidi connection establishes
* @returns a promise that resolves once the connection to WebDriver Bidi protocol was established
*/
waitForConnected() {
return __privateGet(this, _waitForConnected);
}
get socket() {
return __privateGet(this, _ws);
}
get isConnected() {
return this._isConnected;
}
/**
* for testing purposes only
* @internal
*/
get __handleResponse() {
return __privateMethod(this, _BidiCore_instances, handleResponse_fn).bind(this);
}
async send(params) {
const id = this.sendAsync(params);
const failError = new Error('WebDriver Bidi command "'.concat(params.method, '" failed'));
const payload = await new Promise((resolve, reject) => {
const t = setTimeout(() => {
reject(new Error("Command ".concat(params.method, " with id ").concat(id, " (with the following parameter: ").concat(JSON.stringify(params.params), ") timed out")));
__privateGet(this, _pendingCommands).delete(id);
}, RESPONSE_TIMEOUT);
__privateGet(this, _pendingCommands).set(id, (payload2) => {
clearTimeout(t);
resolve(payload2);
});
});
if (payload.type === "error" || "error" in payload) {
const error = payload;
failError.message += " with error: ".concat(payload.error, " - ").concat(error.message);
if (error.stacktrace && typeof error.stacktrace === "string") {
const driverStack = error.stacktrace.split("\n").filter(Boolean).map((line) => " at ".concat(line)).join("\n");
failError.stack += "\n\nDriver Stack:\n".concat(driverStack);
}
throw failError;
}
return payload;
}
sendAsync(params) {
var _a;
if (!__privateGet(this, _ws) || !this._isConnected) {
throw new Error("No connection to WebDriver Bidi was established");
}
log.info("BIDI COMMAND", ...parseBidiCommand(params));
const id = ++__privateWrapper(this, _id)._;
(_a = this.client) == null ? void 0 : _a.emit("bidiCommand", params);
__privateGet(this, _ws).send(JSON.stringify({ id, ...params }));
return id;
}
};
_id = new WeakMap();
_ws = new WeakMap();
_waitForConnected = new WeakMap();
_resolveWaitForConnected = new WeakMap();
_webSocketUrl = new WeakMap();
_clientOptions = new WeakMap();
_pendingCommands = new WeakMap();
_BidiCore_instances = new WeakSet();
handleResponse_fn = function(data) {
var _a;
try {
const payload = JSON.parse(data.toString());
if (!payload.id) {
return;
}
let resultLog = data.toString();
if (typeof payload.result === "object" && payload.result && "data" in payload.result && typeof payload.result.data === "string" && isBase64Safe(payload.result.data)) {
resultLog = JSON.stringify({
...payload.result,
data: "Base64 string [".concat(payload.result.data.length, " chars]")
});
}
log.info("BIDI RESULT", resultLog);
(_a = this.client) == null ? void 0 : _a.emit("bidiResult", payload);
const resolve = __privateGet(this, _pendingCommands).get(payload.id);
if (!resolve) {
log.error("Couldn't resolve command with id ".concat(payload.id));
return;
}
__privateGet(this, _pendingCommands).delete(payload.id);
resolve(payload);
} catch (err) {
const error = err instanceof Error ? err : new Error("Failed parse message: ".concat(String(err)));
log.error("Failed parse message: ".concat(error.message));
}
};
function parseBidiCommand(params) {
const commandName = params.method;
if (commandName === "script.addPreloadScript") {
const param = params.params;
const logString = "{ functionDeclaration: <PreloadScript[".concat(new TextEncoder().encode(param.functionDeclaration).length, " bytes]>, contexts: ").concat(JSON.stringify(param.contexts), " }");
return [commandName, logString];
} else if (commandName === "script.callFunction") {
const param = params.params;
const fn = param.functionDeclaration;
let fnName = "";
if (fn.includes(SCRIPT_PREFIX)) {
const internalFn = fn.slice(
fn.indexOf(SCRIPT_PREFIX) + SCRIPT_PREFIX.length,
fn.indexOf(SCRIPT_SUFFIX)
);
const functionPrefix = "function ";
if (internalFn.startsWith(functionPrefix)) {
fnName = internalFn.slice(
internalFn.indexOf(functionPrefix) + functionPrefix.length,
internalFn.indexOf("(")
);
}
}
const logString = JSON.stringify({
...param,
functionDeclaration: "<Function[".concat(new TextEncoder().encode(param.functionDeclaration).length, " bytes] ").concat(fnName || "anonymous", ">")
});
return [commandName, logString];
}
return [commandName, JSON.stringify(params.params)];
}
// src/bidi/handler.ts
var BidiHandler = class extends BidiCore {
/**
* WebDriver Bidi command to send command method "session.status" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-session-status
* @param params `remote.EmptyParams` {@link https://w3c.github.io/webdriver-bidi/#command-session-status | command parameter}
* @returns `Promise<local.SessionStatusResult>`
**/
async sessionStatus(params) {
const result = await this.send({
method: "session.status",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "session.new" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-session-new
* @param params `remote.SessionNewParameters` {@link https://w3c.github.io/webdriver-bidi/#command-session-new | command parameter}
* @returns `Promise<local.SessionNewResult>`
**/
async sessionNew(params) {
const result = await this.send({
method: "session.new",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "session.end" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-session-end
* @param params `remote.EmptyParams` {@link https://w3c.github.io/webdriver-bidi/#command-session-end | command parameter}
* @returns `Promise<local.EmptyResult>`
**/
async sessionEnd(params) {
const result = await this.send({
method: "session.end",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "session.subscribe" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-session-subscribe
* @param params `remote.SessionSubscriptionRequest` {@link https://w3c.github.io/webdriver-bidi/#command-session-subscribe | command parameter}
* @returns `Promise<local.SessionSubscribeResult>`
**/
async sessionSubscribe(params) {
const result = await this.send({
method: "session.subscribe",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "session.unsubscribe" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-session-unsubscribe
* @param params `remote.SessionUnsubscribeParameters` {@link https://w3c.github.io/webdriver-bidi/#command-session-unsubscribe | command parameter}
* @returns `Promise<local.EmptyResult>`
**/
async sessionUnsubscribe(params) {
const result = await this.send({
method: "session.unsubscribe",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "browser.close" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-browser-close
* @param params `remote.EmptyParams` {@link https://w3c.github.io/webdriver-bidi/#command-browser-close | command parameter}
* @returns `Promise<local.EmptyResult>`
**/
async browserClose(params) {
const result = await this.send({
method: "browser.close",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "browser.createUserContext" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-browser-createUserContext
* @param params `remote.BrowserCreateUserContextParameters` {@link https://w3c.github.io/webdriver-bidi/#command-browser-createUserContext | command parameter}
* @returns `Promise<local.BrowserCreateUserContextResult>`
**/
async browserCreateUserContext(params) {
const result = await this.send({
method: "browser.createUserContext",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "browser.getClientWindows" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-browser-getClientWindows
* @param params `remote.EmptyParams` {@link https://w3c.github.io/webdriver-bidi/#command-browser-getClientWindows | command parameter}
* @returns `Promise<local.BrowserGetClientWindowsResult>`
**/
async browserGetClientWindows(params) {
const result = await this.send({
method: "browser.getClientWindows",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "browser.getUserContexts" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-browser-getUserContexts
* @param params `remote.EmptyParams` {@link https://w3c.github.io/webdriver-bidi/#command-browser-getUserContexts | command parameter}
* @returns `Promise<local.BrowserGetUserContextsResult>`
**/
async browserGetUserContexts(params) {
const result = await this.send({
method: "browser.getUserContexts",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "browser.removeUserContext" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-browser-removeUserContext
* @param params `remote.BrowserRemoveUserContextParameters` {@link https://w3c.github.io/webdriver-bidi/#command-browser-removeUserContext | command parameter}
* @returns `Promise<local.EmptyResult>`
**/
async browserRemoveUserContext(params) {
const result = await this.send({
method: "browser.removeUserContext",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "browser.setClientWindowState" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-browser-setClientWindowState
* @param params `remote.BrowserSetClientWindowStateParameters` {@link https://w3c.github.io/webdriver-bidi/#command-browser-setClientWindowState | command parameter}
* @returns `Promise<local.EmptyResult>`
**/
async browserSetClientWindowState(params) {
const result = await this.send({
method: "browser.setClientWindowState",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "browsingContext.activate" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-browsingContext-activate
* @param params `remote.BrowsingContextActivateParameters` {@link https://w3c.github.io/webdriver-bidi/#command-browsingContext-activate | command parameter}
* @returns `Promise<local.EmptyResult>`
**/
async browsingContextActivate(params) {
const result = await this.send({
method: "browsingContext.activate",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "browsingContext.captureScreenshot" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-browsingContext-captureScreenshot
* @param params `remote.BrowsingContextCaptureScreenshotParameters` {@link https://w3c.github.io/webdriver-bidi/#command-browsingContext-captureScreenshot | command parameter}
* @returns `Promise<local.BrowsingContextCaptureScreenshotResult>`
**/
async browsingContextCaptureScreenshot(params) {
const result = await this.send({
method: "browsingContext.captureScreenshot",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "browsingContext.close" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-browsingContext-close
* @param params `remote.BrowsingContextCloseParameters` {@link https://w3c.github.io/webdriver-bidi/#command-browsingContext-close | command parameter}
* @returns `Promise<local.EmptyResult>`
**/
async browsingContextClose(params) {
const result = await this.send({
method: "browsingContext.close",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "browsingContext.create" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-browsingContext-create
* @param params `remote.BrowsingContextCreateParameters` {@link https://w3c.github.io/webdriver-bidi/#command-browsingContext-create | command parameter}
* @returns `Promise<local.BrowsingContextCreateResult>`
**/
async browsingContextCreate(params) {
const result = await this.send({
method: "browsingContext.create",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "browsingContext.getTree" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-browsingContext-getTree
* @param params `remote.BrowsingContextGetTreeParameters` {@link https://w3c.github.io/webdriver-bidi/#command-browsingContext-getTree | command parameter}
* @returns `Promise<local.BrowsingContextGetTreeResult>`
**/
async browsingContextGetTree(params) {
const result = await this.send({
method: "browsingContext.getTree",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "browsingContext.handleUserPrompt" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-browsingContext-handleUserPrompt
* @param params `remote.BrowsingContextHandleUserPromptParameters` {@link https://w3c.github.io/webdriver-bidi/#command-browsingContext-handleUserPrompt | command parameter}
* @returns `Promise<local.EmptyResult>`
**/
async browsingContextHandleUserPrompt(params) {
const result = await this.send({
method: "browsingContext.handleUserPrompt",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "browsingContext.locateNodes" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-browsingContext-locateNodes
* @param params `remote.BrowsingContextLocateNodesParameters` {@link https://w3c.github.io/webdriver-bidi/#command-browsingContext-locateNodes | command parameter}
* @returns `Promise<local.BrowsingContextLocateNodesResult>`
**/
async browsingContextLocateNodes(params) {
const result = await this.send({
method: "browsingContext.locateNodes",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "browsingContext.navigate" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-browsingContext-navigate
* @param params `remote.BrowsingContextNavigateParameters` {@link https://w3c.github.io/webdriver-bidi/#command-browsingContext-navigate | command parameter}
* @returns `Promise<local.BrowsingContextNavigateResult>`
**/
async browsingContextNavigate(params) {
const result = await this.send({
method: "browsingContext.navigate",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "browsingContext.print" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-browsingContext-print
* @param params `remote.BrowsingContextPrintParameters` {@link https://w3c.github.io/webdriver-bidi/#command-browsingContext-print | command parameter}
* @returns `Promise<local.BrowsingContextPrintResult>`
**/
async browsingContextPrint(params) {
const result = await this.send({
method: "browsingContext.print",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "browsingContext.reload" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-browsingContext-reload
* @param params `remote.BrowsingContextReloadParameters` {@link https://w3c.github.io/webdriver-bidi/#command-browsingContext-reload | command parameter}
* @returns `Promise<local.EmptyResult>`
**/
async browsingContextReload(params) {
const result = await this.send({
method: "browsingContext.reload",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "browsingContext.setViewport" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-browsingContext-setViewport
* @param params `remote.BrowsingContextSetViewportParameters` {@link https://w3c.github.io/webdriver-bidi/#command-browsingContext-setViewport | command parameter}
* @returns `Promise<local.EmptyResult>`
**/
async browsingContextSetViewport(params) {
const result = await this.send({
method: "browsingContext.setViewport",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "browsingContext.traverseHistory" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-browsingContext-traverseHistory
* @param params `remote.BrowsingContextTraverseHistoryParameters` {@link https://w3c.github.io/webdriver-bidi/#command-browsingContext-traverseHistory | command parameter}
* @returns `Promise<local.BrowsingContextTraverseHistoryResult>`
**/
async browsingContextTraverseHistory(params) {
const result = await this.send({
method: "browsingContext.traverseHistory",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "emulation.setGeolocationOverride" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-emulation-setGeolocationOverride
* @param params `remote.EmulationSetGeolocationOverrideParameters` {@link https://w3c.github.io/webdriver-bidi/#command-emulation-setGeolocationOverride | command parameter}
* @returns `Promise<local.EmptyResult>`
**/
async emulationSetGeolocationOverride(params) {
const result = await this.send({
method: "emulation.setGeolocationOverride",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "network.addIntercept" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-network-addIntercept
* @param params `remote.NetworkAddInterceptParameters` {@link https://w3c.github.io/webdriver-bidi/#command-network-addIntercept | command parameter}
* @returns `Promise<local.NetworkAddInterceptResult>`
**/
async networkAddIntercept(params) {
const result = await this.send({
method: "network.addIntercept",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "network.continueRequest" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-network-continueRequest
* @param params `remote.NetworkContinueRequestParameters` {@link https://w3c.github.io/webdriver-bidi/#command-network-continueRequest | command parameter}
* @returns `Promise<local.EmptyResult>`
**/
async networkContinueRequest(params) {
const result = await this.send({
method: "network.continueRequest",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "network.continueResponse" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-network-continueResponse
* @param params `remote.NetworkContinueResponseParameters` {@link https://w3c.github.io/webdriver-bidi/#command-network-continueResponse | command parameter}
* @returns `Promise<local.EmptyResult>`
**/
async networkContinueResponse(params) {
const result = await this.send({
method: "network.continueResponse",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "network.continueWithAuth" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-network-continueWithAuth
* @param params `remote.NetworkContinueWithAuthParameters` {@link https://w3c.github.io/webdriver-bidi/#command-network-continueWithAuth | command parameter}
* @returns `Promise<local.EmptyResult>`
**/
async networkContinueWithAuth(params) {
const result = await this.send({
method: "network.continueWithAuth",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "network.failRequest" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-network-failRequest
* @param params `remote.NetworkFailRequestParameters` {@link https://w3c.github.io/webdriver-bidi/#command-network-failRequest | command parameter}
* @returns `Promise<local.EmptyResult>`
**/
async networkFailRequest(params) {
const result = await this.send({
method: "network.failRequest",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "network.provideResponse" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-network-provideResponse
* @param params `remote.NetworkProvideResponseParameters` {@link https://w3c.github.io/webdriver-bidi/#command-network-provideResponse | command parameter}
* @returns `Promise<local.EmptyResult>`
**/
async networkProvideResponse(params) {
const result = await this.send({
method: "network.provideResponse",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "network.removeIntercept" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-network-removeIntercept
* @param params `remote.NetworkRemoveInterceptParameters` {@link https://w3c.github.io/webdriver-bidi/#command-network-removeIntercept | command parameter}
* @returns `Promise<local.EmptyResult>`
**/
async networkRemoveIntercept(params) {
const result = await this.send({
method: "network.removeIntercept",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "network.setCacheBehavior" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-network-setCacheBehavior
* @param params `remote.NetworkSetCacheBehaviorParameters` {@link https://w3c.github.io/webdriver-bidi/#command-network-setCacheBehavior | command parameter}
* @returns `Promise<local.EmptyResult>`
**/
async networkSetCacheBehavior(params) {
const result = await this.send({
method: "network.setCacheBehavior",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "script.addPreloadScript" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-script-addPreloadScript
* @param params `remote.ScriptAddPreloadScriptParameters` {@link https://w3c.github.io/webdriver-bidi/#command-script-addPreloadScript | command parameter}
* @returns `Promise<local.ScriptAddPreloadScriptResult>`
**/
async scriptAddPreloadScript(params) {
const result = await this.send({
method: "script.addPreloadScript",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "script.disown" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-script-disown
* @param params `remote.ScriptDisownParameters` {@link https://w3c.github.io/webdriver-bidi/#command-script-disown | command parameter}
* @returns `Promise<local.EmptyResult>`
**/
async scriptDisown(params) {
const result = await this.send({
method: "script.disown",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "script.callFunction" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-script-callFunction
* @param params `remote.ScriptCallFunctionParameters` {@link https://w3c.github.io/webdriver-bidi/#command-script-callFunction | command parameter}
* @returns `Promise<local.EmptyResult>`
**/
async scriptCallFunction(params) {
const result = await this.send({
method: "script.callFunction",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "script.evaluate" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-script-evaluate
* @param params `remote.ScriptEvaluateParameters` {@link https://w3c.github.io/webdriver-bidi/#command-script-evaluate | command parameter}
* @returns `Promise<local.ScriptEvaluateResult>`
**/
async scriptEvaluate(params) {
const result = await this.send({
method: "script.evaluate",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "script.getRealms" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-script-getRealms
* @param params `remote.ScriptGetRealmsParameters` {@link https://w3c.github.io/webdriver-bidi/#command-script-getRealms | command parameter}
* @returns `Promise<local.ScriptGetRealmsResult>`
**/
async scriptGetRealms(params) {
const result = await this.send({
method: "script.getRealms",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "script.removePreloadScript" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-script-removePreloadScript
* @param params `remote.ScriptRemovePreloadScriptParameters` {@link https://w3c.github.io/webdriver-bidi/#command-script-removePreloadScript | command parameter}
* @returns `Promise<local.EmptyResult>`
**/
async scriptRemovePreloadScript(params) {
const result = await this.send({
method: "script.removePreloadScript",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "storage.getCookies" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-storage-getCookies
* @param params `remote.StorageGetCookiesParameters` {@link https://w3c.github.io/webdriver-bidi/#command-storage-getCookies | command parameter}
* @returns `Promise<local.StorageGetCookiesResult>`
**/
async storageGetCookies(params) {
const result = await this.send({
method: "storage.getCookies",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "storage.setCookie" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-storage-setCookie
* @param params `remote.StorageSetCookieParameters` {@link https://w3c.github.io/webdriver-bidi/#command-storage-setCookie | command parameter}
* @returns `Promise<local.StorageSetCookieResult>`
**/
async storageSetCookie(params) {
const result = await this.send({
method: "storage.setCookie",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "storage.deleteCookies" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-storage-deleteCookies
* @param params `remote.StorageDeleteCookiesParameters` {@link https://w3c.github.io/webdriver-bidi/#command-storage-deleteCookies | command parameter}
* @returns `Promise<local.StorageDeleteCookiesResult>`
**/
async storageDeleteCookies(params) {
const result = await this.send({
method: "storage.deleteCookies",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "input.performActions" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-input-performActions
* @param params `remote.InputPerformActionsParameters` {@link https://w3c.github.io/webdriver-bidi/#command-input-performActions | command parameter}
* @returns `Promise<local.EmptyResult>`
**/
async inputPerformActions(params) {
const result = await this.send({
method: "input.performActions",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "input.releaseActions" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-input-releaseActions
* @param params `remote.InputReleaseActionsParameters` {@link https://w3c.github.io/webdriver-bidi/#command-input-releaseActions | command parameter}
* @returns `Promise<local.EmptyResult>`
**/
async inputReleaseActions(params) {
const result = await this.send({
method: "input.releaseActions",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "input.setFiles" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-input-setFiles
* @param params `remote.InputSetFilesParameters` {@link https://w3c.github.io/webdriver-bidi/#command-input-setFiles | command parameter}
* @returns `Promise<local.EmptyResult>`
**/
async inputSetFiles(params) {
const result = await this.send({
method: "input.setFiles",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "webExtension.install" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-webExtension-install
* @param params `remote.WebExtensionInstallParameters` {@link https://w3c.github.io/webdriver-bidi/#command-webExtension-install | command parameter}
* @returns `Promise<local.WebExtensionInstallResult>`
**/
async webExtensionInstall(params) {
const result = await this.send({
method: "webExtension.install",
params
});
return result.result;
}
/**
* WebDriver Bidi command to send command method "webExtension.uninstall" with parameters.
* @url https://w3c.github.io/webdriver-bidi/#command-webExtension-uninstall
* @param params `remote.WebExtensionUninstallParameters` {@link https://w3c.github.io/webdriver-bidi/#command-webExtension-uninstall | command parameter}
* @returns `Promise<local.EmptyResult>`
**/
async webExtensionUninstall(params) {
const result = await this.send({
method: "webExtension.uninstall",
params
});
return result.result;
}
};
// src/utils.ts
var log2 = logger2("webdriver");
var deepmerge = deepmergeCustom({ mergeArrays: false });
function deepEqual(a, b) {
if (a === b) {
return true;
}
if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) {
return false;
}
const keysA = Object.keys(a);
const keysB = Object.keys(b);
if (keysA.length !== keysB.length) {
return false;
}
for (const key of keysA) {
if (!keysB.includes(key) || !deepEqual(a[key], b[key])) {
return false;
}
}
return true;
}
var BROWSER_DRIVER_ERRORS = [
"unknown command: wd/hub/session",
// chromedriver
"HTTP method not allowed",
// geckodriver
"'POST /wd/hub/session' was not found.",
// safaridriver
"Command not found"
// iedriver
];
async function startWebDriverSession(params) {
const capabilities = params.capabilities && "alwaysMatch" in params.capabilities ? params.capabilities : { alwaysMatch: params.capabilities, firstMatch: [{}] };
if (
/**
* except, if user does not want to opt-in
*/
!capabilities.alwaysMatch["wdio:enforceWebDriverClassic"] && /**
* or user requests a Safari session which does not support Bidi
*/
typeof capabilities.alwaysMatch.browserName === "string" && capabilities.alwaysMatch.browserName.toLowerCase() !== "safari"
) {
capabilities.alwaysMatch.webSocketUrl = true;
capabilities.alwaysMatch.unhandledPromptBehavior = "ignore";
}
validateCapabilities(capabilities.alwaysMatch);
const keysToNormalize = new Set(Object.keys(capabilities.alwaysMatch));
if (capabilities.firstMatch) {
capabilities.firstMatch.forEach((match) => {
Object.keys(match).forEach((key) => keysToNormalize.add(key));
});
for (const key of keysToNormalize) {
const alwaysVal = capabilities.alwaysMatch[key];
if (alwaysVal === void 0) {
continue;
}
const hasConflict = capabilities.firstMatch.some(
(match) => key in match && !deepEqual(match[key], alwaysVal)
);
if (hasConflict) {
delete capabilities.alwaysMatch[key];
capabilities.firstMatch.forEach((match) => {
if (!(key in match)) {
match[key] = alwaysVal;
}
});
} else {
capabilities.firstMatch.forEach((match) => {
if (key in match) {
delete match[key];
}
});
}
}
}
const sessionRequest = new environment.value.Request(
"POST",
"/session",
{ capabilities }
);
let response;
try {
response = await sessionRequest.makeRequest(params);
} catch (err) {
log2.error(err);
const message = getSessionError(err, params);
throw new Error(message);
}
const sessionId = response.value.sessionId || response.sessionId;
params.capabilities = response.value.capabilities || response.value;
return { sessionId, capabilities: params.capabilities };
}
function validateCapabilities(capabilities) {
var _a;
const chromeArgs = ((_a = capabilities["goog:chromeOptions"]) == null ? void 0 : _a.args) || [];
if (chromeArgs.includes("incognito") || chromeArgs.includes("--incognito")) {
throw new Error(
'Please remove "incognito" from `"goog:chromeOptions".args` as it is not supported running Chrome with WebDriver. WebDriver sessions are always incognito mode and do not persist across browser sessions.'
);
}
if (capabilities) {
const extensionCaps = Object.keys(capabilities).filter((cap) => cap.includes(":"));
const invalidWebDriverCaps = Object.keys(capabilities).filter((cap) => !CAPABILITY_KEYS.includes(cap) && !cap.includes(":"));
if (extensionCaps.length && invalidWebDriverCaps.length) {
throw new Error(
'Invalid or unsupported WebDriver capabilities found ("'.concat(invalidWebDriverCaps.join('", "'), '"). ') + 'Ensure to only use valid W3C WebDriver capabilities (see https://w3c.github.io/webdriver/#capabilities).If you run your tests on a remote vendor, like Sauce Labs or BrowserStack, make sure that you put them into vendor specific capabilities, e.g. "sauce:options" or "bstack:options". Please reach out to your vendor support team if you have further questions.'
);
}
}
}
function isSuccessfulResponse(statusCode, body) {
if (!body || typeof body !== "object" || !("value" in body) || typeof body.value === "undefined") {
log2.debug("request failed due to missing body");
return false;
}
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
body.value.message === "An element could not be located on the page using the given search parameters." || // Internet Explorer
body.value.message.toLowerCase().startsWith("unable to find element"))) {
return true;
}
if ("status" in body && body.status && body.status !== 0) {
log2.debug("request failed due to status ".concat(body.status));
return false;
}
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);
if (statusCode === 200 && !hasErrorResponse) {
return true;
}
if (statusCode === 404 && typeof body.value === "object" && body.value && "error" in body.value && body.value.error === "no such element") {
return true;
}
if (hasErrorResponse) {
const errMsg = typeof body.value === "object" && body.value && "error" in body.value ? body.value.error : body.value;
log2.debug("request failed due to response error:", errMsg);
return false;
}
return true;
}
function getPrototype({ isW3C, isChromium, isFirefox, isMobile, isSauce, isSeleniumStandalone }) {
const prototype = {};
const ProtocolCommands = deepmerge(
/**
* if mobile apply JSONWire and WebDriver protocol because
* some legacy JSONWire commands are still used in Appium
* (e.g. set/get geolocation)
*/
isMobile ? deepmerge(AppiumProtocol, WebDriverProtocol) : WebDriverProtocol,
/**
* enable Bidi protocol for W3C sessions
*/
isW3C ? WebDriverBidiProtocol : {},
/**
* only apply mobile protocol if session is actually for mobile
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
isMobile ? deepmerge(MJsonWProtocol, AppiumProtocol) : {},
/**
* only apply special Chromium commands if session is using Chrome or Edge
*/
isChromium ? ChromiumProtocol : {},
/**
* only apply special Firefox commands if session is using Firefox
*/
isFirefox ? GeckoProtocol : {},
/**
* only Sauce Labs specific vendor commands
*/
isSauce ? SauceLabsProtocol : {},
/**
* only apply special commands when running tests using
* Selenium Grid or Selenium Standalone server
*/
isSeleniumStandalone ? SeleniumProtocol : {},
{}
);
for (const [endpoint, methods] of Object.entries(ProtocolCommands)) {
for (const [method, commandData] of Object.entries(methods)) {
prototype[commandData.command] = { value: command_default(method, endpoint, commandData, isSeleniumStandalone) };
}
}
return prototype;
}
function getEnvironmentVars({ isW3C, isMobile, isIOS, isAndroid, isFirefox, isSauce, isSeleniumStandalone, isChromium, isWindowsApp, isMacApp }) {
return {
isW3C: { value: isW3C },
isMobile: { value: isMobile },
isIOS: { value: isIOS },
isAndroid: { value: isAndroid },
isFirefox: { value: isFirefox },
isSauce: { value: isSauce },
isSeleniumStandalone: { value: isSeleniumStandalone },
isBidi: {
/**
* Return the value of this flag dynamically based on whether the
* BidiHandler was able to connect to the `webSocketUrl` url provided
* by the session response.
*/
get: function() {
var _a;
return Boolean((_a = this._bidiHandler) == null ? void 0 : _a.isConnected);
}
},
isChromium: { value: isChromium },
isWindowsApp: { value: isWindowsApp },
isMacApp: { value: isMacApp }
};
}
function setupDirectConnect(client) {
const capabilities = client.capabilities;
const directConnectProtocol = capabilities["appium:directConnectProtocol"];
const directConnectHost = capabilities["appium:directConnectHost"];
const directConnectPath = capabilities["appium:directConnectPath"];
const directConnectPort = capabilities["appium:directConnectPort"];
if (directConnectProtocol && directConnectHost && directConnectPort && (directConnectPath || directConnectPath === "")) {
log2.info("Found direct connect information in new session response. " + "Will connect to server at ".concat(directConnectProtocol, "://") + "".concat(directConnectHost, ":").concat(directConnectPort).concat(directConnectPath));
client.options.protocol = directConnectProtocol;
client.options.hostname = directConnectHost;
client.options.port = directConnectPort;
client.options.path = directConnectPath;
}
}
var getSessionError = (err, params = {}) => {
var _a;
if (err.code === "ECONNREFUSED") {
return 'Unable to connect to "'.concat(params.protocol, "://").concat(params.hostname, ":").concat(params.port).concat(params.path, '", make sure browser driver is running on that address.') + "\nIt seems like the service failed to start or is rejecting any connections.";
}
if (err.message === "unhandled request") {
return 'The browser driver couldn\'t start the session. Make sure you have set the "path" correctly!';
}
if (!err.message) {
return "See wdio.* logs for more information.";
}
if (err.message.includes("Whoops! The URL specified routes to this help page.")) {
return "It seems you are running a Selenium Standalone server and point to a wrong path. Please set `path: '/wd/hub'` in your wdio.conf.js!";
}
if (BROWSER_DRIVER_ERRORS.some((m) => err && err.message && err.message.includes(m))) {
return "Make sure to set `path: '/'` in your wdio.conf.js!";
}
if (err.message.includes("Bad Request - Invalid Hostname") && err.message.includes("HTTP Error 400")) {
return "Run edge driver on 127.0.0.1 instead of localhost, ex: --host=127.0.0.1, or set `hostname: 'localhost'` in your wdio.conf.js";
}
const w3cCapMessage = '\nMake sure to add vendor prefix like "goog:", "appium:", "moz:", etc to non W3C capabilities.\nSee more https://www.w3.org/TR/webdriver/#capabilities';
if (err.message.includes("Illegal key values seen in w3c capabilities")) {
return err.message + w3cCapMessage;
}
if (err.message === "Response has empty body") {
return "Make sure to connect to valid hostname:port or the port is not in use.\nIf you use a grid server " + w3cCapMessage;
}
if (err.message.includes("failed serving request POST /wd/hub/session: Unauthorized") && (params.hostname === "saucelabs.com" || ((_a = params.hostname) == null ? void 0 : _a.endsWith(".saucelabs.com")))) {
return "Session request was not authorized because you either did provide a wrong access key or tried to run in a region that has not been enabled for your user. If have registered a free trial account it is connected to a specific region. Ensure this region is set in your configuration (https://webdriver.io/docs/options.html#region).";
}
return err.message;
};
function initiateBidi(socketUrl, strictSSL = true, userHeaders) {
const isUnitTesting = environment.value.variables.WDIO_UNIT_TESTS;
if (isUnitTesting) {
log2.info("Skip connecting to WebDriver Bidi interface due to unit tests");
return {
_bidiHandler: {
value: {
isConnected: true,
waitForConnected: () => Promise.resolve(),
socket: { on: () => {
}, off: () => {
} }
}
}
};
}
socketUrl = socketUrl.replace("localhost", "127.0.0.1");
const bidiReqOpts = strictSSL ? {} : { rejectUnauthorized: false };
if (userHeaders) {
bidiReqOpts.headers = userHeaders;
}
const handler = new BidiHandler(socketUrl, bidiReqOpts);
handler.connect().then((isConnected) => isConnected && log2.info("Connected to WebDriver Bidi interface at ".concat(socketUrl)));
return {
_bidiHandler: { value: handler },
...Object.values(WebDriverBidiProtocol).map((def) => def.socket).reduce((acc, cur) => {
acc[cur.command] = {
value: function(...args) {
const bidiFn = handler[cur.command];
handler.attachClient(this);
this.emit(cur.command, args);
return bidiFn == null ? void 0 : bidiFn.apply(handler, args);
}
};
return acc;
}, {})
};
}
function parseBidiMessage(data) {
try {
const payload = JSON.parse(data.toString());
if (payload.type !== "event") {
return;
}
this.emit(payload.method, payload.params);
} catch (err) {
log2.error("Failed parse WebDriver Bidi message: ".concat(err.message));
}
}
function mask(commandInfo, options, body, args) {
var _a, _b, _c, _d;
const unmaskedResult = { maskedBody: body, maskedArgs: args, isMasked: false };
if (!options.mask) {
return unmaskedResult;
}
const textValueParamIndex = commandInfo.parameters.findIndex((param) => param.name === "text");
if (textValueParamIndex === -1) {
return unmaskedResult;
}
const textValueIndexInArgs = ((_b = (_a = commandInfo.variables) == null ? void 0 : _a.length) != null ? _b : 0) + textValueParamIndex;
const text = args[textValueIndexInArgs];
if (typeof text !== "string" || !text) {
return unmaskedResult;
}
const maskedBody = {
...body,
text: SENSITIVE_DATA_REPLACER
};
const textValueArgsIndex = textValueParamIndex + ((_d = (_c = commandInfo.variables) == null ? void 0 : _c.length) != null ? _d : 0);
const maskedArgs = args.slice(0, textValueArgsIndex).concat(SENSITIVE_DATA_REPLACER).concat(args.slice(textValueArgsIndex + 1));
return {
maskedBody,
maskedArgs,
isMasked: true
};
}
// src/command.ts
var log3 = logger3("webdriver");
var BIDI_COMMANDS = Object.values(WebDriverBidiProtocol2).map((def) => def.socket.command);
var sessionAbortListeners = /* @__PURE__ */ new Map();
function command_default(method, endpointUri, commandInfo, doubleEncodeVariables = false) {
const { command, deprecated, ref, parameters, variables = [], isHubCommand = false } = commandInfo;
return async function protocolCommand(...unmaskedArgs) {
let runtimeOptions = {};
if (unmaskedArgs.length > 0 && unmaskedArgs[unmaskedArgs.length - 1] instanceof CommandRuntimeOptions) {
runtimeOptions = unmaskedArgs.pop();
}
const isBidiCommand = BIDI_COMMANDS.includes(command);
let endpoint = endpointUri;
const commandParams = [...variables.map((v) => Object.assign(v, {
/**
* url variables are:
*/
required: true,
// always required as they are part of the endpoint
type: "string"
// have to be always type of string
})), ...parameters];
const commandUsage = "".concat(command, "(").concat(commandParams.map((p) => p.name).join(", "), ")");
const moreInfo = "\n\nFor more info see ".concat(ref, "\n");
const DISABLE_WEBDRIVERIO_DEPRECATION_WARNINGS = globalThis.process && globalThis.process.env ? globalThis.process.env.DISABLE_WEBDRIVERIO_DEPRECATION_WARNINGS : void 0;
if (typeof deprecated === "string" && !DISABLE_WEBDRIVERIO_DEPRECATION_WARNINGS) {
const warning = deprecated.replace("This command", 'The "'.concat(command, '" command'));
log3.warn(warning);
console.warn("\u26A0\uFE0F [WEBDRIVERIO DEPRECATION NOTICE] ".concat(warning));
}
if (isBidiCommand) {
throw new Error(
'Failed to execute WebDriver Bidi command "'.concat(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.'
);
}
const minAllowedParams = commandParams.filter((param) => param.required).length;
if (unmaskedArgs.length < minAllowedParams || unmaskedArgs.length > commandParams.length) {
const parameterDescription = commandParams.length ? "\n\nProperty Description:\n".concat(commandParams.map((p) => ' "'.concat(p.name, '" (').concat(p.type, "): ").concat(p.description)).join("\n")) : "";
throw new Error(
"Wrong parameters applied for ".concat(command, "\n") + "Usage: ".concat(commandUsage) + parameterDescription + moreInfo
);
}
const unmaskedBody = {};
for (const [it, arg] of Object.entries(unmaskedArgs)) {
if (isBidiCommand) {
break;
}
const i = parseInt(it, 10);
const commandParam = commandParams[i];
if (!isValidParameter(arg, commandParam.type)) {
if (typeof arg === "undefined" && !commandParam.required) {
continue;
}
const actual = commandParam.type.endsWith("[]") ? "(".concat((Array.isArray(arg) ? arg : [arg]).map((a) => getArgumentType(a)), ")[]") : getArgumentType(arg);
throw new Error(
'Malformed type for "'.concat(commandParam.name, '" parameter of command ').concat(command, "\n") + "Expected: ".concat(commandParam.type, "\n") + "Actual: ".concat(actual) + moreInfo
);
}
if (i < variables.length) {
const encodedArg = doubleEncodeVariables ? encodeURIComponent(encodeURIComponent(arg)) : encodeURIComponent(arg);
endpoint = endpoint.replace(":".concat(commandParams[i].name), encodedArg);
continue;
}
unmaskedBody[commandParams[i].name] = arg;
}
const { maskedBody, maskedArgs, isMasked } = mask(commandInfo, runtimeOptions, unmaskedBody, unmaskedArgs);
const { isAborted, abortSignal, cleanup } = manageSessionAbortions.call(this);
const requiresSession = endpointUri.includes("/:sessionId/");
if (isAborted && command !== "deleteSession" && requiresSession) {
throw new Error('Trying to run command "'.concat(commandCallStructure(command, maskedArgs), '" after session has been deleted, aborting request without executing it'));
}
const request = new environment.value.Request(method, endpoint, unmaskedBody, abortSignal, isHubCommand, {
onPerformance: (data) => this.emit("request.performance", { ...data, request: {
...data.request,
body: isMasked ? maskedBody : data.request.body
} }),
onRequest: (data) => this.emit("request.start", { ...data, body: isMasked ? maskedBody : data.body }),
onResponse: (data) => this.emit("request.end", data),
onRetry: (data) => this.emit("request.retry", data),
onLogData: (data) => log3.info("DATA", transformCommandLogResult(isMasked ? maskedBody : data))
});
this.emit("command", { command, method, endpoint, body: maskedBody });
log3.info("COMMAND", commandCallStructure(command, maskedArgs));
const options = isMasked ? { ...this.options, headers: { ...this.options.headers, ...APPIUM_MASKING_HEADER } } : this.options;
return request.makeRequest(options, this.sessionId).then((result) => {
var _a, _b;
if (typeof result.value !== "undefined") {
let resultLog = result.value;
if (/screenshot|recording/i.test(command) && typeof result.value === "string" && result.value.length > 64) {
resultLog = "".concat(result.value.slice(0, 61), "...");
} else if (command === "executeScript" && typeof maskedBody.script === "string" && maskedBody.script.includes("(() => window.__wdioEvents__)")) {
resultLog = "[".concat(result.value.length, " framework events captured]");
}
log3.info("RESULT", resultLog);
}
this.emit("result", { command, method, endpoint, body: maskedBody, result });
if (command === "deleteSession") {
const browser = this;
(_a = browser._bidiHandler) == null ? void 0 : _a.close();
const shutdownDriver = ((_b = maskedBody.deleteSessionOpts) == null ? void 0 : _b.shutdownDriver) !== false;
environment.value.killDriverProcess(this.capabilities, shutdownDriver);
if (!environment.value.variables.WDIO_WORKER_ID) {
logger3.clearLogger();
}
}
return result.value;
}).catch((error) => {
this.emit("result", { command, method, endpoint, body: maskedBody, result: { error } });
throw error;
}).finally(() => {
cleanup();
});
};
}
function manageSessionAbortions() {
const abort = new AbortController();
const abortOnSessionEnd = (result) => {
if (result.command !== "deleteSession") {
return;
}
const abortListeners = sessionAbortListeners.get(this.sessionId);
if (abortListeners) {
for (const abortListener of abortListeners) {
abortListener.abort();
}
abortListeners.clear();
sessionAbortListeners.set(this.sessionId, null);
}
};
let abortListenerForCurrentSession = sessionAbortListeners.get(this.sessionId);
if (typeof abortListenerForCurrentSession === "undefined") {
abortListenerForCurrentSession = /* @__PURE__ */ new Set();
sessionAbortListeners.set(this.sessionId, abortListenerForCurrentSession);
this.on("result", abortOnSessionEnd);
}
if (abortListenerForCurrentSession === null) {
return { isAborted: true, abortSignal: void 0, cleanup: () => {
} };
}
abortListenerForCurrentSession.add(abort);
return {
isAborted: false,
abortSignal: abort.signal,
cleanup: () => {
this.off("result", abortOnSessionEnd);
abortListenerForCurrentSession == null ? void 0 : abortListenerForCurrentSession.delete(abort);
}
};
}
// src/bidi/localTypes.ts
var localTypes_exports = {};
// src/bidi/remoteTypes.ts
var remoteTypes_exports = {};
// src/index.ts
var log4 = logger4("webdriver");
var WebDriver = class _WebDriver {
static async newSession(options, modifier, userPrototype = {}, customCommandWrapper, implicitWaitExclusionList = []) {
var _a;
const envLogLevel = environment.value.variables.WDIO_LOG_LEVEL;
options.logLevel = envLogLevel != null ? envLogLevel : options.logLevel;
const params = validateConfig(DEFAULTS, options);
if (params.logLevel && (!options.logLevels || !options.logLevels.webdriver)) {
logger4.setLevel("webdriver", params.logLevel);
}
log4.info("Initiate new session using the WebDriver protocol");
const driverProcess = await startWebDriver(params);
const requestedCapabilities = { ...params.capabilities };
const { sessionId, capabilities } = await startWebDriverSession(params);
const environment2 = sessionEnvironmentDetector({ capabilities, requestedCapabilities });
const environmentPrototype = getEnvironmentVars(environment2);
const protocolCommands = getPrototype(environment2);
if (driverProcess == null ? void 0 : driverProcess.pid) {
capabilities["wdio:driverPID"] = driverProcess.pid;
}
const bidiPrototype = {};
if (isBidi(capabilities)) {
log4.info("Register BiDi handler for session with id ".concat(sessionId));
Object.assign(bidiPrototype, initiateBidi(
capabilities.webSocketUrl,
options.strictSSL,
options.headers
));
}
const monad = webdriverMonad(
{ ...params, requestedCapabilities },
modifier,
{
...protocolCommands,
...environmentPrototype,
...userPrototype,
...bidiPrototype
}
);
const client = monad(sessionId, customCommandWrapper, implicitWaitExclusionList);
if (isBidi(capabilities)) {
if (await client._bidiHandler.waitForConnected()) {
(_a = client._bidiHandler.socket) == null ? void 0 : _a.on("message", parseBidiMessage.bind(client));
}
}
if (params.enableDirectConnect) {
setupDirectConnect(client);
}
return client;
}
/**
* allows user to attach to existing sessions
*/
static attachToSession(options, modifier, userPrototype = {}, commandWrapper) {
var _a, _b;
if (!options || typeof options.sessionId !== "string") {
throw new Error("sessionId is required to attach to existing session");
}
if (options.logLevel) {
logger4.setLevel("webdriver", options.logLevel);
}
options.capabilities = options.capabilities || {};
options.isW3C = options.isW3C === false ? false : true;
options.protocol = options.protocol || DEFAULTS.protocol.default;
options.hostname = options.hostname || DEFAULTS.hostname.default;
options.port = options.port || DEFAULTS.port.default;
options.path = options.path || DEFAULTS.path.default;
const environment2 = sessionEnvironmentDetector({ capabilities: options.capabilities, requestedCapabilities: options.capabilities });
options = Object.assign(environment2, options);
const environmentPrototype = getEnvironmentVars(options);
const protocolCommands = getPrototype(options);
const bidiPrototype = {};
if (isBidi(options.capabilities || {})) {
const webSocketUrl = (_a = options.capabilities) == null ? void 0 : _a.webSocketUrl;
log4.info("Register BiDi handler for session with id ".concat(options.sessionId));
Object.assign(bidiPrototype, initiateBidi(
webSocketUrl,
options.strictSSL,
options.headers
));
}
const prototype = { ...protocolCommands, ...environmentPrototype, ...userPrototype, ...bidiPrototype };
const monad = webdriverMonad(options, modifier, prototype);
const client = monad(options.sessionId, commandWrapper);
if (isBidi(options.capabilities || {})) {
(_b = client._bidiHandler) == null ? void 0 : _b.waitForConnected().then(() => {
var _a2;
(_a2 = client._bidiHandler) == null ? void 0 : _a2.socket.on("message", parseBidiMessage.bind(client));
});
}
return client;
}
/**
* Changes The instance session id and browser capabilities for the new session
* directly into the passed in browser object
*
* @param {object} instance the object we get from a new browser session.
* @returns {string} the new session id of the browser
*/
static async reloadSession(instance, newCapabilities) {
var _a, _b, _c;
const capabilities = newCapabilities ? newCapabilities : Object.assign({}, instance.requestedCapabilities);
let params = { ...instance.options, capabilities };
for (const prop of ["protocol", "hostname", "port", "path", "queryParams", "user", "key"]) {
if (prop in capabilities) {
params = { ...params, [prop]: capabilities[prop] };
delete capabilities[prop];
}
}
let driverProcess;
if (params.hostname === "localhost" && (newCapabilities == null ? void 0 : newCapabilities.browserName)) {
delete params.port;
delete params.hostname;
driverProcess = await startWebDriver(params);
}
const { sessionId, capabilities: newSessionCapabilities } = await startWebDriverSession(params);
if (driverProcess == null ? void 0 : driverProcess.pid) {
newSessionCapabilities["wdio:driverPID"] = driverProcess.pid;
}
for (const prop of ["protocol", "hostname", "port", "path", "queryParams", "user", "key"]) {
if (prop in params) {
instance.options[prop] = params[prop];
}
}
for (const prop in instance.requestedCapabilities) {
delete instance.requestedCapabilities[prop];
}
const driverPid = instance.capabilities["wdio:driverPID"];
instance.sessionId = sessionId;
instance.capabilities = newSessionCapabilities;
instance.capabilities["wdio:driverPID"] = driverPid;
Object.assign(instance.requestedCapabilities, capabilities);
if (isBidi(instance.capabilities || {})) {
const bidiReqOpts = instance.options.strictSSL ? {} : { rejectUnauthorized: false };
await ((_a = instance._bidiHandler) == null ? void 0 : _a.reconnect(newSessionCapabilities.webSocketUrl, bidiReqOpts));
(_c = (_b = instance._bidiHandler) == null ? void 0 : _b.socket) == null ? void 0 : _c.on("message", parseBidiMessage.bind(instance));
}
return sessionId;
}
static get WebDriver() {
return _WebDriver;
}
};
// src/bidi/socket.ts
var _callbacks, _ws2;
var BrowserSocket = class {
constructor(wsUrl, _opts) {
__privateAdd(this, _callbacks, /* @__PURE__ */ new Set());
__privateAdd(this, _ws2);
__privateSet(this, _ws2, new globalThis.WebSocket(wsUrl));
__privateGet(this, _ws2).onmessage = this.handleMessage.bind(this);
}
handleMessage(event) {
for (const callback of __privateGet(this, _callbacks)) {
callback(event.data);
}
}
send(data) {
__privateGet(this, _ws2).send(data);
}
on(event, callback) {
if (event === "open") {
__privateGet(this, _ws2).onopen = callback;
} else if (event === "close") {
__privateGet(this, _ws2).onclose = callback;
} else if (event === "error") {
__privateGet(this, _ws2).onerror = callback;
} else {
__privateGet(this, _callbacks).add(callback);
}
return this;
}
off(_event, callback) {
__privateGet(this, _callbacks).delete(callback);
return this;
}
close() {
__privateGet(this, _ws2).close();
}
};
_callbacks = new WeakMap();
_ws2 = new WeakMap();
// src/request/request.ts
import logger5 from "@wdio/logger";
import { sleep } from "@wdio/utils";
// src/request/error.ts
import { transformCommandLogResult as transformCommandLogResult2 } from "@wdio/utils";
// src/request/constants.ts
var RETRYABLE_STATUS_CODES = [408, 413, 429, 500, 502, 503, 504];
var RETRYABLE_ERROR_CODES = [
"ETIMEDOUT",
"ECONNRESET",
"EADDRINUSE",
"ECONNREFUSED",
"EPIPE",
"ENOTFOUND",
"ENETUNREACH",
"EAI_AGAIN",
// additional error codes we like to retry
"UND_ERR_CONNECT_TIMEOUT",
"UND_ERR_SOCKET"
];
var REG_EXPS = {
commandName: /.*\/session\/[0-9a-f-]+\/(.*)/,
execFn: /return \(([\s\S]*)\)\.apply\(null, arguments\)/
};
// src/request/error.ts
var _WebDriverError_instances, getExecCmdName_fn, getExecCmdArgs_fn;
var WebDriverError = class extends Error {
constructor() {
super(...arguments);
__privateAdd(this, _WebDriverError_instances);
}
/**
* return timeout error with information about the executing command on which the test hangs
*/
computeErrorMessage() {
const cmdName = __privateMethod(this, _WebDriverError_instances, getExecCmdName_fn).call(this);
const cmdArgs = __privateMethod(this, _WebDriverError_instances, getExecCmdArgs_fn).call(this, this.opts);
const cmdInfoMsg = 'when running "'.concat(cmdName, '" with method "').concat(this.opts.method, '"');
const cmdArgsMsg = cmdArgs ? " and args ".concat(cmdArgs) : "";
return "WebDriverError: ".concat(this.message, " ").concat(cmdInfoMsg).concat(cmdArgsMsg);
}
};
_WebDriverError_instances = new WeakSet();
getExecCmdName_fn = function() {
const { href } = this.url;
const res = href.match(REG_EXPS.commandName) || [];
return res[1] || href;
};
getExecCmdArgs_fn = function(requestOptions) {
const { body: cmdJson } = requestOptions;
if (typeof cmdJson !== "object") {
return "";
}
const transformedRes = transformCommandLogResult2(cmdJson);
if (typeof transformedRes === "string") {
return transformedRes;
}
if (typeof cmdJson.script === "string") {
const scriptRes = cmdJson.script.match(REG_EXPS.execFn) || [];
return '"'.concat(scriptRes[1] || cmdJson.script, '"');
}
return Object.keys(cmdJson).length ? '"'.concat(JSON.stringify(cmdJson), '"') : "";
};
var WebDriverRequestError = class extends WebDriverError {
constructor(err, url, opts) {
let message = err.message;
if (err.message === "fetch failed") {
message = "Failed to fetch [".concat(opts.method, "] ").concat(url.href, ": please make sure you have a WebDriver compatible server running on ").concat(url.origin);
}
super(message);
__publicField(this, "url");
__publicField(this, "opts");
__publicField(this, "statusCode");
__publicField(this, "body");
__publicField(this, "code");
this.url = url;
this.opts = opts;
const errorCode = typeof err.cause === "object" && err.cause && "code" in err.cause && typeof err.cause.code === "string" ? err.cause.code : "code" in err && typeof err.code === "string" ? err.code : void 0;
if (errorCode) {
this.code = errorCode;
this.message = errorCode === "UND_ERR_CONNECT_TIMEOUT" ? 'Request timed out! Consider increasing the "connectionRetryTimeout" option.' : "Request failed with error code " + errorCode;
}
if (typeof err.cause === "object" && err.cause) {
this.statusCode = "statusCode" in err.cause && typeof err.cause.statusCode === "number" ? err.cause.statusCode : void 0;
this.body = "body" in err.cause ? err.cause.body : void 0;
}
this.message = this.computeErrorMessage();
}
};
var WebDriverResponseError = class _WebDriverResponseError extends WebDriverError {
constructor(response, url, opts) {
const errorObj = !response || typeof response !== "object" || !("body" in response) || !response.body ? new Error("Response has empty body") : typeof response.body === "string" && response.body.length ? new Error(response.body) : typeof response.body !== "object" ? new Error("Unknown error") : "value" in response.body && response.body.value ? response.body.value : response.body;
let errorMessage = errorObj.message || errorObj.error || errorObj.class || "unknown error";
if (typeof errorMessage === "string" && errorMessage.includes("invalid locator")) {
const requestOptions = opts.body;
errorMessage = 'The selector "'.concat(requestOptions.value, '" used with strategy "').concat(requestOptions.using, '" is invalid!');
}
super(errorMessage);
__publicField(this, "url");
__publicField(this, "opts");
if (errorObj.error) {
this.name = errorObj.error;
} else if (errorMessage && errorMessage.includes("stale element reference")) {
this.name = "stale element reference";
} else {
this.name = errorObj.name || "WebDriver Error";
}
Error.captureStackTrace(this, _WebDriverResponseError);
this.url = url;
this.opts = opts;
this.message = this.computeErrorMessage();
}
};
// package.json
var package_default = {
name: "webdriver",
version: "9.24.0",
description: "A Node.js bindings implementation for the W3C WebDriver and Mobile JSONWire Protocol",
author: "Christian Bromann <mail@bromann.dev>",
homepage: "https://github.com/webdriverio/webdriverio/tree/main/packages/webdriver",
license: "MIT",
type: "module",
main: "./build/index.cjs",
module: "./build/index.js",
exports: {
".": {
types: "./build/index.d.ts",
browserSource: "./src/browser.js",
browser: "./build/index.js",
importSource: "./src/node.ts",
import: "./build/node.js",
requireSource: "./src/index.cts",
require: "./build/index.cjs"
}
},
types: "./build/index.d.ts",
typeScriptVersion: "3.8.3",
engines: {
node: ">=18.20.0"
},
repository: {
type: "git",
url: "git+https://github.com/webdriverio/webdriverio.git",
directory: "packages/webdriver"
},
keywords: [
"webdriver"
],
bugs: {
url: "https://github.com/webdriverio/webdriverio/issues"
},
dependencies: {
"@types/node": "^20.1.0",
"@types/ws": "^8.5.3",
"@wdio/config": "workspace:*",
"@wdio/logger": "workspace:*",
"@wdio/protocols": "workspace:*",
"@wdio/types": "workspace:*",
"@wdio/utils": "workspace:*",
"deepmerge-ts": "^7.0.3",
"https-proxy-agent": "^7.0.6",
undici: "^6.21.3",
ws: "^8.8.0"
}
};
// src/request/polyfill.ts
if (!AbortSignal.any) {
AbortSignal.any = function(signals) {
if (!signals || !Array.isArray(signals)) {
throw new TypeError("AbortSignal.any requires an array of AbortSignal objects");
}
const controller = new AbortController();
if (signals.some((signal) => signal.aborted)) {
controller.abort();
return controller.signal;
}
const listeners = signals.map((signal) => {
const listener = () => {
if ("reason" in signal && signal.reason !== void 0) {
controller.abort(signal.reason);
} else {
controller.abort();
}
cleanup();
};
signal.addEventListener("abort", listener);
return { signal, listener };
});
const cleanup = () => {
listeners.forEach(({ signal, listener }) => {
signal.removeEventListener("abort", listener);
});
};
controller.signal.addEventListener("abort", cleanup);
return controller.signal;
};
}
// src/request/request.ts
var ERRORS_TO_EXCLUDE_FROM_RETRY = [
"detached shadow root",
"move target out of bounds"
];
var DEFAULT_HEADERS = {
"Content-Type": "application/json; charset=utf-8",
"Connection": "keep-alive",
"Accept": "application/json",
"User-Agent": "webdriver/" + package_default.version
};
var log5 = logger5("webdriver");
var WebDriverRequest = class {
constructor(method, endpoint, body, abortSignal, isHubCommand = false, eventHandler = {}) {
__publicField(this, "body");
__publicField(this, "method");
__publicField(this, "endpoint");
__publicField(this, "isHubCommand");
__publicField(this, "requiresSessionId");
__publicField(this, "eventHandler");
__publicField(this, "abortSignal");
this.body = body;
this.method = method;
this.endpoint = endpoint;
this.isHubCommand = isHubCommand;
this.requiresSessionId = Boolean(this.endpoint.match(/:sessionId/));
this.eventHandler = eventHandler;
this.abortSignal = abortSignal;
}
async makeRequest(options, sessionId) {
var _a, _b;
const { url, requestOptions } = await this.createOptions(options, sessionId);
(_b = (_a = this.eventHandler).onRequest) == null ? void 0 : _b.call(_a, requestOptions);
return this._request(url, requestOptions, options.transformResponse, options.connectionRetryCount, 0);
}
async createOptions(options, sessionId, isBrowser = false) {
const timeout = options.connectionRetryTimeout || DEFAULTS.connectionRetryTimeout.default;
const requestOptions = {
method: this.method,
redirect: "follow",
signal: AbortSignal.any([
AbortSignal.timeout(timeout),
...this.abortSignal ? [this.abortSignal] : []
])
};
const requestHeaders = new Headers({
...DEFAULT_HEADERS,
...typeof options.headers === "object" ? options.headers : {}
});
const searchParams = isBrowser ? void 0 : typeof options.queryParams === "object" ? options.queryParams : void 0;
if (this.body && (Object.keys(this.body).length || this.method === "POST")) {
requestOptions.body = JSON.stringify(this.body);
const contentLength = new TextEncoder().encode(requestOptions.body).length;
requestHeaders.set("Content-Length", "".concat(contentLength));
}
let endpoint = this.endpoint;
if (this.requiresSessionId) {
if (!sessionId) {
throw new Error("A sessionId is required for this command");
}
endpoint = endpoint.replace(":sessionId", sessionId);
}
const url = new URL("".concat(options.protocol, "://").concat(options.hostname, ":").concat(options.port).concat(this.isHubCommand ? this.endpoint : "".concat(options.path || "", "/").concat(endpoint).replace(/(\/){2,}/g, "/")));
if (searchParams) {
url.search = new URLSearchParams(searchParams).toString();
}
if (this.endpoint === "/session" && options.user && options.key) {
requestHeaders.set("Authorization", "Basic " + btoa(options.user + ":" + options.key));
}
requestOptions.headers = requestHeaders;
return {
url,
requestOptions: typeof options.transformRequest === "function" ? options.transformRequest(requestOptions) : requestOptions
};
}
async _libRequest(url, opts) {
try {
const response = await this.fetch(url, opts);
return await this.parseResponse(response);
} catch (err) {
if (!(err instanceof Error)) {
throw new WebDriverRequestError(
new Error("Failed to fetch ".concat(url.href, ": ").concat(err.message || err || "Unknown error")),
url,
opts
);
}
throw new WebDriverRequestError(err, url, opts);
}
}
async parseResponse(response) {
const rawBody = await response.text();
try {
return {
statusCode: response.status,
body: rawBody ? JSON.parse(rawBody) : {}
};
} catch {
throw new Error('Could not parse response body: "'.concat(rawBody, '"'), {
cause: {
statusCode: response.status,
body: rawBody
}
});
}
}
async _request(url, fullRequestOptions, transformResponse, totalRetryCount = 0, retryCount = 0) {
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
log5.info("[".concat(fullRequestOptions.method, "] ").concat(url.href));
if (fullRequestOptions.body && Object.keys(fullRequestOptions.body).length) {
(_b = (_a = this.eventHandler).onLogData) == null ? void 0 : _b.call(_a, fullRequestOptions.body);
}
const { ...requestLibOptions } = fullRequestOptions;
const startTime = performance.now();
let response = await this._libRequest(url, requestLibOptions).catch((err) => err);
const durationMillisecond = performance.now() - startTime;
const retry = async (error2) => {
var _a2, _b2, _c2, _d2, _e2, _f2, _g2, _h2;
if (retryCount >= totalRetryCount || error2.message.includes("invalid session id")) {
log5.error(error2.message);
(_b2 = (_a2 = this.eventHandler).onResponse) == null ? void 0 : _b2.call(_a2, { error: error2 });
(_d2 = (_c2 = this.eventHandler).onPerformance) == null ? void 0 : _d2.call(_c2, { request: fullRequestOptions, durationMillisecond, success: false, error: error2, retryCount });
throw error2;
}
if (retryCount > 0) {
await sleep(Math.min(1e4, 250 * Math.pow(2, retryCount)));
}
++retryCount;
(_f2 = (_e2 = this.eventHandler).onRetry) == null ? void 0 : _f2.call(_e2, { error: error2, retryCount });
(_h2 = (_g2 = this.eventHandler).onPerformance) == null ? void 0 : _h2.call(_g2, { request: fullRequestOptions, durationMillisecond, success: false, error: error2, retryCount });
log5.warn(error2.message);
log5.info("Retrying ".concat(retryCount, "/").concat(totalRetryCount));
return this._request(url, fullRequestOptions, transformResponse, totalRetryCount, retryCount);
};
if (response instanceof Error) {
const resError = response;
if (!(this.abortSignal && this.abortSignal.aborted) && (resError.code && RETRYABLE_ERROR_CODES.includes(resError.code) || resError.statusCode && RETRYABLE_STATUS_CODES.includes(resError.statusCode))) {
return retry(resError);
}
(_d = (_c = this.eventHandler).onPerformance) == null ? void 0 : _d.call(_c, { request: fullRequestOptions, durationMillisecond, success: false, error: response, retryCount });
throw response;
}
if (typeof transformResponse === "function") {
response = transformResponse(response, fullRequestOptions);
}
if (isSuccessfulResponse(response.statusCode, response.body)) {
(_f = (_e = this.eventHandler).onResponse) == null ? void 0 : _f.call(_e, { result: response.body });
(_h = (_g = this.eventHandler).onPerformance) == null ? void 0 : _h.call(_g, { request: fullRequestOptions, durationMillisecond, success: true, retryCount });
return response.body;
}
const error = new WebDriverResponseError(response, url, fullRequestOptions);
if (this.isHubCommand) {
if (typeof response.body === "string" && response.body.startsWith("<!DOCTYPE html>")) {
(_j = (_i = this.eventHandler).onPerformance) == null ? void 0 : _j.call(_i, { request: fullRequestOptions, durationMillisecond, success: false, error, retryCount });
return Promise.reject(new Error("Command can only be called to a Selenium Hub"));
}
return { value: response.body || null };
}
if (error.name === "stale element reference") {
log5.warn("Request encountered a stale element - terminating request");
(_l = (_k = this.eventHandler).onResponse) == null ? void 0 : _l.call(_k, { error });
(_n = (_m = this.eventHandler).onPerformance) == null ? void 0 : _n.call(_m, { request: fullRequestOptions, durationMillisecond, success: false, error, retryCount });
throw error;
}
if (ERRORS_TO_EXCLUDE_FROM_RETRY.includes(error.name)) {
throw error;
}
return retry(error);
}
};
// src/request/web.ts
var FetchRequest = class extends WebDriverRequest {
fetch(url, opts) {
return fetch(url, opts);
}
};
// src/browser.ts
var browser_default = WebDriver;
var log6 = logger6("webdriver");
environment.value = {
Request: FetchRequest,
Socket: BrowserSocket,
killDriverProcess: () => {
},
createBidiConnection: (webSocketUrl, options) => {
log6.info("Connecting to webSocketUrl ".concat(webSocketUrl));
const ws = new BrowserSocket(webSocketUrl, options);
return new Promise((resolve) => {
ws.on("open", () => {
log6.info("Connected session to Bidi protocol");
resolve(ws);
});
ws.on("error", (err) => {
const error = err instanceof Error ? err : new Error(String(err));
log6.warn("Couldn't connect to Bidi protocol: ".concat(error.message));
resolve(void 0);
});
});
},
variables: {}
};
export {
APPIUM_MASKING_HEADER,
BASE_64_REGEX,
BASE_64_SAFE_STRING_TO_PROCESS_LENGTH,
BidiHandler,
CommandRuntimeOptions,
DEFAULTS,
ELEMENT_KEY,
SHADOW_ELEMENT_KEY,
WebDriver,
command_default as command,
browser_default as default,
getEnvironmentVars,
getPrototype,
initiateBidi,
localTypes_exports as local,
parseBidiMessage,
remoteTypes_exports as remote
};