UNPKG

webdriverio

Version:

Next-gen browser and mobile automation test framework for Node.js

1,504 lines (1,472 loc) 284 kB
var __defProp = Object.defineProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; // src/index.ts import logger25 from "@wdio/logger"; import WebDriver, { DEFAULTS } from "webdriver"; import { validateConfig } from "@wdio/config"; import { enableFileLogging, wrapCommand as wrapCommand3 } from "@wdio/utils"; // src/multiremote.ts import zip from "lodash.zip"; import clone2 from "lodash.clonedeep"; import { webdriverMonad as webdriverMonad2, wrapCommand as wrapCommand2 } from "@wdio/utils"; // src/middlewares.ts import { ELEMENT_KEY as ELEMENT_KEY19 } from "webdriver"; import { getBrowserObject as getBrowserObject36 } from "@wdio/utils"; // src/utils/implicitWait.ts import logger from "@wdio/logger"; import { getBrowserObject } from "@wdio/utils"; var log = logger("webdriverio"); async function implicitWait(currentElement, commandName) { const browser = getBrowserObject(currentElement); const skipForMobileScroll = browser.isMobile && await browser.isNativeContext && (commandName === "scrollIntoView" || commandName === "tap"); if (!currentElement.elementId && !commandName.match(/(waitUntil|waitFor|isExisting|is?\w+Displayed|is?\w+Clickable)/) && !skipForMobileScroll) { log.debug( `command ${commandName} was called on an element ("${currentElement.selector}") that wasn't found, waiting for it...` ); try { await currentElement.waitForExist(); return currentElement.parent.$(currentElement.selector).getElement(); } catch { if (currentElement.selector.toString().includes("this.previousElementSibling")) { throw new Error( `Can't call ${commandName} on previous element of element with selector "${currentElement.parent.selector}" because sibling wasn't found` ); } if (currentElement.selector.toString().includes("this.nextElementSibling")) { throw new Error( `Can't call ${commandName} on next element of element with selector "${currentElement.parent.selector}" because sibling wasn't found` ); } if (currentElement.selector.toString().includes("this.parentElement")) { throw new Error( `Can't call ${commandName} on parent element of element with selector "${currentElement.parent.selector}" because it wasn't found` ); } throw new Error( `Can't call ${commandName} on element with selector "${currentElement.selector}" because element wasn't found` ); } } return currentElement; } // src/utils/refetchElement.ts async function refetchElement(currentElement, commandName) { const selectors = []; while (currentElement.elementId && currentElement.parent) { selectors.push({ selector: currentElement.selector, index: currentElement.index || 0 }); currentElement = currentElement.parent; } selectors.reverse(); const length = selectors.length; return selectors.reduce(async (elementPromise, { selector, index }, currentIndex) => { const resolvedElement = await elementPromise; let nextElement2 = index > 0 ? await resolvedElement.$$(selector)[index]?.getElement() : null; nextElement2 = nextElement2 || await resolvedElement.$(selector).getElement(); return await implicitWait(nextElement2, currentIndex + 1 < length ? "$" : commandName); }, Promise.resolve(currentElement)); } // src/utils/index.ts import fs12 from "node:fs/promises"; import path3 from "node:path"; import { URL as URL2 } from "node:url"; import cssValue from "css-value"; import rgb2hex from "rgb2hex"; import GraphemeSplitter from "grapheme-splitter"; import logger24 from "@wdio/logger"; import isPlainObject from "is-plain-obj"; import { ELEMENT_KEY as ELEMENT_KEY18 } from "webdriver"; import { UNICODE_CHARACTERS as UNICODE_CHARACTERS2, asyncIterators, getBrowserObject as getBrowserObject35 } from "@wdio/utils"; // src/commands/browser.ts var browser_exports = {}; __export(browser_exports, { $: () => $, $$: () => $$, SESSION_MOCKS: () => SESSION_MOCKS, action: () => action, actions: () => actions, addInitScript: () => addInitScript, call: () => call, custom$: () => custom$, custom$$: () => custom$$, debug: () => debug, deleteCookies: () => deleteCookies, downloadFile: () => downloadFile, emulate: () => emulate, execute: () => execute, executeAsync: () => executeAsync, getCookies: () => getCookies, getPuppeteer: () => getPuppeteer, getWindowSize: () => getWindowSize, keys: () => keys, mock: () => mock, mockClearAll: () => mockClearAll, mockRestoreAll: () => mockRestoreAll, newWindow: () => newWindow, pause: () => pause, react$: () => react$, react$$: () => react$$, reloadSession: () => reloadSession, restore: () => restore, savePDF: () => savePDF, saveRecordingScreen: () => saveRecordingScreen, saveScreenshot: () => saveScreenshot, scroll: () => scroll, setCookies: () => setCookies, setTimeout: () => setTimeout2, setViewport: () => setViewport, setWindowSize: () => setWindowSize, swipe: () => swipe, switchFrame: () => switchFrame, switchWindow: () => switchWindow, tap: () => tap, throttle: () => throttle, throttleCPU: () => throttleCPU, throttleNetwork: () => throttleNetwork, touchAction: () => touchAction2, uploadFile: () => uploadFile, url: () => url3, waitUntil: () => waitUntil }); // src/utils/getElementObject.ts import { webdriverMonad, wrapCommand } from "@wdio/utils"; import clone from "lodash.clonedeep"; import { ELEMENT_KEY } from "webdriver"; import { getBrowserObject as getBrowserObject2 } from "@wdio/utils"; var WebDriverError = class extends Error { constructor(obj) { const { name, stack } = obj; const { error, stacktrace } = obj; super(error || name || ""); Object.assign(this, { message: obj.message, stack: stacktrace || stack }); } }; function getElement(selector, res, props = { isReactElement: false, isShadowElement: false }) { const browser = getBrowserObject2(this); const browserCommandKeys = Object.keys(browser_exports); const propertiesObject = { /** * filter out browser commands from object */ ...Object.entries(clone(browser.__propertiesObject__)).reduce((commands, [name, descriptor]) => { if (!browserCommandKeys.includes(name)) { commands[name] = descriptor; } return commands; }, {}), ...getPrototype("element"), scope: { value: "element" } }; propertiesObject.emit = { value: this.emit.bind(this) }; const element = webdriverMonad(this.options, (client) => { const elementId = getElementFromResponse(res); if (elementId) { client.elementId = elementId; client[ELEMENT_KEY] = elementId; if (res && this.isBidi && "locator" in res) { client.locator = res.locator; } } else { client.error = res; } if (selector) { client.selector = selector; } client.parent = this; client.isReactElement = props.isReactElement; client.isShadowElement = props.isShadowElement; return client; }, propertiesObject); const elementInstance = element(this.sessionId, elementErrorHandler(wrapCommand)); const origAddCommand = elementInstance.addCommand.bind(elementInstance); elementInstance.addCommand = (name, fn) => { browser.__propertiesObject__[name] = { value: fn }; origAddCommand(name, fn); }; return elementInstance; } var getElements = function getElements2(selector, elemResponse, props = { isReactElement: false, isShadowElement: false }) { const browser = getBrowserObject2(this); const browserCommandKeys = Object.keys(browser_exports); const propertiesObject = { /** * filter out browser commands from object */ ...Object.entries(clone(browser.__propertiesObject__)).reduce((commands, [name, descriptor]) => { if (!browserCommandKeys.includes(name)) { commands[name] = descriptor; } return commands; }, {}), ...getPrototype("element") }; if (elemResponse.length === 0) { return []; } const elements = [elemResponse].flat(1).map((res, i) => { if (res.selector && "$$" in res) { return res; } propertiesObject.scope = { value: "element" }; propertiesObject.emit = { value: this.emit.bind(this) }; const element = webdriverMonad(this.options, (client) => { const elementId = getElementFromResponse(res); if (elementId) { client.elementId = elementId; client[ELEMENT_KEY] = elementId; if (res && this.isBidi && "locator" in res) { client.locator = res.locator; } } else { res = res; client.error = res instanceof Error ? res : new WebDriverError(res); } client.selector = Array.isArray(selector) ? selector[i].selector : selector; client.parent = this; client.index = i; client.isReactElement = props.isReactElement; client.isShadowElement = props.isShadowElement; return client; }, propertiesObject); const elementInstance = element(this.sessionId, elementErrorHandler(wrapCommand)); const origAddCommand = elementInstance.addCommand.bind(elementInstance); elementInstance.addCommand = (name, fn) => { browser.__propertiesObject__[name] = { value: fn }; origAddCommand(name, fn); }; return elementInstance; }); return elements; }; // src/constants.ts import { UNICODE_CHARACTERS, HOOK_DEFINITION } from "@wdio/utils"; var SupportedAutomationProtocols = /* @__PURE__ */ ((SupportedAutomationProtocols2) => { SupportedAutomationProtocols2["webdriver"] = "webdriver"; SupportedAutomationProtocols2["stub"] = "./protocol-stub.js"; return SupportedAutomationProtocols2; })(SupportedAutomationProtocols || {}); var WDIO_DEFAULTS = { /** * allows to specify automation protocol */ automationProtocol: { type: "string", default: "webdriver" /* webdriver */, validate: (param) => { if (param.endsWith("driver.js")) { return; } if (!Object.values(SupportedAutomationProtocols).includes(param.toLowerCase())) { throw new Error(`Currently only "webdriver" and "devtools" is supported as automationProtocol, you set "${param}"`); } } }, /** * capabilities of WebDriver sessions */ capabilities: { type: "object", validate: (param) => { if (typeof param === "object") { return true; } throw new Error('the "capabilities" options needs to be an object or a list of objects'); }, required: true }, /** * Shorten navigateTo command calls by setting a base url */ baseUrl: { type: "string" }, /** * Default interval for all waitFor* commands */ waitforInterval: { type: "number", default: 500 }, /** * Default timeout for all waitFor* commands */ waitforTimeout: { type: "number", default: 5e3 }, /** * Hooks */ onReload: HOOK_DEFINITION, beforeCommand: HOOK_DEFINITION, afterCommand: HOOK_DEFINITION }; var FF_REMOTE_DEBUG_ARG = "-remote-debugging-port"; var DEEP_SELECTOR = ">>>"; var ARIA_SELECTOR = "aria/"; var restoreFunctions = /* @__PURE__ */ new Map(); var Key = { /** * Special control key that works cross browser for Mac, where it's the command key, and for * Windows or Linux, where it is the control key. */ Ctrl: "WDIO_CONTROL", NULL: UNICODE_CHARACTERS.NULL, Cancel: UNICODE_CHARACTERS.Cancel, Help: UNICODE_CHARACTERS.Help, Backspace: UNICODE_CHARACTERS.Backspace, Tab: UNICODE_CHARACTERS.Tab, Clear: UNICODE_CHARACTERS.Clear, Return: UNICODE_CHARACTERS.Return, Enter: UNICODE_CHARACTERS.Enter, Shift: UNICODE_CHARACTERS.Shift, Control: UNICODE_CHARACTERS.Control, Alt: UNICODE_CHARACTERS.Alt, Pause: UNICODE_CHARACTERS.Pause, Escape: UNICODE_CHARACTERS.Escape, Space: UNICODE_CHARACTERS.Space, PageUp: UNICODE_CHARACTERS.PageUp, PageDown: UNICODE_CHARACTERS.PageDown, End: UNICODE_CHARACTERS.End, Home: UNICODE_CHARACTERS.Home, ArrowLeft: UNICODE_CHARACTERS.ArrowLeft, ArrowUp: UNICODE_CHARACTERS.ArrowUp, ArrowRight: UNICODE_CHARACTERS.ArrowRight, ArrowDown: UNICODE_CHARACTERS.ArrowDown, Insert: UNICODE_CHARACTERS.Insert, Delete: UNICODE_CHARACTERS.Delete, Semicolon: UNICODE_CHARACTERS.Semicolon, Equals: UNICODE_CHARACTERS.Equals, Numpad0: UNICODE_CHARACTERS["Numpad 0"], Numpad1: UNICODE_CHARACTERS["Numpad 1"], Numpad2: UNICODE_CHARACTERS["Numpad 2"], Numpad3: UNICODE_CHARACTERS["Numpad 3"], Numpad4: UNICODE_CHARACTERS["Numpad 4"], Numpad5: UNICODE_CHARACTERS["Numpad 5"], Numpad6: UNICODE_CHARACTERS["Numpad 6"], Numpad7: UNICODE_CHARACTERS["Numpad 7"], Numpad8: UNICODE_CHARACTERS["Numpad 8"], Numpad9: UNICODE_CHARACTERS["Numpad 9"], Multiply: UNICODE_CHARACTERS.Multiply, Add: UNICODE_CHARACTERS.Add, Separator: UNICODE_CHARACTERS.Separator, Subtract: UNICODE_CHARACTERS.Subtract, Decimal: UNICODE_CHARACTERS.Decimal, Divide: UNICODE_CHARACTERS.Divide, F1: UNICODE_CHARACTERS.F1, F2: UNICODE_CHARACTERS.F2, F3: UNICODE_CHARACTERS.F3, F4: UNICODE_CHARACTERS.F4, F5: UNICODE_CHARACTERS.F5, F6: UNICODE_CHARACTERS.F6, F7: UNICODE_CHARACTERS.F7, F8: UNICODE_CHARACTERS.F8, F9: UNICODE_CHARACTERS.F9, F10: UNICODE_CHARACTERS.F10, F11: UNICODE_CHARACTERS.F11, F12: UNICODE_CHARACTERS.F12, Command: UNICODE_CHARACTERS.Command, ZenkakuHankaku: UNICODE_CHARACTERS.ZenkakuHankaku }; // src/commands/browser/$$.ts async function $$(selector) { if (this.isBidi && typeof selector === "string" && !selector.startsWith(DEEP_SELECTOR)) { if (globalThis.wdio?.execute) { const command = "$$"; const res3 = "elementId" in this ? await globalThis.wdio.executeWithScope(command, this.elementId, selector) : await globalThis.wdio.execute(command, selector); const elements3 = await getElements.call(this, selector, res3); return enhanceElementsArray(elements3, this, selector); } const res2 = await findDeepElements.call(this, selector); const elements2 = await getElements.call(this, selector, res2); return enhanceElementsArray(elements2, getParent.call(this, res2), selector); } let res = Array.isArray(selector) ? selector : await findElements.call(this, selector); if (Array.isArray(selector) && isElement(selector[0])) { res = []; for (const el of selector) { const $el = await findElement.call(this, el); if ($el) { res.push($el); } } } const elements = await getElements.call(this, selector, res); return enhanceElementsArray(elements, getParent.call(this, res), selector); } function getParent(res) { let parent = res.length > 0 ? res[0].parent || this : this; if (typeof parent.$ === "undefined") { parent = "selector" in parent ? getElement.call(this, parent.selector, parent) : this; } return parent; } // src/commands/browser/$.ts import { ELEMENT_KEY as ELEMENT_KEY2 } from "webdriver"; async function $(selector) { if (globalThis.wdio && typeof selector === "string" && !selector.startsWith(DEEP_SELECTOR)) { const res2 = "elementId" in this ? await globalThis.wdio.executeWithScope("$", this.elementId, selector) : await globalThis.wdio.execute("$", selector); return getElement.call(this, selector, res2); } if (typeof selector === "object") { const elementRef = selector; if (typeof elementRef[ELEMENT_KEY2] === "string") { return getElement.call(this, void 0, elementRef); } } const res = await findElement.call(this, selector); return getElement.call(this, selector, res); } // src/utils/actions/key.ts import os from "node:os"; // src/utils/actions/base.ts import { ELEMENT_KEY as ELEMENT_KEY3 } from "webdriver"; var actionIds = 0; var BaseAction = class { constructor(instance, type, params) { this.instance = instance; this.#instance = instance; this.#id = params?.id || `action${++actionIds}`; this.#type = type; this.#parameters = params?.parameters || {}; } #id; #type; #parameters; #instance; sequence = []; toJSON() { return { id: this.#id, type: this.#type, parameters: this.#parameters, actions: this.sequence }; } /** * Inserts a pause action for the specified device, ensuring it idles for a tick. * @param duration idle time of tick */ pause(duration) { this.sequence.push({ type: "pause", duration }); return this; } /** * Perform action sequence * @param skipRelease set to true if `releaseActions` command should not be invoked */ async perform(skipRelease = false) { for (const seq of this.sequence) { if (!seq.origin || typeof seq.origin === "string") { continue; } if (typeof seq.origin.then === "function") { await seq.origin.waitForExist(); seq.origin = await seq.origin; } if (!seq.origin[ELEMENT_KEY3]) { throw new Error(`Couldn't find element for "${seq.type}" action sequence`); } seq.origin = { [ELEMENT_KEY3]: seq.origin[ELEMENT_KEY3] }; } await this.#instance.performActions([this.toJSON()]); if (!skipRelease) { await this.#instance.releaseActions(); } } }; // src/utils/actions/key.ts var KeyAction = class extends BaseAction { constructor(instance, params) { super(instance, "key", params); } #sanitizeKey(value) { if (typeof value !== "string") { throw new Error(`Invalid type for key input: "${typeof value}", expected a string!`); } const platformName = this.instance.capabilities.platformName; const isMac = ( // check capabilities first platformName && platformName.match(/mac(\s)*os/i) || // if not set, expect we run locally this.instance.options.hostname?.match(/0\.0\.0\.0|127\.0\.0\.1|local/i) && os.type().match(/darwin/i) ); if (value === Key.Ctrl) { return isMac ? Key.Command : Key.Control; } if (value.length > 1) { throw new Error(`Your key input contains more than one character: "${value}", only one is allowed though!`); } return value; } /** * Generates a key up action. * @param value key value */ up(value) { this.sequence.push({ type: "keyUp", value: this.#sanitizeKey(value) }); return this; } /** * Generates a key down action. * @param value key value */ down(value) { this.sequence.push({ type: "keyDown", value: this.#sanitizeKey(value) }); return this; } }; // src/utils/actions/pointer.ts var buttonNumbers = [0, 1, 2]; var buttonNames = ["left", "middle", "right"]; var buttonValue = [...buttonNumbers, ...buttonNames]; var ORIGIN_DEFAULT = "viewport"; var BUTTON_DEFAULT = 0; var POINTER_TYPE_DEFAULT = "mouse"; var UP_PARAM_DEFAULTS = { button: BUTTON_DEFAULT }; var PARAM_DEFAULTS = { ...UP_PARAM_DEFAULTS, width: 0, height: 0, pressure: 0, tangentialPressure: 0, tiltX: 0, tiltY: 0, twist: 0, altitudeAngle: 0, azimuthAngle: 0 }; var MOVE_PARAM_DEFAULTS = { x: 0, y: 0, duration: 100, origin: ORIGIN_DEFAULT }; function removeDefaultParams(seq) { for (const [key, value] of Object.entries(seq)) { if (value === 0 && !["x", "y", "button", "duration"].includes(key)) { delete seq[key]; } } } function mapButton(params) { const buttons = { left: 0, middle: 1, right: 2 }; if (typeof params === "number") { return { button: params }; } if (typeof params === "string") { return { button: buttons[params] }; } if (typeof params === "object" && typeof params.button === "string") { return { ...params, button: buttons[params.button] }; } return params; } var PointerAction = class extends BaseAction { constructor(instance, params = {}) { if (!params.parameters) { params.parameters = { pointerType: POINTER_TYPE_DEFAULT }; } super(instance, "pointer", params); } move(params = {}, y) { const seq = { type: "pointerMove", // default params ...PARAM_DEFAULTS, ...UP_PARAM_DEFAULTS, ...MOVE_PARAM_DEFAULTS }; if (typeof params === "number") { Object.assign(seq, { x: params, y }); } else if (params) { Object.assign(seq, params); } removeDefaultParams(seq); this.sequence.push(seq); return this; } up(params = UP_PARAM_DEFAULTS) { this.sequence.push({ type: "pointerUp", ...mapButton(params) }); return this; } down(params = {}) { const seq = { type: "pointerDown", ...PARAM_DEFAULTS, ...mapButton(params) }; removeDefaultParams(seq); this.sequence.push(seq); return this; } /** * An action that cancels this pointer's current input. */ cancel() { this.sequence.push({ type: "pointerCancel" }); return this; } }; // src/utils/actions/wheel.ts var DEFAULT_SCROLL_PARAMS = { x: 0, y: 0, deltaX: 0, deltaY: 0, duration: 0 }; var WheelAction = class extends BaseAction { constructor(instance, params) { super(instance, "wheel", params); } /** * Scrolls a page to given coordinates or origin. */ scroll(params) { this.sequence.push({ type: "scroll", ...DEFAULT_SCROLL_PARAMS, ...params }); return this; } }; // src/commands/browser/action.ts function action(type, opts) { if (type === "key") { return new KeyAction(this, opts); } if (type === "pointer") { return new PointerAction(this, opts); } if (type === "wheel") { return new WheelAction(this, opts); } throw new Error(`Unsupported action type "${type}", supported are "key", "pointer", "wheel"`); } // src/commands/browser/actions.ts async function actions(actions2) { await this.performActions(actions2.map((action2) => action2.toJSON())); await this.releaseActions(); } // src/commands/browser/addInitScript.ts import { EventEmitter } from "node:events"; // src/utils/bidi/index.ts import { ELEMENT_KEY as ELEMENT_KEY4 } from "webdriver"; // src/commands/constant.ts var TOUCH_ACTIONS = ["press", "longPress", "tap", "moveTo", "wait", "release"]; var POS_ACTIONS = TOUCH_ACTIONS.slice(0, 4); var ACCEPTED_OPTIONS = ["x", "y", "element"]; var SCRIPT_PREFIX = "/* __wdio script__ */"; var SCRIPT_SUFFIX = "/* __wdio script end__ */"; var formatArgs = function(scope, actions2) { return actions2.map((action2) => { if (Array.isArray(action2)) { return formatArgs(scope, action2); } if (typeof action2 === "string") { action2 = { action: action2 }; } const formattedAction = { action: action2.action, options: {} }; const actionElement = action2.element && typeof action2.element.elementId === "string" ? action2.element.elementId : scope.elementId; if (POS_ACTIONS.includes(action2.action) && formattedAction.options && actionElement) { formattedAction.options.element = actionElement; } if (formattedAction.options && typeof action2.x === "number" && isFinite(action2.x)) { formattedAction.options.x = action2.x; } if (formattedAction.options && typeof action2.y === "number" && isFinite(action2.y)) { formattedAction.options.y = action2.y; } if (formattedAction.options && action2.ms) { formattedAction.options.ms = action2.ms; } if (formattedAction.options && Object.keys(formattedAction.options).length === 0) { delete formattedAction.options; } return formattedAction; }); }; var validateParameters = (params) => { const options = Object.keys(params.options || {}); if (params.action === "release" && options.length !== 0) { throw new Error( `action "release" doesn't accept any options ("${options.join('", "')}" found)` ); } if (params.action === "wait" && (options.includes("x") || options.includes("y"))) { throw new Error(`action "wait" doesn't accept x or y options`); } if (POS_ACTIONS.includes(params.action)) { for (const option in params.options) { if (!ACCEPTED_OPTIONS.includes(option)) { throw new Error(`action "${params.action}" doesn't accept "${option}" as option`); } } if (options.length === 0) { throw new Error( `Touch actions like "${params.action}" need at least some kind of position information like "element", "x" or "y" options, you've none given.` ); } } }; var touchAction = function(actions2) { if (!this.multiTouchPerform || !this.touchPerform) { throw new Error("touchAction can be used with Appium only."); } if (!Array.isArray(actions2)) { actions2 = [actions2]; } const formattedAction = formatArgs(this, actions2); const protocolCommand = Array.isArray(actions2[0]) ? this.multiTouchPerform.bind(this) : this.touchPerform.bind(this); formattedAction.forEach((params) => validateParameters(params)); return protocolCommand(formattedAction); }; // src/utils/bidi/error.ts var WebdriverBidiExeception = class extends Error { #params; #result; constructor(params, result) { super(result.exceptionDetails.text); this.name = "WebdriverBidiExeception"; this.#params = params; this.#result = result; this.stack = this.#getCustomStack(); } #getCustomStack() { const origStack = this.stack; const failureLine = this.#getFailureLine(); const stack = origStack?.split("\n") || []; const wrapCommandIndex = stack.findLastIndex((line) => line.includes("Context.executeAsync")); const executeLine = stack[wrapCommandIndex - 1]; if (failureLine && executeLine) { const line = executeLine.replace("file://", "").split(":"); const row = line.length > 3 ? line[2] : line[1]; const [errorMessage, ...restOfStack] = stack; const linePrefix = ` ${row} \u2502 `; const codeLine = [ linePrefix + failureLine, " ".repeat(linePrefix.length - 2) + "\u2575 " + "~".repeat(failureLine.length), "" ]; return [errorMessage, executeLine, ...codeLine, ...restOfStack].join("\n"); } return origStack; } /** * This is an attempt to identify the snippet of code that caused an execute(Async) function to * throw an exception * @param {string} script script that executed in the browser * @param {number} columnNumber column in which the scrpt threw an exception * @returns the line of failure in which the code threw an exception or `undefined` if we could not find it */ #getFailureLine() { const script = this.#params.functionDeclaration; const exceptionDetails = this.#result.exceptionDetails; const userScript = script.split("\n").find((l) => l.includes(SCRIPT_PREFIX)); if (!userScript) { return; } let length = 0; const isMinified = script.split("\n").some((line) => line.includes(SCRIPT_PREFIX) && line.includes(SCRIPT_SUFFIX)); if (isMinified) { for (const line of userScript.split(";")) { if (length + line.length >= exceptionDetails.columnNumber) { return line.includes(SCRIPT_SUFFIX) ? line.slice(0, line.indexOf(SCRIPT_SUFFIX)) : line; } length += line.length; } } else { const slicedScript = script.slice( script.indexOf(SCRIPT_PREFIX) + SCRIPT_PREFIX.length, script.indexOf(SCRIPT_SUFFIX) ); const lineDiff = 9; const line = slicedScript.split("\n")[exceptionDetails.lineNumber - lineDiff]?.slice(exceptionDetails.columnNumber); return line; } return void 0; } }; // src/utils/bidi/index.ts function parseScriptResult(params, result) { const type = result.type; if (type === "success" /* Success */) { return deserialize(result.result); } if (type === "exception" /* Exception */) { throw new WebdriverBidiExeception(params, result); } throw new Error(`Unknown evaluate result type: ${type}`); } var references = /* @__PURE__ */ new Map(); function deserialize(result) { const deserializedValue = deserializeValue(result); references.clear(); return deserializedValue; } function deserializeValue(result) { if (result && "internalId" in result && typeof result.internalId === "string") { if ("value" in result) { references.set(result.internalId, result.value); } else { result.value = references.get(result.internalId); } } const { type, value } = result; if (type === "regexp" /* RegularExpression */) { return new RegExp(value.pattern, value.flags); } if (type === "array" /* Array */) { return value.map((element) => deserializeValue(element)); } if (type === "date" /* Date */) { return new Date(value); } if (type === "map" /* Map */) { return new Map(value.map(([key, value2]) => [typeof key === "string" ? key : deserializeValue(key), deserializeValue(value2)])); } if (type === "set" /* Set */) { return new Set(value.map((element) => deserializeValue(element))); } if (type === "number" /* Number */ && value === "NaN") { return NaN; } if (type === "number" /* Number */ && value === "Infinity") { return Infinity; } if (type === "number" /* Number */ && value === "-Infinity") { return -Infinity; } if (type === "number" /* Number */ && value === "-0") { return -0; } if (type === "bigint" /* BigInt */) { return BigInt(value); } if (type === "null" /* Null */) { return null; } if (type === "object" /* Object */) { return Object.fromEntries((value || []).map(([key, value2]) => { return [typeof key === "string" ? key : deserializeValue(key), deserializeValue(value2)]; })); } if (type === "node" /* Node */) { return { [ELEMENT_KEY4]: result.sharedId }; } if (type === "error" /* Error */) { return new Error("<unserializable error>"); } return value; } // src/commands/browser/addInitScript.ts async function addInitScript(script, ...args) { if (typeof script !== "function") { throw new Error("The `addInitScript` command requires a function as first parameter, but got: " + typeof script); } if (!this.isBidi) { throw new Error("This command is only supported when automating browser using WebDriver Bidi protocol"); } const serializedParameters = (args || []).map((arg) => JSON.stringify(arg)); const context = await this.getWindowHandle(); const fn = `(emit) => { const closure = new Function(\`return ${script.toString()}\`) return closure()(${serializedParameters.length ? `${serializedParameters.join(", ")}, emit` : "emit"}) }`; const channel = btoa(fn.toString()); const result = await this.scriptAddPreloadScript({ functionDeclaration: fn, arguments: [{ type: "channel", value: { channel } }], contexts: [context] }); await this.sessionSubscribe({ events: ["script.message"] }); const emitter = new EventEmitter(); const messageHandler = (msg) => { if (msg.channel === channel) { emitter.emit("data", deserialize(msg.data)); } }; this.on("script.message", messageHandler); const resetFn = () => { this.off("script.message", messageHandler); return this.scriptRemovePreloadScript({ script: result.script }); }; const returnVal = { remove: resetFn, on: emitter.on.bind(emitter) }; return returnVal; } // src/commands/browser/call.ts function call(fn) { if (typeof fn === "function") { return fn(); } throw new Error('Command argument for "call" needs to be a function'); } // src/commands/browser/custom$$.ts import { ELEMENT_KEY as ELEMENT_KEY5 } from "webdriver"; async function custom$$(strategyName, ...strategyArguments) { const strategy = this.strategies.get(strategyName); if (!strategy) { throw Error("No strategy found for " + strategyName); } const strategyRef = { strategy, strategyName, strategyArguments }; let res = await this.execute(strategy, ...strategyArguments); if (!Array.isArray(res)) { res = [res]; } res = res.filter((el) => !!el && typeof el[ELEMENT_KEY5] === "string"); const elements = res.length ? await getElements.call(this, strategyRef, res) : []; return enhanceElementsArray(elements, this, strategyName, "custom$$", strategyArguments); } // src/commands/browser/custom$.ts import { ELEMENT_KEY as ELEMENT_KEY6 } from "webdriver"; async function custom$(strategyName, ...strategyArguments) { const strategy = this.strategies.get(strategyName); if (!strategy) { throw Error("No strategy found for " + strategyName); } const strategyRef = { strategy, strategyName, strategyArguments }; let res = await this.execute(strategy, ...strategyArguments); if (Array.isArray(res)) { res = res[0]; } if (res && typeof res[ELEMENT_KEY6] === "string") { return await getElement.call(this, strategyRef, res); } return await getElement.call(this, strategyRef, new Error("no such element")); } // src/commands/browser/debug.ts import { serializeError } from "serialize-error"; import WDIORepl from "@wdio/repl"; function debug(commandTimeout = 5e3) { const repl = new WDIORepl(); const { introMessage } = WDIORepl; if (!process.env.WDIO_WORKER_ID || typeof process.send !== "function") { console.log(WDIORepl.introMessage); const context = { browser: this, driver: this, $: this.$.bind(this), $$: this.$$.bind(this) }; return repl.start(context); } process._debugProcess(process.pid); process.send({ origin: "debugger", name: "start", params: { commandTimeout, introMessage } }); let commandResolve = ( /* istanbul ignore next */ () => { } ); process.on("message", (m) => { if (m.origin !== "debugger") { return; } if (m.name === "stop") { process._debugEnd(process.pid); return commandResolve(); } if (m.name === "eval") { repl.eval(m.content.cmd, global, void 0, (err, result) => { if (typeof process.send !== "function") { return; } if (err) { process.send({ origin: "debugger", name: "result", params: { error: true, ...serializeError(err) } }); } if (typeof result === "function") { result = `[Function: ${result.name}]`; } process.send({ origin: "debugger", name: "result", params: { result } }); }); } }); return new Promise((resolve5) => commandResolve = resolve5); } // src/commands/browser/deleteCookies.ts async function deleteCookies(filter) { const filterArray = typeof filter === "undefined" ? void 0 : Array.isArray(filter) ? filter : [filter]; if (!this.isBidi) { const names = filterArray?.map((f) => { if (typeof f === "object") { const name = f.name; if (!name) { throw new Error("In WebDriver Classic you can only filter for cookie names"); } return name; } if (typeof f === "string") { return f; } throw new Error(`Invalid value for cookie filter, expected 'string' or 'remote.StorageCookieFilter' but found "${typeof f}"`); }); await deleteCookiesClassic.call(this, names); return; } if (!filterArray) { await this.storageDeleteCookies({}); return; } const bidiFilter = filterArray.map((f) => { if (typeof f === "string") { return { name: f }; } if (typeof f === "object") { return f; } throw new Error(`Invalid value for cookie filter, expected 'string' or 'remote.StorageCookieFilter' but found "${typeof f}"`); }); await Promise.all(bidiFilter.map((filter2) => this.storageDeleteCookies({ filter: filter2 }))); return; } function deleteCookiesClassic(names) { if (names === void 0) { return this.deleteAllCookies(); } const namesList = Array.isArray(names) ? names : [names]; if (namesList.every((obj) => typeof obj !== "string")) { return Promise.reject(new Error("Invalid input (see https://webdriver.io/docs/api/browser/deleteCookies for documentation)")); } return Promise.all(namesList.map((name) => this.deleteCookie(name))); } // src/commands/browser/downloadFile.ts import fs from "node:fs"; import path from "node:path"; import JSZip from "jszip"; import logger2 from "@wdio/logger"; var log2 = logger2("webdriverio"); async function downloadFile(fileName, targetDirectory) { if (typeof fileName !== "string" || typeof targetDirectory !== "string") { throw new Error("number or type of arguments don't agree with downloadFile command"); } if (typeof this.download !== "function") { throw new Error(`The downloadFile command is not available in ${this.capabilities.browserName} and only available when using Selenium Grid`); } const response = await this.download(fileName); const base64Content = response.contents; if (!targetDirectory.endsWith("/")) { targetDirectory += "/"; } fs.mkdirSync(targetDirectory, { recursive: true }); const zipFilePath = path.join(targetDirectory, `${fileName}.zip`); const binaryString = atob(base64Content); const bytes = Uint8Array.from(binaryString, (char) => char.charCodeAt(0)); fs.writeFileSync(zipFilePath, bytes); const zipData = fs.readFileSync(zipFilePath); const filesData = []; try { const zip2 = await JSZip.loadAsync(zipData); const keys2 = Object.keys(zip2.files); for (let i = 0; i < keys2.length; i++) { const fileData = await zip2.files[keys2[i]].async("nodebuffer"); const dir = path.resolve(targetDirectory, keys2[i]); fs.writeFileSync(dir, fileData); log2.info(`File extracted: ${keys2[i]}`); filesData.push(dir); } } catch (error) { log2.error("Error unzipping file:", error); } return Promise.resolve({ files: filesData }); } // src/clock.ts import logger3 from "@wdio/logger"; var log3 = logger3("webdriverio:ClockManager"); function installFakeTimers(options) { window.__clock = window.__wdio_sinon.install(options); } function uninstallFakeTimers() { window.__clock.uninstall(); } var ClockManager = class { #browser; #resetFn = () => Promise.resolve(); #isInstalled = false; constructor(browser) { this.#browser = browser; } /** * Install fake timers on the browser. If you call the `emulate` command, WebdriverIO will automatically install * the fake timers for you. You can use this method to re-install the fake timers if you have called `restore`. * * @param options {FakeTimerInstallOpts} Options to pass to the fake clock * @returns {Promise<void>} */ async install(options) { if (this.#isInstalled) { return log3.warn("Fake timers are already installed"); } if (globalThis.window) { return; } const url6 = await import("node:url"); const path4 = await import("node:path"); const fs13 = await import("node:fs/promises"); const __dirname = path4.dirname(url6.fileURLToPath(import.meta.url)); const rootDir = path4.resolve(__dirname, ".."); const emulateOptions = options || {}; const scriptPath = path4.join(rootDir, "third_party", "fake-timers.js"); const functionDeclaration = await fs13.readFile(scriptPath, "utf-8"); const installOptions = { ...emulateOptions, now: emulateOptions.now && emulateOptions.now instanceof Date ? emulateOptions.now.getTime() : emulateOptions.now }; const [, libScript, restoreInstallScript] = await Promise.all([ /** * install fake timers for current ex */ this.#browser.executeScript(`return (${functionDeclaration}).apply(null, arguments)`, []).then(() => this.#browser.execute(installFakeTimers, installOptions)), /** * add preload script to to emulate clock for upcoming page loads */ this.#browser.scriptAddPreloadScript({ functionDeclaration }), this.#browser.addInitScript(installFakeTimers, installOptions) ]); this.#resetFn = async () => Promise.all([ this.#browser.scriptRemovePreloadScript({ script: libScript.script }), this.#browser.execute(uninstallFakeTimers), restoreInstallScript ]); this.#isInstalled = true; } /** * Restore all overridden native functions. This is automatically called between tests, so should not * generally be needed. * * ```ts * it('should restore the clock', async () => { * console.log(new Date()) // returns e.g. 1722560447102 * * const clock = await browser.emulate('clock', { now: new Date(2021, 3, 14) }) * console.log(await browser.execute(() => new Date().getTime())) // returns 1618383600000 * * await clock.restore() * console.log(await browser.execute(() => new Date().getTime())) // returns 1722560447102 * }) * ``` * * @returns {Promise<void>} */ async restore() { await this.#resetFn(); this.#isInstalled = false; } /** * Move the clock the specified number of `milliseconds`. Any timers within the affected range of time will be called. * @param ms {number} The number of milliseconds to move the clock. * * ```ts * it('should move the clock', async () => { * console.log(new Date()) // returns e.g. 1722560447102 * * const clock = await browser.emulate('clock', { now: new Date(2021, 3, 14) }) * console.log(await browser.execute(() => new Date().getTime())) // returns 1618383600000 * * await clock.tick(1000) * console.log(await browser.execute(() => new Date().getTime())) // returns 1618383601000 * }) * ``` * * @param {number} ms The number of milliseconds to move the clock. * @returns {Promise<void>} */ async tick(ms) { await this.#browser.execute((ms2) => window.__clock.tick(ms2), ms); } /** * Change the system time to the new now. Now can be a timestamp, date object, or not passed in which defaults * to 0. No timers will be called, nor will the time left before they trigger change. * * ```ts * it('should set the system time', async () => { * const clock = await browser.emulate('clock', { now: new Date(2021, 3, 14) }) * console.log(await browser.execute(() => new Date().getTime())) // returns 1618383600000 * * await clock.setSystemTime(new Date(2011, 3, 15)) * console.log(await browser.execute(() => new Date().getTime())) // returns 1302850800000 * }) * ``` * * @param date {Date|number} The new date to set the system time to. * @returns {Promise<void>} */ async setSystemTime(date) { const serializableSystemTime = date instanceof Date ? date.getTime() : date; await this.#browser.execute((date2) => window.__clock.setSystemTime(date2), serializableSystemTime); } }; // src/deviceDescriptorsSource.ts var deviceDescriptorsSource = { "Blackberry PlayBook": { userAgent: "Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/18.0 Safari/536.2+", viewport: { width: 600, height: 1024 }, deviceScaleFactor: 1, isMobile: true, hasTouch: true }, "Blackberry PlayBook landscape": { userAgent: "Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/18.0 Safari/536.2+", viewport: { width: 1024, height: 600 }, deviceScaleFactor: 1, isMobile: true, hasTouch: true }, "BlackBerry Z30": { userAgent: "Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/18.0 Mobile Safari/537.10+", viewport: { width: 360, height: 640 }, deviceScaleFactor: 2, isMobile: true, hasTouch: true }, "BlackBerry Z30 landscape": { userAgent: "Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/18.0 Mobile Safari/537.10+", viewport: { width: 640, height: 360 }, deviceScaleFactor: 2, isMobile: true, hasTouch: true }, "Galaxy Note 3": { userAgent: "Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/18.0 Mobile Safari/534.30", viewport: { width: 360, height: 640 }, deviceScaleFactor: 3, isMobile: true, hasTouch: true }, "Galaxy Note 3 landscape": { userAgent: "Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/18.0 Mobile Safari/534.30", viewport: { width: 640, height: 360 }, deviceScaleFactor: 3, isMobile: true, hasTouch: true }, "Galaxy Note II": { userAgent: "Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/18.0 Mobile Safari/534.30", viewport: { width: 360, height: 640 }, deviceScaleFactor: 2, isMobile: true, hasTouch: true }, "Galaxy Note II landscape": { userAgent: "Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/18.0 Mobile Safari/534.30", viewport: { width: 640, height: 360 }, deviceScaleFactor: 2, isMobile: true, hasTouch: true }, "Galaxy S III": { userAgent: "Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/18.0 Mobile Safari/534.30", viewport: { width: 360, height: 640 }, deviceScaleFactor: 2, isMobile: true, hasTouch: true }, "Galaxy S III landscape": { userAgent: "Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/18.0 Mobile Safari/534.30", viewport: { width: 640, height: 360 }, deviceScaleFactor: 2, isMobile: true, hasTouch: true }, "Galaxy S5": { userAgent: "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Mobile Safari/537.36", viewport: { width: 360, height: 640 }, deviceScaleFactor: 3, isMobile: true, hasTouch: true }, "Galaxy S5 landscape": { userAgent: "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Mobile Safari/537.36", viewport: { width: 640, height: 360 }, deviceScaleFactor: 3, isMobile: true, hasTouch: true }, "Galaxy S8": { userAgent: "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Mobile Safari/537.36", viewport: { width: 360, height: 740 }, deviceScaleFactor: 3, isMobile: true, hasTouch: true }, "Galaxy S8 landscape": { userAgent: "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Mobile Safari/537.36", viewport: { width: 740, height: 360 }, deviceScaleFactor: 3, isMobile: true, hasTouch: true }, "Galaxy S9 +": { userAgent: "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Mobile Safari/537.36", viewport: { width: 320, height: 658 }, deviceScaleFactor: 4.5, isMobile: true, hasTouch: true }, "Galaxy S9 + landscape": { userAgent: "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Mobile Safari/537.36", viewport: { width: 658, height: 320 }, deviceScaleFactor: 4.5, isMobile: true, hasTouch: true }, "Galaxy Tab S4": { userAgent: "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Safari/537.36", viewport: { width: 712, height: 1138 }, deviceScaleFactor: 2.25, isMobile: true, hasTouch: true }, "Galaxy Tab S4 landscape": { userAgent: "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Safari/537.36", viewport: { width: 1138, height: 712 }, deviceScaleFactor: 2.25, isMobile: true, hasTouch: true }, "iPad(gen 5)": { userAgent: "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1", viewport: { width: 768, height: 1024 }, deviceScaleFactor: 2, isMobile: true, hasTouch: true }, "iPad(gen 5) landscape": { userAgent: "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1", viewport: { width: 1024, height: 768 }, deviceScaleFactor: 2, isMobile: true, hasTouch: true }, "iPad(gen 6)": { userAgent: "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1", viewport: { width: 768, height: 1024 }, deviceScaleFactor: 2, isMobile: true, hasTouch: true }, "iPad(gen 6) landscape": { userAgent: "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1", viewport: { width: 1024, height: 768 }, deviceScaleFactor: 2, isMobile: true, hasTouch: true }, "iPad(gen 7)": { userAgent: "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1", viewport: { width: 810, height: 1080 }, deviceScaleFactor: 2, isMobile: true, hasTouch: true }, "iPad(gen 7) landscape": { userAgent: "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1", viewport: { width: 1080, height: 8