UNPKG

@wdio/utils

Version:

A WDIO helper utility to provide several utility functions used across the project.

1,495 lines (1,480 loc) 51.4 kB
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) { 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, attachToElement = false, proto, instances) { const customCommand = typeof commandWrapper === "function" ? commandWrapper(name, func) : func; if (attachToElement) { 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 (mapIterator) => wrapElementFn( target, function(mapIterator2) { return pIteration_exports[prop](this, mapIterator2); }, [mapIterator] ); } 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 _timeout = (this?._runnable?._timeout || globalThis.jasmine?.DEFAULT_TIMEOUT_INTERVAL || 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 ); }; 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 ); }; 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 false; } return ( /** * Selenium v3 and below */ // @ts-expect-error outdated JSONWP capabilities Boolean(capabilities["webdriver.remote.sessionid"]) || /** * Selenium v4 and up */ Boolean(capabilities["se:cdp"]) ); } function isChromium(capabilities) { if (!capabilities) { return false; } return isChrome(capabilities) || isEdge(capabilities); } function capabilitiesEnvironmentDetector(capabilities) { return { isChrome: isChrome(capabilities), isFirefox: isFirefox(capabilities), isMobile: isMobile(capabilities), isIOS: isIOS(capabilities), isAndroid: isAndroid(capabilities), isSauce: isSauce(capabilitie