@wdio/utils
Version:
A WDIO helper utility to provide several utility functions used across the project.
1,478 lines (1,463 loc) • 52.1 kB
JavaScript
var __defProp = Object.defineProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
// src/monad.ts
import logger from "@wdio/logger";
import { MESSAGE_TYPES } from "@wdio/types";
import _mitt from "mitt";
// src/utils.ts
import fs from "node:fs/promises";
import url from "node:url";
import path from "node:path";
// src/constants.ts
var UNICODE_CHARACTERS = {
"NULL": "\uE000",
"Unidentified": "\uE000",
"Cancel": "\uE001",
"Help": "\uE002",
"Backspace": "\uE003",
"Back space": "\uE003",
"Tab": "\uE004",
"Clear": "\uE005",
"Return": "\uE006",
"Enter": "\uE007",
"Shift": "\uE008",
"Control": "\uE009",
"Control Left": "\uE009",
"Control Right": "\uE051",
"Alt": "\uE00A",
"Pause": "\uE00B",
"Escape": "\uE00C",
"Space": "\uE00D",
" ": "\uE00D",
"PageUp": "\uE00E",
"Pageup": "\uE00E",
"Page_Up": "\uE00E",
"PageDown": "\uE00F",
"Pagedown": "\uE00F",
"Page_Down": "\uE00F",
"End": "\uE010",
"Home": "\uE011",
"ArrowLeft": "\uE012",
"Left arrow": "\uE012",
"Arrow_Left": "\uE012",
"ArrowUp": "\uE013",
"Up arrow": "\uE013",
"Arrow_Up": "\uE013",
"ArrowRight": "\uE014",
"Right arrow": "\uE014",
"Arrow_Right": "\uE014",
"ArrowDown": "\uE015",
"Down arrow": "\uE015",
"Arrow_Down": "\uE015",
"Insert": "\uE016",
"Delete": "\uE017",
"Semicolon": "\uE018",
"Equals": "\uE019",
"Numpad 0": "\uE01A",
"Numpad 1": "\uE01B",
"Numpad 2": "\uE01C",
"Numpad 3": "\uE01D",
"Numpad 4": "\uE01E",
"Numpad 5": "\uE01F",
"Numpad 6": "\uE020",
"Numpad 7": "\uE021",
"Numpad 8": "\uE022",
"Numpad 9": "\uE023",
"Multiply": "\uE024",
"Add": "\uE025",
"Separator": "\uE026",
"Subtract": "\uE027",
"Decimal": "\uE028",
"Divide": "\uE029",
"F1": "\uE031",
"F2": "\uE032",
"F3": "\uE033",
"F4": "\uE034",
"F5": "\uE035",
"F6": "\uE036",
"F7": "\uE037",
"F8": "\uE038",
"F9": "\uE039",
"F10": "\uE03A",
"F11": "\uE03B",
"F12": "\uE03C",
"Command": "\uE03D",
"Meta": "\uE03D",
"ZenkakuHankaku": "\uE040",
"Zenkaku_Hankaku": "\uE040"
};
var SUPPORTED_BROWSERNAMES = {
chrome: ["chrome", "googlechrome", "chromium", "chromium-browser"],
firefox: ["firefox", "ff", "mozilla", "mozilla firefox"],
edge: ["edge", "microsoftedge", "msedge"],
safari: ["safari", "safari technology preview"]
};
var DEFAULT_HOSTNAME = "localhost";
var DEFAULT_PROTOCOL = "http";
var DEFAULT_PATH = "/";
var HOOK_DEFINITION = {
type: "object",
validate: (param) => {
if (!Array.isArray(param)) {
throw new Error("a hook option needs to be a list of functions");
}
for (const option of param) {
if (typeof option === "function") {
continue;
}
throw new Error("expected hook to be type of function");
}
}
};
// src/utils.ts
var SCREENSHOT_REPLACEMENT = '"<Screenshot[base64]>"';
var SCRIPT_PLACEHOLDER = '"<Script[base64]>"';
var REGEX_SCRIPT_NAME = /return \((async )?function (\w+)/;
var SLASH = "/";
var REG_EXP_WINDOWS_ABS_PATH = /^[A-Za-z]:\\/;
function assertPath(path2) {
if (typeof path2 !== "string") {
throw new TypeError("Path must be a string. Received " + JSON.stringify(path2));
}
}
function isAbsolute(p) {
assertPath(p);
return p.length > 0 && (p.charCodeAt(0) === SLASH.codePointAt(0) || REG_EXP_WINDOWS_ABS_PATH.test(p));
}
function overwriteElementCommands(propertiesObject) {
const elementOverrides = propertiesObject.__elementOverrides__ ? propertiesObject.__elementOverrides__.value : {};
for (const [commandName, userDefinedCommand] of Object.entries(elementOverrides)) {
if (typeof userDefinedCommand !== "function") {
throw new Error("overwriteCommand: commands be overwritten only with functions, command: " + commandName);
}
if (!propertiesObject[commandName]) {
throw new Error("overwriteCommand: no command to be overwritten: " + commandName);
}
const propertiesObjectCommand = propertiesObject[commandName].value;
if (typeof propertiesObjectCommand !== "function") {
throw new Error("overwriteCommand: only functions can be overwritten, command: " + commandName);
}
const origCommand = propertiesObjectCommand;
delete propertiesObject[commandName];
const newCommand = function(...args) {
const element = this;
return userDefinedCommand.apply(element, [
function origCommandFunction(..._args) {
const context = this || element;
return origCommand.apply(context, arguments);
},
...args
]);
};
propertiesObject[commandName] = {
value: newCommand,
configurable: true
};
}
delete propertiesObject.__elementOverrides__;
propertiesObject.__elementOverrides__ = { value: {} };
}
function commandCallStructure(commandName, args, unfurl = false) {
const callArgs = args.map((arg) => {
if (typeof arg === "string" && /**
* The regex pattern matches:
* - Regular functions: `function()` or `function foo()`
* - Async functions: `async function()` or `async function foo()`
* - IIFEs: `!function()`
* - Returned functions: `return function` or `return (function`
* - Returned async functions: `return async function` or `return (async function`
* - Arrow functions: `() =>` or `param =>` or `(param1, param2) =>`
*/
/^\s*(?:(?:async\s+)?function(?:\s+\w+)?\s*\(|!function\(|return\s+\(?(?:async\s+)?function|\([^)]*\)\s*=>|\w+\s*=>)/.test(arg.trim())) {
arg = "<fn>";
} else if (typeof arg === "string" && /**
* the isBase64 method returns for xPath values like
* "/html/body/a" a true value which is why we should
* include a command check in here.
*/
!commandName.startsWith("findElement") && /**
* the isBase64 method returns for the argument value like
* "9A562133B0552E0ECB7628F2E8A09E86" a true value which is
* why we should include a command check in here.
*/
!commandName.startsWith("switch") && isBase64(arg)) {
arg = SCREENSHOT_REPLACEMENT;
} else if (typeof arg === "string") {
arg = `"${arg}"`;
} else if (typeof arg === "function") {
arg = "<fn>";
} else if (arg === null) {
arg = "null";
} else if (typeof arg === "object") {
arg = unfurl ? JSON.stringify(arg) : "<object>";
} else if (typeof arg === "undefined") {
arg = typeof arg;
}
return arg;
}).join(", ");
return `${commandName}(${callArgs})`;
}
function transformCommandLogResult(result) {
if (typeof result === "undefined") {
return "<empty result>";
} else if (typeof result !== "object" || !result) {
return result;
} else if ("file" in result && typeof result.file === "string" && isBase64(result.file)) {
return SCREENSHOT_REPLACEMENT;
} else if ("script" in result && typeof result.script === "string" && isBase64(result.script)) {
return SCRIPT_PLACEHOLDER;
} else if ("script" in result && typeof result.script === "string" && result.script.match(REGEX_SCRIPT_NAME)) {
const newScript = result.script.match(REGEX_SCRIPT_NAME)[2];
return { ...result, script: `${newScript}(...) [${Buffer.byteLength(result.script, "utf-8")} bytes]` };
} else if ("script" in result && typeof result.script === "string" && result.script.startsWith("!function(")) {
return { ...result, script: `<minified function> [${Buffer.byteLength(result.script, "utf-8")} bytes]` };
}
return result;
}
function isValidParameter(arg, expectedType) {
let shouldBeArray = false;
if (expectedType.slice(-2) === "[]") {
expectedType = expectedType.slice(0, -2);
shouldBeArray = true;
}
if (shouldBeArray) {
if (!Array.isArray(arg)) {
return false;
}
} else {
arg = [arg];
}
if (Array.isArray(arg)) {
for (const argEntity of arg) {
const argEntityType = getArgumentType(argEntity);
if (!argEntityType.match(expectedType)) {
return false;
}
}
}
return true;
}
function getArgumentType(arg) {
return arg === null ? "null" : typeof arg;
}
async function userImport(moduleName, namedImport = "default") {
try {
const mod = await import(
/* @vite-ignore */
moduleName
);
if (namedImport in mod) {
return mod[namedImport];
}
} catch {
throw new Error(`Couldn't import "${moduleName}"! Do you have it installed? If not run "npm install ${moduleName}"!`);
}
throw new Error(`Couldn't find "${namedImport}" in module "${moduleName}"`);
}
async function safeImport(name) {
let importPath = name;
try {
if (!globalThis.window) {
const { resolve } = await import("import-meta-resolve");
try {
importPath = await resolve(name, import.meta.url);
} catch {
const localNodeModules = path.join(process.cwd(), "node_modules");
try {
importPath = await resolve(name, url.pathToFileURL(localNodeModules).toString());
} catch {
return null;
}
}
}
} catch {
return null;
}
try {
const pkg = await import(
/* @vite-ignore */
importPath
);
if (pkg.default && pkg.default.default) {
return pkg.default;
}
return pkg;
} catch (e) {
throw new Error(`Couldn't initialize "${name}".
${e.stack}`);
}
}
function isFunctionAsync(fn) {
return fn.constructor && fn.constructor.name === "AsyncFunction" || fn.name === "async";
}
function filterSpecArgs(args) {
return args.filter((arg) => typeof arg !== "function");
}
function isBase64(str) {
if (typeof str !== "string") {
throw new Error("Expected string but received invalid type.");
}
const len = str.length;
const notBase64 = /[^A-Z0-9+/=]/i;
if (!len || len % 4 !== 0 || notBase64.test(str)) {
return false;
}
const firstPaddingChar = str.indexOf("=");
return firstPaddingChar === -1 || firstPaddingChar === len - 1 || firstPaddingChar === len - 2 && str[len - 1] === "=";
}
var sleep = (ms = 0) => new Promise((r) => setTimeout(r, ms));
function isAppiumCapability(caps) {
return Boolean(
caps && // @ts-expect-error outdated jsonwp cap
(caps.automationName || caps["appium:automationName"] || "appium:options" in caps && caps["appium:options"]?.automationName || // @ts-expect-error outdated jsonwp cap
caps.deviceName || caps["appium:deviceName"] || "appium:options" in caps && caps["appium:options"]?.deviceName || "lt:options" in caps && caps["lt:options"]?.deviceName || // @ts-expect-error outdated jsonwp cap
caps.appiumVersion || caps["appium:appiumVersion"] || "appium:options" in caps && caps["appium:options"]?.appiumVersion || "lt:options" in caps && caps["lt:options"]?.appiumVersion)
);
}
function definesRemoteDriver(options) {
return Boolean(
options.protocol && options.protocol !== DEFAULT_PROTOCOL || options.hostname && options.hostname !== DEFAULT_HOSTNAME || Boolean(options.port) || options.path && options.path !== DEFAULT_PATH || Boolean(options.user && options.key)
);
}
function getBrowserObject(elem) {
const elemObject = elem;
return elemObject.parent ? getBrowserObject(elemObject.parent) : elem;
}
async function enableFileLogging(outputDir) {
if (!outputDir) {
return;
}
await fs.mkdir(path.join(outputDir), { recursive: true });
process.env.WDIO_LOG_PATH = path.join(outputDir, "wdio.log");
}
// src/monad.ts
var SCOPE_TYPES = {
browser: (
/* istanbul ignore next */
function Browser() {
}
),
element: (
/* istanbul ignore next */
function Element() {
}
)
};
var mitt = _mitt;
var EVENTHANDLER_FUNCTIONS = ["on", "off", "emit", "once", "removeListener", "removeAllListeners"];
function WebDriver(options, modifier, propertiesObject = {}) {
const scopeType = SCOPE_TYPES[propertiesObject.scope?.value || "browser"];
delete propertiesObject.scope;
const prototype = Object.create(scopeType.prototype);
const log4 = logger("webdriver");
const mittInstance = mitt();
const eventHandler = {
on: mittInstance.on.bind(mittInstance),
off: mittInstance.off.bind(mittInstance),
emit: mittInstance.emit.bind(mittInstance),
once: (type, handler) => {
const onceWrapper = (...args) => {
mittInstance.off(type, onceWrapper);
handler(...args);
};
mittInstance.on(type, onceWrapper);
},
removeListener: mittInstance.off.bind(mittInstance),
removeAllListeners: (type) => {
if (type) {
mittInstance.off(type);
} else {
Object.assign(mittInstance, mitt());
}
}
};
function unit(sessionId, commandWrapper, elementCmdImplicitWaitExclusionList) {
propertiesObject.commandList = { value: Object.keys(propertiesObject) };
propertiesObject.options = { value: options };
if ("requestedCapabilities" in options) {
propertiesObject.requestedCapabilities = { value: options.requestedCapabilities };
}
if (typeof commandWrapper === "function") {
for (const [commandName, { value }] of Object.entries(propertiesObject)) {
if (typeof value !== "function" || Object.keys(EVENTHANDLER_FUNCTIONS).includes(commandName)) {
continue;
}
propertiesObject[commandName].value = commandWrapper(commandName, value, propertiesObject);
propertiesObject[commandName].configurable = true;
}
}
overwriteElementCommands.call(this, propertiesObject);
const { puppeteer, ...propertiesObjectWithoutPuppeteer } = propertiesObject;
propertiesObject.__propertiesObject__ = { value: propertiesObjectWithoutPuppeteer };
let client = Object.create(prototype, propertiesObject);
client.sessionId = sessionId;
if (scopeType.name === "Browser" && "capabilities" in options) {
client.capabilities = options.capabilities;
}
if (typeof modifier === "function") {
client = modifier(client, options);
}
client.addCommand = function(name, func, attachToElementOrOptions = false, proto, instances) {
const { attachToElement, disableElementImplicitWait, proto: _proto, instances: _instances } = typeof attachToElementOrOptions === "object" && attachToElementOrOptions !== null ? attachToElementOrOptions : { attachToElement: attachToElementOrOptions, proto, instances };
const customCommand = typeof commandWrapper === "function" ? commandWrapper(name, func) : func;
if (attachToElement) {
if (disableElementImplicitWait && elementCmdImplicitWaitExclusionList && !elementCmdImplicitWaitExclusionList.includes(name)) {
elementCmdImplicitWaitExclusionList.push(name);
}
if (_instances) {
Object.values(_instances).forEach((instance) => {
instance.__propertiesObject__[name] = {
value: customCommand
};
});
}
this.__propertiesObject__[name] = { value: customCommand };
} else {
unit.lift(name, customCommand, _proto);
}
if (typeof process.send === "function" && process.env.WDIO_WORKER_ID) {
const message = {
origin: "worker",
name: "workerEvent",
args: {
type: MESSAGE_TYPES.customCommand,
value: {
commandName: name,
cid: process.env.WDIO_WORKER_ID
}
}
};
process.send(message);
}
};
client.overwriteCommand = function(name, func, attachToElement = false, proto, instances) {
const customCommand = typeof commandWrapper === "function" ? commandWrapper(name, func) : func;
if (attachToElement) {
if (instances) {
Object.values(instances).forEach((instance) => {
instance.__propertiesObject__.__elementOverrides__.value[name] = customCommand;
});
} else {
this.__propertiesObject__.__elementOverrides__.value[name] = customCommand;
}
} else if (client[name]) {
const origCommand = client[name];
delete client[name];
unit.lift(name, customCommand, proto, (...args) => origCommand.apply(this, args));
} else {
throw new Error("overwriteCommand: no command to be overwritten: " + name);
}
};
return client;
}
unit.lift = function(name, func, proto, origCommand) {
(proto || prototype)[name] = function next(...args) {
log4.info("COMMAND", commandCallStructure(name, args));
this.emit("command", { command: name, body: args });
Object.defineProperty(func, "name", {
value: name,
writable: false
});
try {
const result = func.apply(this, origCommand ? [origCommand, ...args] : args);
if (isPromiseLike(result)) {
result.then((res) => {
const elem = res;
let resultLog = res;
if (elem instanceof SCOPE_TYPES.element) {
resultLog = `WebdriverIO.Element<${elem.elementId || elem.selector}>`;
} else if (res instanceof SCOPE_TYPES.browser) {
resultLog = "WebdriverIO.Browser";
}
log4.info("RESULT", resultLog);
this.emit("result", {
command: name,
result: { value: res },
name
// Kept for legacy reasons, as the `command` property is now used in the reporter. To remove one day!
});
}).catch((error) => {
this.emit("result", { command: name, result: { error } });
});
} else {
this.emit("result", { command: name, result: { value: result } });
}
return result;
} catch (error) {
this.emit("result", { command: name, result: { error } });
throw error;
}
};
};
for (const eventCommand of EVENTHANDLER_FUNCTIONS) {
prototype[eventCommand] = function(...args) {
if (eventCommand === "on" && args[0] === "dialog") {
eventHandler.emit("_dialogListenerRegistered");
}
if (eventCommand === "off" && args[0] === "dialog") {
eventHandler.emit("_dialogListenerRemoved");
}
switch (eventCommand) {
case "on":
eventHandler.on(args[0], args[1]);
break;
case "off":
case "removeListener":
eventHandler.off(args[0], args[1]);
break;
case "emit":
eventHandler.emit(args[0], args[1]);
break;
case "once":
eventHandler.once(args[0], args[1]);
break;
case "removeAllListeners":
eventHandler.removeAllListeners(args[0]);
break;
}
return this;
};
}
return unit;
}
var isPromiseLike = (value) => {
return value !== null && typeof value === "object" && typeof value.then === "function" && typeof value.catch === "function";
};
// src/initializePlugin.ts
var FILE_PROTOCOL = "file://";
async function initializePlugin(name, type) {
if (name[0] === "@" || isAbsolute(name)) {
const fileUrl = name[0] === "@" ? name : ensureFileURL(name);
const service = await safeImport(fileUrl);
if (service) {
return service;
}
}
if (typeof type !== "string") {
throw new Error("No plugin type provided");
}
const scopedPlugin = await safeImport(`@wdio/${name.toLowerCase()}-${type}`);
if (scopedPlugin) {
return scopedPlugin;
}
const plugin = await safeImport(`wdio-${name.toLowerCase()}-${type}`);
if (plugin) {
return plugin;
}
throw new Error(
`Couldn't find plugin "${name}" ${type}, neither as wdio scoped package "@wdio/${name.toLowerCase()}-${type}" nor as community package "wdio-${name.toLowerCase()}-${type}". Please make sure you have it installed!`
);
}
function ensureFileURL(path2) {
if (path2.startsWith(FILE_PROTOCOL)) {
return path2;
}
if (REG_EXP_WINDOWS_ABS_PATH.test(path2)) {
return `${FILE_PROTOCOL}/${path2.replace(/\\/g, "/")}`;
}
if (path2.startsWith(SLASH)) {
return `${FILE_PROTOCOL}${path2}`;
}
return path2;
}
// src/startWebDriver.ts
import logger2 from "@wdio/logger";
var log = logger2("@wdio/utils");
async function startWebDriver(options) {
if (definesRemoteDriver(options)) {
log.info(`Connecting to existing driver at ${options.protocol}://${options.hostname}:${options.port}${options.path}`);
return;
}
if (globalThis.process) {
const nodeModule = "./node.js";
const { startWebDriver: startWebDriver2 } = await import(nodeModule);
return startWebDriver2(options);
}
throw new Error("Please provide a valid `hostname` and `port` to start WebDriver sessions in the browser!");
}
// src/initializeServices.ts
import logger3 from "@wdio/logger";
var log2 = logger3("@wdio/utils:initializeServices");
async function initializeServices(services) {
const initializedServices = [];
for (const [serviceName, serviceConfig = {}] of services) {
if (typeof serviceName === "object") {
log2.debug("initialize custom initiated service");
initializedServices.push([serviceName, {}]);
continue;
}
if (typeof serviceName === "function") {
log2.debug(`initialize custom service "${serviceName.name}"`);
initializedServices.push([serviceName, serviceConfig]);
continue;
}
log2.debug(`initialize service "${serviceName}" as NPM package`);
const service = await initializePlugin(serviceName, "service");
initializedServices.push([service, serviceConfig, serviceName]);
}
return initializedServices;
}
function sanitizeServiceArray(service) {
return Array.isArray(service) ? service : [service, {}];
}
async function initializeLauncherService(config, caps) {
const ignoredWorkerServices = [];
const launcherServices = [];
let serviceLabelToBeInitialised = "unknown";
try {
const services = await initializeServices(config.services.map(sanitizeServiceArray));
for (const [service, serviceConfig, serviceName] of services) {
if (typeof service === "object" && !serviceName) {
serviceLabelToBeInitialised = "object";
launcherServices.push(service);
continue;
}
const Launcher = service.launcher;
if (typeof Launcher === "function" && serviceName) {
serviceLabelToBeInitialised = `"${serviceName}"`;
launcherServices.push(new Launcher(serviceConfig, caps, config));
}
if (typeof service === "function" && !serviceName) {
serviceLabelToBeInitialised = `"${service.constructor?.name || service.toString()}"`;
launcherServices.push(new service(serviceConfig, caps, config));
}
if (serviceName && typeof service.default !== "function" && typeof service !== "function") {
ignoredWorkerServices.push(serviceName);
}
}
} catch (err) {
throw new Error(`Failed to initialise launcher service ${serviceLabelToBeInitialised}: ${err.stack}`);
}
return { ignoredWorkerServices, launcherServices };
}
async function initializeWorkerService(config, caps, ignoredWorkerServices = []) {
let serviceLabelToBeInitialised = "unknown";
const initializedServices = [];
const workerServices = config.services.map(sanitizeServiceArray).filter(([serviceName]) => !ignoredWorkerServices.includes(serviceName));
try {
const services = await initializeServices(workerServices);
for (const [service, serviceConfig, serviceName] of services) {
if (typeof service === "object" && !serviceName) {
serviceLabelToBeInitialised = "object";
initializedServices.push(service);
continue;
}
const Service = service.default || service;
if (typeof Service === "function") {
serviceLabelToBeInitialised = serviceName || Service.constructor?.name || Service.toString();
initializedServices.push(new Service(serviceConfig, caps, config));
continue;
}
}
return initializedServices;
} catch (err) {
throw new Error(`Failed to initialise service ${serviceLabelToBeInitialised}: ${err.stack}`);
}
}
// src/shim.ts
import logger4 from "@wdio/logger";
// src/pIteration.ts
var pIteration_exports = {};
__export(pIteration_exports, {
every: () => every,
everySeries: () => everySeries,
filter: () => filter,
filterSeries: () => filterSeries,
find: () => find,
findIndex: () => findIndex,
findIndexSeries: () => findIndexSeries,
findSeries: () => findSeries,
forEach: () => forEach,
forEachSeries: () => forEachSeries,
map: () => map,
mapSeries: () => mapSeries,
reduce: () => reduce,
some: () => some,
someSeries: () => someSeries
});
var forEach = async (array, callback, thisArg) => {
const promiseArray = [];
for (let i = 0; i < array.length; i++) {
if (i in array) {
const p = Promise.resolve(array[i]).then((currentValue) => {
return callback.call(thisArg || void 0, currentValue, i, array);
});
promiseArray.push(p);
}
}
await Promise.all(promiseArray);
};
var forEachSeries = async (array, callback, thisArg) => {
for (let i = 0; i < array.length; i++) {
if (i in array) {
await callback.call(thisArg || void 0, await array[i], i, array);
}
}
};
var map = async (array, callback, thisArg) => {
const promiseArray = [];
for (let i = 0; i < array.length; i++) {
if (i in array) {
promiseArray[i] = Promise.resolve(array[i]).then((currentValue) => {
return callback.call(thisArg || void 0, currentValue, i, array);
});
}
}
return Promise.all(promiseArray);
};
var mapSeries = async (array, callback, thisArg) => {
const result = [];
for (let i = 0; i < array.length; i++) {
if (i in array) {
result[i] = await callback.call(thisArg || void 0, await array[i], i, array);
}
}
return result;
};
var find = (array, callback, thisArg) => {
return new Promise((resolve, reject) => {
if (array.length === 0) {
return resolve(void 0);
}
let counter = 1;
for (let i = 0; i < array.length; i++) {
const check = (found) => {
if (found) {
resolve(array[i]);
} else if (counter === array.length) {
resolve(void 0);
}
counter++;
};
Promise.resolve(array[i]).then((elem) => callback.call(thisArg || void 0, elem, i, array)).then(check).catch(reject);
}
});
};
var findSeries = async (array, callback, thisArg) => {
for (let i = 0; i < array.length; i++) {
if (await callback.call(thisArg || void 0, await array[i], i, array)) {
return array[i];
}
}
};
var findIndex = (array, callback, thisArg) => {
return new Promise((resolve, reject) => {
if (array.length === 0) {
return resolve(-1);
}
let counter = 1;
for (let i = 0; i < array.length; i++) {
const check = (found) => {
if (found) {
resolve(i);
} else if (counter === array.length) {
resolve(-1);
}
counter++;
};
Promise.resolve(array[i]).then((elem) => callback.call(thisArg || void 0, elem, i, array)).then(check).catch(reject);
}
});
};
var findIndexSeries = async (array, callback, thisArg) => {
for (let i = 0; i < array.length; i++) {
if (await callback.call(thisArg || void 0, await array[i], i, array)) {
return i;
}
}
};
var some = (array, callback, thisArg) => {
return new Promise((resolve, reject) => {
if (array.length === 0) {
return resolve(false);
}
let counter = 1;
for (let i = 0; i < array.length; i++) {
if (!(i in array)) {
counter++;
continue;
}
const check = (found) => {
if (found) {
resolve(true);
} else if (counter === array.length) {
resolve(false);
}
counter++;
};
Promise.resolve(array[i]).then((elem) => callback.call(thisArg || void 0, elem, i, array)).then(check).catch(reject);
}
});
};
var someSeries = async (array, callback, thisArg) => {
for (let i = 0; i < array.length; i++) {
if (i in array && await callback.call(thisArg || void 0, await array[i], i, array)) {
return true;
}
}
return false;
};
var every = (array, callback, thisArg) => {
return new Promise((resolve, reject) => {
if (array.length === 0) {
return resolve(true);
}
let counter = 1;
for (let i = 0; i < array.length; i++) {
if (!(i in array)) {
counter++;
continue;
}
const check = (found) => {
if (!found) {
resolve(false);
} else if (counter === array.length) {
resolve(true);
}
counter++;
};
Promise.resolve(array[i]).then((elem) => callback.call(thisArg || void 0, elem, i, array)).then(check).catch(reject);
}
});
};
var everySeries = async (array, callback, thisArg) => {
for (let i = 0; i < array.length; i++) {
if (i in array && !await callback.call(thisArg || void 0, await array[i], i, array)) {
return false;
}
}
return true;
};
var filter = (array, callback, thisArg) => {
return new Promise((resolve, reject) => {
const promiseArray = [];
for (let i = 0; i < array.length; i++) {
if (i in array) {
promiseArray[i] = Promise.resolve(array[i]).then((currentValue) => {
return callback.call(thisArg || void 0, currentValue, i, array);
}).catch(reject);
}
}
return Promise.all(
promiseArray.map(async (p, i) => {
if (await p) {
return await array[i];
}
return void 0;
})
).then(
(result) => result.filter((val) => typeof val !== "undefined")
).then(resolve, reject);
});
};
var filterSeries = async (array, callback, thisArg) => {
const result = [];
for (let i = 0; i < array.length; i++) {
if (i in array && await callback.call(thisArg || void 0, await array[i], i, array)) {
result.push(await array[i]);
}
}
return result;
};
var reduce = async (array, callback, initialValue) => {
if (array.length === 0 && initialValue === void 0) {
throw TypeError("Reduce of empty array with no initial value");
}
let i;
let previousValue;
if (initialValue !== void 0) {
previousValue = initialValue;
i = 0;
} else {
previousValue = array[0];
i = 1;
}
for (i; i < array.length; i++) {
if (i in array) {
previousValue = await callback(await previousValue, await array[i], i, array);
}
}
return previousValue;
};
// src/shim.ts
var log3 = logger4("@wdio/utils:shim");
var inCommandHook = false;
var ELEMENT_QUERY_COMMANDS = [
"$",
"$$",
"custom$",
"custom$$",
"shadow$",
"shadow$$",
"react$",
"react$$",
"nextElement",
"previousElement",
"parentElement"
];
var ELEMENT_PROPS = [
"elementId",
"error",
"selector",
"parent",
"index",
"isReactElement",
"length"
];
var ACTION_COMMANDS = ["action", "actions"];
var PROMISE_METHODS = ["then", "catch", "finally"];
var ELEMENT_RETURN_COMMANDS = ["getElement", "getElements"];
var TIME_BUFFER = 3;
async function executeHooksWithArgs(hookName, hooks = [], args = []) {
if (!Array.isArray(hooks)) {
hooks = [hooks];
}
if (!Array.isArray(args)) {
args = [args];
}
const rejectIfSkipped = function(e, rejectionFunc) {
if (/^(sync|async) skip; aborting execution$/.test(e.message)) {
rejectionFunc();
return true;
}
if (/^=> marked Pending/.test(e)) {
rejectionFunc(e);
return true;
}
};
const hooksPromises = hooks.map((hook) => new Promise((resolve, reject) => {
let result2;
try {
result2 = hook.apply(this, args);
} catch (e) {
if (rejectIfSkipped(e, reject)) {
return;
}
log3.error(e.stack);
return resolve(e);
}
if (result2 && typeof result2.then === "function") {
return result2.then(resolve, (e) => {
if (rejectIfSkipped(e, reject)) {
return;
}
log3.error(e.stack || e.message);
resolve(e);
});
}
resolve(result2);
}));
const start = Date.now();
const result = await Promise.all(hooksPromises);
if (hooksPromises.length) {
log3.debug(`Finished to run "${hookName}" hook in ${Date.now() - start}ms`);
}
return result;
}
function wrapCommand(commandName, fn) {
async function wrapCommandFn(...args) {
const beforeHookArgs = [commandName, args];
if (!inCommandHook && this.options.beforeCommand) {
inCommandHook = true;
await executeHooksWithArgs.call(this, "beforeCommand", this.options.beforeCommand, beforeHookArgs);
inCommandHook = false;
}
let commandResult;
let commandError;
try {
commandResult = await fn.apply(this, args);
} catch (err) {
commandError = err;
}
if (!inCommandHook && this.options.afterCommand) {
inCommandHook = true;
const afterHookArgs = [...beforeHookArgs, commandResult, commandError];
await executeHooksWithArgs.call(this, "afterCommand", this.options.afterCommand, afterHookArgs);
inCommandHook = false;
}
if (commandError) {
throw commandError;
}
return commandResult;
}
function wrapElementFn(promise, cmd, args, prevInnerArgs) {
return new Proxy(
Promise.resolve(promise).then((ctx) => cmd.call(ctx, ...args)),
{
get: (target, prop) => {
if (typeof prop === "symbol" || prop === "entries") {
return () => ({
i: 0,
target,
async next() {
const elems = await this.target;
if (!Array.isArray(elems)) {
throw new Error("Can not iterate over non array");
}
if (this.i < elems.length) {
if (prop === "entries") {
return { value: [this.i, elems[this.i++]], done: false };
}
return { value: elems[this.i++], done: false };
}
return { done: true };
}
});
}
const numValue = parseInt(prop, 10);
if (!isNaN(numValue)) {
return wrapElementFn(
target,
/**
* `this` is an array of WebdriverIO elements
*/
function(index) {
if (index >= this.length) {
const browser = getBrowserObject(this);
return browser.waitUntil(async () => {
const elems = await this.parent[this.foundWith](this.selector);
if (elems.length > index) {
return elems[index];
}
return false;
}, {
timeout: browser.options.waitforTimeout,
timeoutMsg: `Index out of bounds! $$(${this.selector}) returned only ${this.length} elements.`
});
}
return this[index];
},
[prop],
{ prop, args }
);
}
if (ELEMENT_QUERY_COMMANDS.includes(prop) || prop.endsWith("$")) {
return wrapCommand(prop, function(...args2) {
return this[prop].apply(this, args2);
});
}
if (commandName.endsWith("$$") && typeof pIteration_exports[prop] === "function") {
return (...iteratorArgs) => wrapElementFn(
target,
function(...iteratorArgs2) {
return pIteration_exports[prop](this, ...iteratorArgs2);
},
iteratorArgs
);
}
if (ELEMENT_PROPS.includes(prop)) {
return target.then((res) => res[prop]);
}
if (PROMISE_METHODS.includes(prop)) {
return target[prop].bind(target);
}
if (ELEMENT_RETURN_COMMANDS.includes(prop)) {
return () => target;
}
return (...args2) => target.then(async (elem) => {
if (!elem) {
let errMsg = "Element could not be found";
const prevElem = await promise;
if (Array.isArray(prevElem) && prevInnerArgs && prevInnerArgs.prop === "get") {
errMsg = `Index out of bounds! $$(${prevInnerArgs.args[0]}) returned only ${prevElem.length} elements.`;
}
throw new Error(errMsg);
}
if (prop === "toJSON") {
return { ELEMENT: elem.elementId };
}
if (typeof elem[prop] !== "function") {
throw new Error(`Can't call "${prop}" on element with selector "${elem.selector}", it is not a function`);
}
return elem[prop](...args2);
});
}
}
);
}
function chainElementQuery(...args) {
return wrapElementFn(this, wrapCommandFn, args);
}
return function(...args) {
const command = ELEMENT_QUERY_COMMANDS.includes(commandName) || commandName.endsWith("$") ? chainElementQuery : ACTION_COMMANDS.includes(commandName) ? fn : wrapCommandFn;
return command.apply(this, args);
};
}
async function executeAsync(fn, retries, args = [], timeout = 2e4) {
this.wdioRetries = retries.attempts;
try {
const runnableTimeout = this?._runnable?._timeout;
const frameworkTimeout = runnableTimeout ?? globalThis.jasmine?.DEFAULT_TIMEOUT_INTERVAL ?? timeout;
const _timeout = (frameworkTimeout ?? timeout) - TIME_BUFFER;
let done = false;
const result = await Promise.race([
fn.apply(this, args),
new Promise((resolve, reject) => {
setTimeout(() => {
if (done) {
resolve();
} else {
reject(new Error("Timeout"));
}
}, _timeout);
})
]);
done = true;
if (result !== null && typeof result === "object" && "finally" in result && typeof result.finally === "function") {
result.catch((err) => err);
}
return await result;
} catch (err) {
if (retries.limit > retries.attempts) {
retries.attempts++;
return await executeAsync.call(this, fn, retries, args);
}
throw err;
}
}
// src/test-framework/errorHandler.ts
var logHookError = (hookName, hookResults = [], cid) => {
const result = hookResults.find((result2) => result2 instanceof Error);
if (typeof result === "undefined") {
return;
}
const error = { message: result.message };
const content = {
cid,
error,
fullTitle: `${hookName} Hook`,
type: "hook",
state: "fail"
};
if (globalThis.process && typeof globalThis.process.send === "function") {
globalThis.process.send({
origin: "reporter",
name: "printFailureMessage",
content
});
}
};
// src/test-framework/testFnWrapper.ts
var STACKTRACE_FILTER = [
"node_modules/webdriver/",
"node_modules/webdriverio/",
"node_modules/@wdio/",
"(internal/process/task",
"(node:internal/process/task"
];
var testFnWrapper = function(...args) {
return testFrameworkFnWrapper.call(this, { executeHooksWithArgs, executeAsync }, ...args);
};
var testFrameworkFnWrapper = async function({ executeHooksWithArgs: executeHooksWithArgs2, executeAsync: executeAsync2 }, type, { specFn, specFnArgs }, { beforeFn, beforeFnArgs }, { afterFn, afterFnArgs }, cid, repeatTest = 0, hookName, timeout) {
const retries = { attempts: 0, limit: repeatTest };
const beforeArgs = beforeFnArgs(this);
if (type === "Hook" && hookName) {
beforeArgs.push(hookName);
}
await logHookError(`Before${type}`, await executeHooksWithArgs2(`before${type}`, beforeFn, beforeArgs), cid);
let result;
let error;
let skip = false;
const testStart = Date.now();
try {
result = await executeAsync2.call(this, specFn, retries, specFnArgs, timeout);
if (globalThis._jasmineTestResult !== void 0) {
result = globalThis._jasmineTestResult;
globalThis._jasmineTestResult = void 0;
}
if (globalThis._wdioDynamicJasmineResultErrorList?.length > 0) {
globalThis._wdioDynamicJasmineResultErrorList[0].stack = filterStackTrace(globalThis._wdioDynamicJasmineResultErrorList[0].stack);
error = globalThis._wdioDynamicJasmineResultErrorList[0];
globalThis._wdioDynamicJasmineResultErrorList = void 0;
}
} catch (_err) {
if (!(JSON.stringify(_err, Object.getOwnPropertyNames(_err)).includes("sync skip; aborting execution") || JSON.stringify(_err, Object.getOwnPropertyNames(_err)).includes("marked Pending"))) {
const err = _err instanceof Error ? _err : new Error(typeof _err === "string" ? _err : "An unknown error occurred");
if (err.stack) {
err.stack = filterStackTrace(err.stack);
}
error = err;
} else {
skip = true;
}
}
const duration = Date.now() - testStart;
const afterArgs = afterFnArgs(this);
afterArgs.push({
retries,
error,
result,
duration,
passed: !error && !skip,
skipped: skip
});
if (type === "Hook" && hookName) {
afterArgs.push(hookName);
}
await logHookError(`After${type}`, await executeHooksWithArgs2(`after${type}`, afterFn, [...afterArgs]), cid);
if (error && !error.matcherName) {
throw error;
}
return result;
};
var filterStackTrace = (stack) => {
return stack.split("\n").filter((line) => !STACKTRACE_FILTER.some((l) => line.includes(l))).map((line) => line.replace(/\?invalidateCache=(\d\.\d+|\d)/g, "")).join("\n");
};
// src/test-framework/testInterfaceWrapper.ts
var MOCHA_COMMANDS = ["skip", "only"];
var runHook = function(hookFn, origFn, beforeFn, beforeFnArgs, afterFn, afterFnArgs, cid, repeatTest, timeout) {
const wrappedHook = function(...hookFnArgs) {
return testFnWrapper.call(
this,
"Hook",
{
specFn: hookFn,
specFnArgs: filterSpecArgs(hookFnArgs)
},
{
beforeFn,
beforeFnArgs
},
{
afterFn,
afterFnArgs
},
cid,
repeatTest,
origFn.name,
timeout
);
};
wrappedHook.toString = () => hookFn.toString();
return origFn(wrappedHook, timeout);
};
var runSpec = function(specTitle, specFn, origFn, beforeFn, beforeFnArgs, afterFn, afterFnArgs, cid, repeatTest, timeout) {
const wrappedFn = function(...specFnArgs) {
return testFnWrapper.call(
this,
"Test",
{
specFn,
specFnArgs: filterSpecArgs(specFnArgs)
},
{
beforeFn,
beforeFnArgs
},
{
afterFn,
afterFnArgs
},
cid,
repeatTest,
void 0,
timeout
);
};
wrappedFn.toString = () => specFn.toString();
return origFn(specTitle, wrappedFn, timeout);
};
var wrapTestFunction = function(origFn, isSpec, beforeFn, beforeArgsFn, afterFn, afterArgsFn, cid) {
return function(...specArguments) {
let retryCnt = typeof specArguments[specArguments.length - 1] === "number" ? specArguments.pop() : 0;
let timeout = globalThis.jasmine?.DEFAULT_TIMEOUT_INTERVAL;
if (globalThis.jasmine) {
if (typeof specArguments[specArguments.length - 1] === "number") {
timeout = specArguments.pop();
} else {
timeout = retryCnt;
retryCnt = 0;
}
}
const specFn = typeof specArguments[0] === "function" ? specArguments.shift() : typeof specArguments[1] === "function" ? specArguments[1] : void 0;
const specTitle = specArguments[0];
if (isSpec) {
if (specFn) {
return runSpec(
specTitle,
specFn,
origFn,
beforeFn,
beforeArgsFn,
afterFn,
afterArgsFn,
cid,
retryCnt,
timeout
);
}
return origFn(specTitle);
}
return runHook(
specFn,
origFn,
beforeFn,
beforeArgsFn,
afterFn,
afterArgsFn,
cid,
retryCnt,
timeout
);
};
};
var wrapGlobalTestMethod = function(isSpec, beforeFn, beforeArgsFn, afterFn, afterArgsFn, fnName, cid, scope = globalThis) {
const origFn = scope[fnName];
scope[fnName] = wrapTestFunction(
origFn,
isSpec,
beforeFn,
beforeArgsFn,
afterFn,
afterArgsFn,
cid
);
addMochaCommands(origFn, scope[fnName]);
};
function addMochaCommands(origFn, newFn) {
MOCHA_COMMANDS.forEach((commandName) => {
if (typeof origFn[commandName] === "function") {
newFn[commandName] = origFn[commandName];
}
});
}
// src/envDetector.ts
var MOBILE_BROWSER_NAMES = ["ipad", "iphone", "android"];
var MOBILE_CAPABILITIES = [
"appium-version",
"appiumVersion",
"device-type",
"deviceType",
"app",
"appArguments",
"device-orientation",
"deviceOrientation",
"deviceName",
"automationName"
];
function isW3C(capabilities) {
if (!capabilities) {
return false;
}
const isAppium = Boolean(
capabilities["appium:automationName"] || capabilities["appium:deviceName"] || capabilities["appium:appiumVersion"]
);
const hasW3CCaps = Boolean(
/**
* safari docker image may not provide a platformName therefore
* check one of the available "platformName" or "browserVersion"
*/
(capabilities.platformName || capabilities.browserVersion) && /**
* local safari and BrowserStack don't provide platformVersion therefore
* check also if setWindowRect is provided
*/
(capabilities["appium:platformVersion"] || Object.prototype.hasOwnProperty.call(capabilities, "setWindowRect"))
);
const hasWebdriverFlag = Boolean(capabilities["ms:experimental-webdriver"]);
return Boolean(hasW3CCaps || isAppium || hasWebdriverFlag);
}
function isChrome(capabilities) {
if (!capabilities) {
return false;
}
return Boolean(
capabilities["goog:chromeOptions"] && (capabilities.browserName === "chrome" || capabilities.browserName === "chrome-headless-shell")
);
}
function isEdge(capabilities) {
if (!capabilities) {
return false;
}
return Boolean(capabilities.browserName && SUPPORTED_BROWSERNAMES.edge.includes(capabilities.browserName.toLowerCase()) || capabilities["ms:edgeOptions"]);
}
function isFirefox(capabilities) {
if (!capabilities) {
return false;
}
return capabilities.browserName === "firefox" || Boolean(Object.keys(capabilities).find((cap) => cap.startsWith("moz:")));
}
function getAutomationName(capabilities) {
return capabilities["appium:options"]?.automationName || capabilities["appium:automationName"] || capabilities["automationName"];
}
function isMobile(capabilities) {
const browserName = (capabilities.browserName || "").toLowerCase();
const bsOptions = capabilities["bstack:options"] || {};
const browserstackBrowserName = (bsOptions.browserName || "").toLowerCase();
const automationName = getAutomationName(capabilities);
if (automationName && ["gecko", "safari", "chrome", "chromium"].includes(automationName.toLocaleLowerCase())) {
return false;
}
return Boolean(
/**
* If the device is ios, tvos or android, the device might be mobile.
*/
capabilities.platformName && capabilities.platformName.match(/ios/i) || capabilities.platformName && capabilities.platformName.match(/tvos/i) || capabilities.platformName && capabilities.platformName.match(/android/i) || /ios/i.test(bsOptions.platformName || "") || /tvos/i.test(bsOptions.platformName || "") || /android/i.test(bsOptions.platformName || "") || /**
* capabilities contain mobile only specific capabilities
*/
Object.keys(capabilities).find((cap) => MOBILE_CAPABILITIES.includes(cap) || MOBILE_CAPABILITIES.map((c) => `appium:${c}`).includes(cap)) || /**
* browserName is empty (and eventually app is defined)
*/
capabilities.browserName === "" || bsOptions.browserName === "" || /**
* browserName is a mobile browser
*/
MOBILE_BROWSER_NAMES.includes(browserName) || MOBILE_BROWSER_NAMES.includes(browserstackBrowserName)
);
}
function isIOS(capabilities) {
const bsOptions = capabilities?.["bstack:options"] || {};
if (!capabilities) {
return false;
}
return Boolean(
capabilities.platformName && capabilities.platformName.match(/iOS/i) || capabilities["appium:deviceName"] && capabilities["appium:deviceName"].match(/(iPad|iPhone)/i) || /iOS/i.test(bsOptions.platformName || "") || /(iPad|iPhone)/i.test(bsOptions.deviceName || "")
);
}
function isAndroid(capabilities) {
const bsOptions = capabilities?.["bstack:options"] || {};
if (!capabilities) {
return false;
}
const hasAndroidPlatform = Boolean(
capabilities.platformName && capabilities.platformName.match(/Android/i) || /Android/i.test(bsOptions.platformName || "") || /Android/i.test(bsOptions.browserName || "") || capabilities.browserName && capabilities.browserName.match(/Android/i)
);
const deviceName = bsOptions.deviceName || "";
const hasAndroidDeviceName = /android|galaxy|pixel|nexus|oneplus|lg|htc|motorola|sony|huawei|vivo|oppo|xiaomi|redmi|realme|samsung/i.test(deviceName);
return Boolean(hasAndroidPlatform || hasAndroidDeviceName);
}
function matchesAppAutomationName(automationNameValue, capabilities) {
if (!capabilities) {
return false;
}
const automationName = getAutomationName(capabilities);
if (!automationName) {
return false;
}
return Boolean(automationName.match(new RegExp(automationNameValue, "i")));
}
function isWindowsApp(capabilities) {
return matchesAppAutomationName("windows", capabilities);
}
function isMacApp(capabilities) {
return matchesAppAutomationName("mac2", capabilities);
}
function isSauce(capabilities) {
if (!capabilities) {
return false;
}
const caps = "alwaysMatch" in capabilities ? capabilities.alwaysMatch : capabilities;
return Boolean(
caps["sauce:options"] && caps["sauce:options"].extendedDebugging
);
}
function isBidi(capabilities) {
if (!capabilities) {
return false;
}
return typeof capabilities.webSocketUrl === "string";
}
function isSeleniumStandalone(capabilities) {
if (!capabilities) {
return f