UNPKG

webdriver

Version:

A Node.js bindings implementation for the W3C WebDriver and Mobile JSONWire Protocol

1,939 lines 78.3 kB
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 logger from "@wdio/logger";
import { commandCallStructure, isValidParameter, getArgumentType } from "@wdio/utils";
import { WebDriverBidiProtocol } 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 variables() {
      return {};
    }
  }
};

// src/command.ts
var log = logger("webdriver");
var BIDI_COMMANDS = Object.values(WebDriverBidiProtocol).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(...args) {
    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 body = {};
    if (typeof deprecated === "string" && !process.env.DISABLE_WEBDRIVERIO_DEPRECATION_WARNINGS) {
      const warning = deprecated.replace("This command", 'The "'.concat(command, '" command'));
      log.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 (args.length < minAllowedParams || args.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
      );
    }
    for (const [it, arg] of Object.entries(args)) {
      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;
      }
      body[commandParams[i].name] = arg;
    }
    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, args), '" after session has been deleted, aborting request without executing it'));
    }
    const request = new environment.value.Request(method, endpoint, body, abortSignal, isHubCommand, {
      onPerformance: (data) => this.emit("request.performance", data),
      onRequest: (data) => this.emit("request.start", data),
      onResponse: (data) => this.emit("request.end", data),
      onRetry: (data) => this.emit("request.retry", data)
    });
    this.emit("command", { command, method, endpoint, body });
    log.info("COMMAND", commandCallStructure(command, args));
    return request.makeRequest(this.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 body.script === "string" && body.script.includes("(() => window.__wdioEvents__)")) {
          resultLog = "[".concat(result.value.length, " framework events captured]");
        }
        log.info("RESULT", resultLog);
      }
      this.emit("result", { command, method, endpoint, body, result });
      if (command === "deleteSession") {
        const browser = this;
        (_a = browser._bidiHandler) == null ? void 0 : _a.close();
        const shutdownDriver = ((_b = body.deleteSessionOpts) == null ? void 0 : _b.shutdownDriver) !== false;
        if (shutdownDriver && "wdio:driverPID" in this.capabilities && this.capabilities["wdio:driverPID"]) {
          log.info("Kill driver process with PID ".concat(this.capabilities["wdio:driverPID"]));
          try {
            const killedSuccessfully = process.kill(this.capabilities["wdio:driverPID"], "SIGKILL");
            if (!killedSuccessfully) {
              log.warn("Failed to kill driver process, manually clean-up might be required");
            }
          } catch (err) {
            log.warn("Failed to kill driver process", err);
          }
          setTimeout(() => {
            for (const handle of process._getActiveHandles()) {
              if (handle.servername && handle.servername.includes("edgedl.me")) {
                handle.destroy();
              }
            }
          }, 10);
        }
        if (!process.env.WDIO_WORKER_ID) {
          logger.clearLogger();
        }
      }
      return result.value;
    }).catch((error) => {
      this.emit("result", { command, method, endpoint, body, 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/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
  }
};
var ELEMENT_KEY = "element-6066-11e4-a52e-4f735466cecf";
var SHADOW_ELEMENT_KEY = "shadow-6066-11e4-a52e-4f735466cecf";

// src/utils.ts
import { deepmergeCustom } from "deepmerge-ts";
import logger3 from "@wdio/logger";
import {
  WebDriverProtocol,
  MJsonWProtocol,
  AppiumProtocol,
  ChromiumProtocol,
  SauceLabsProtocol,
  SeleniumProtocol,
  GeckoProtocol,
  WebDriverBidiProtocol as WebDriverBidiProtocol2
} from "@wdio/protocols";
import { CAPABILITY_KEYS } from "@wdio/protocols";

// src/bidi/core.ts
import logger2 from "@wdio/logger";
var SCRIPT_PREFIX = "/* __wdio script__ */";
var SCRIPT_SUFFIX = "/* __wdio script end__ */";
var base64Regex = /^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$/;
var log2 = logger2("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() {
    log2.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;
    }
    log2.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) {
    log2.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) {
      failError.message += " with error: ".concat(payload.error, " - ").concat(payload.message);
      if (payload.stacktrace && typeof payload.stacktrace === "string") {
        const driverStack = payload.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");
    }
    log2.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" && base64Regex.test(payload.result.data)) {
      resultLog = JSON.stringify({
        ...payload.result,
        data: "Base64 string [".concat(payload.result.data.length, " chars]")
      });
    }
    log2.info("BIDI RESULT", resultLog);
    (_a = this.client) == null ? void 0 : _a.emit("bidiResult", payload);
    const resolve = __privateGet(this, _pendingCommands).get(payload.id);
    if (!resolve) {
      log2.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)));
    log2.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.EmptyResult>`
   **/
  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.SessionSubscriptionRequest` {@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.EmptyParams` {@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.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 "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 "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 "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;
  }
};

// src/utils.ts
var log3 = logger3("webdriver");
var deepmerge = deepmergeCustom({ mergeArrays: false });
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 sessionRequest = new environment.value.Request(
    "POST",
    "/session",
    { capabilities }
  );
  let response;
  try {
    response = await sessionRequest.makeRequest(params);
  } catch (err) {
    log3.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") {
    log3.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) {
    log3.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;
    log3.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 ? WebDriverBidiProtocol2 : {},
    /**
     * 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 === "")) {
    log3.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") && ((_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 = process.env.WDIO_UNIT_TESTS;
  if (isUnitTesting) {
    log3.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 && log3.info("Connected to WebDriver Bidi interface at ".concat(socketUrl)));
  return {
    _bidiHandler: { value: handler },
    ...Object.values(WebDriverBidiProtocol2).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) {
    log3.error("Failed parse WebDriver Bidi message: ".concat(err.message));
  }
}

// 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) {
    var _a;
    const envLogLevel = process.env.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);
    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 { transformCommandLogResult as transformCommandLogResult2, sleep } from "@wdio/utils";

// src/request/error.ts
import { transformCommandLogResult } 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 = transformCommandLogResult(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;
    }
    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.11.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",
    undici: "^6.20.1",
    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")) {
      const contentLength = Buffer.byteLength(JSON.stringify(this.body), "utf8");
      requestOptions.body = this.body;
      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) {
    var _a;
    try {
      const response = await this.fetch(url, {
        method: opts.method,
        body: JSON.stringify(opts.body),
        headers: opts.headers,
        signal: opts.signal,
        redirect: opts.redirect
      });
      const resp = response.clone();
      return {
        statusCode: resp.status,
        body: (_a = await resp.json()) != null ? _a : {}
      };
    } 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 _request(url, fullRequestOptions, transformResponse, totalRetryCount = 0, retryCount = 0) {
    var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
    log5.info("[".concat(fullRequestOptions.method, "] ").concat(url.href));
    if (fullRequestOptions.body && Object.keys(fullRequestOptions.body).length) {
      log5.info("DATA", transformCommandLogResult2(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);
      }
      (_b = (_a = this.eventHandler).onPerformance) == null ? void 0 : _b.call(_a, { request: fullRequestOptions, durationMillisecond, success: false, error: response, retryCount });
      throw response;
    }
    if (typeof transformResponse === "function") {
      response = transformResponse(response, fullRequestOptions);
    }
    if (isSuccessfulResponse(response.statusCode, response.body)) {
      (_d = (_c = this.eventHandler).onResponse) == null ? void 0 : _d.call(_c, { result: response.body });
      (_f = (_e = this.eventHandler).onPerformance) == null ? void 0 : _f.call(_e, { 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>")) {
        (_h = (_g = this.eventHandler).onPerformance) == null ? void 0 : _h.call(_g, { 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");
      (_j = (_i = this.eventHandler).onResponse) == null ? void 0 : _j.call(_i, { error });
      (_l = (_k = this.eventHandler).onPerformance) == null ? void 0 : _l.call(_k, { 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,
  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 {
  BidiHandler,
  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
};