UNPKG

webdriverio

Version:

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

1,261 lines (1,226 loc) 388 kB
var __defProp = Object.defineProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; // src/node.ts import os from "node:os"; import fs8 from "node:fs"; // src/node/downloadFile.ts import fs from "node:fs"; import path from "node:path"; import JSZip from "jszip"; import logger from "@wdio/logger"; var log = logger("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); log.info(`File extracted: ${keys2[i]}`); filesData.push(dir); } } catch (error) { log.error("Error unzipping file:", error); } return Promise.resolve({ files: filesData }); } // src/node/savePDF.ts import fs3 from "node:fs"; // src/node/utils.ts import path2 from "node:path"; import fs2 from "node:fs/promises"; async function assertDirectoryExists(filepath) { const exist = await fs2.access(path2.dirname(filepath)).then(() => true, () => false); if (!exist) { throw new Error(`directory (${path2.dirname(filepath)}) doesn't exist`); } } function getAbsoluteFilepath(filepath) { return filepath.startsWith("/") || filepath.startsWith("\\") || filepath.match(/^[a-zA-Z]:\\/) ? filepath : path2.join(process.cwd(), filepath); } // src/node/savePDF.ts async function savePDF(filepath, options) { if (typeof filepath !== "string" || !filepath.endsWith(".pdf")) { throw new Error('savePDF expects a filepath of type string and ".pdf" file ending'); } const absoluteFilepath = getAbsoluteFilepath(filepath); await assertDirectoryExists(absoluteFilepath); const pdf = await this.printPage( options?.orientation, options?.scale, options?.background, options?.width, options?.height, options?.top, options?.bottom, options?.left, options?.right, options?.shrinkToFit, options?.pageRanges ); const page = Buffer.from(pdf, "base64"); fs3.writeFileSync(absoluteFilepath, page); return page; } // src/node/saveRecordingScreen.ts import fs4 from "node:fs"; async function saveRecordingScreen(filepath) { if (typeof filepath !== "string") { throw new Error("saveRecordingScreen expects a filepath"); } const absoluteFilepath = getAbsoluteFilepath(filepath); await assertDirectoryExists(absoluteFilepath); const videoBuffer = await this.stopRecordingScreen(); const video = Buffer.from(videoBuffer, "base64"); fs4.writeFileSync(absoluteFilepath, video); return video; } // src/node/uploadFile.ts import fs5 from "node:fs"; import path3 from "node:path"; import archiver from "archiver"; async function uploadFile(localPath) { if (typeof localPath !== "string") { throw new Error("number or type of arguments don't agree with uploadFile command"); } if (typeof this.file !== "function") { throw new Error(`The uploadFile command is not available in ${this.capabilities.browserName}`); } const zipData = []; const source = fs5.createReadStream(localPath); return new Promise((resolve, reject) => { archiver("zip").on("error", (err) => reject(err)).on("data", (data) => zipData.push(data)).on("end", () => this.file(Buffer.concat(zipData).toString("base64")).then((localPath2) => resolve(localPath2), reject)).append(source, { name: path3.basename(localPath) }).finalize(); }); } // src/node/saveScreenshot.ts import fs6 from "node:fs/promises"; import path4 from "node:path"; import { getBrowserObject } from "@wdio/utils"; // src/session/context.ts import logger2 from "@wdio/logger"; // src/session/session.ts var sessionManager = /* @__PURE__ */ new Map(); var listenerRegisteredSession = /* @__PURE__ */ new Set(); var SessionManager = class { #browser; #scope; /** * SessionManager constructor * Logic in here should be executed for all session singletons, e.g. remove instance * of itself when a session was deleted. * @param browser WebdriverIO.Browser * @param scope scope of the session manager, e.g. context, network etc. */ constructor(browser2, scope) { this.#browser = browser2; this.#scope = scope; const registrationId = `${this.#browser.sessionId}-${this.#scope}`; if (!listenerRegisteredSession.has(registrationId)) { this.#browser.on("command", this.#onCommand.bind(this)); listenerRegisteredSession.add(registrationId); } } #onCommand(ev) { if (ev.command === "deleteSession") { const sessionManagerInstances = sessionManager.get(this.#scope); const sessionManagerInstance = sessionManagerInstances?.get(this.#browser); if (sessionManagerInstance && sessionManagerInstances) { sessionManagerInstance.removeListeners(); sessionManagerInstances.delete(this.#browser); } } } removeListeners() { this.#browser.off("command", this.#onCommand.bind(this)); } initialize() { return void 0; } /** * check if session manager should be enabled, if */ isEnabled() { return ( // we are in a Bidi session this.#browser.isBidi && // we are not running unit tests !process.env.WDIO_UNIT_TESTS ); } static getSessionManager(browser2, Manager) { const scope = Manager.name; let sessionManagerInstances = sessionManager.get(scope); if (!sessionManagerInstances) { sessionManagerInstances = /* @__PURE__ */ new Map(); sessionManager.set(scope, sessionManagerInstances); } let sessionManagerInstance = sessionManagerInstances.get(browser2); if (!sessionManagerInstance) { sessionManagerInstance = new Manager(browser2); sessionManagerInstances.set(browser2, sessionManagerInstance); } return sessionManagerInstance; } }; // src/utils/mobile.ts var appiumKeys = ["app", "bundleId", "appPackage", "appActivity", "appWaitActivity", "appWaitPackage"]; function getNativeContext({ capabilities, isMobile }) { if (!capabilities || typeof capabilities !== "object" || !isMobile) { return false; } const isAppiumAppCapPresent = (capabilities2) => { return appiumKeys.some((key) => capabilities2[key] !== void 0 || capabilities2["appium:options"]?.[key] !== void 0 || capabilities2["lt:options"]?.[key] !== void 0); }; const isBrowserNameFalse = !!capabilities?.browserName === false; const isAutoWebviewFalse = !// @ts-expect-error (capabilities?.autoWebview === true || capabilities["appium:autoWebview"] === true || capabilities["appium:options"]?.autoWebview === true || capabilities["lt:options"]?.autoWebview === true); return isBrowserNameFalse && isAppiumAppCapPresent(capabilities) && isAutoWebviewFalse; } function getMobileContext({ capabilities, isAndroid, isNativeContext }) { return isNativeContext ? "NATIVE_APP" : ( // Android webviews are always WEBVIEW_<package_name>, Chrome will always be CHROMIUM // We can only determine it for Android and Chrome, for all other, including iOS, we return undefined isAndroid && capabilities?.browserName?.toLowerCase() === "chrome" ? "CHROMIUM" : void 0 ); } function calculateAndroidPinchAndZoomSpeed({ browser: browser2, duration, scale }) { const deviceScreenSize = (browser2.capabilities?.deviceScreenSize || "1080x2400").split("x").reduce((a, b) => a * b); const baseDistance = Math.sqrt(deviceScreenSize); const gestureDistance = Math.max(baseDistance * Math.abs(scale), baseDistance * 0.1); const durationSeconds = duration / 1e3; return Math.floor(gestureDistance / durationSeconds); } function validatePinchAndZoomOptions({ browser: browser2, gesture, options }) { if (typeof options !== "undefined" && (typeof options !== "object" || Array.isArray(options))) { throw new TypeError("Options must be an object"); } const DEFAULT_SCALE = 0.5; const DEFAULT_DURATION = browser2.isIOS ? 1.5 : 1500; const MIN_SCALE = 0; const MAX_SCALE = 1; const MIN_DURATION_MS = 500; const MAX_DURATION_MS = 1e4; const { scale: scaleOption, duration: durationOption } = options; const scale = typeof scaleOption === "number" ? scaleOption >= MIN_SCALE && scaleOption <= MAX_SCALE ? scaleOption : (() => { throw new Error(`The 'scale' option must be a number between ${MIN_SCALE} and ${MAX_SCALE}`); })() : DEFAULT_SCALE; const duration = typeof durationOption === "number" ? durationOption >= MIN_DURATION_MS && durationOption <= MAX_DURATION_MS ? browser2.isIOS ? durationOption / 1e3 : durationOption : (() => { throw new Error(`The 'duration' option must be between ${MIN_DURATION_MS} and ${MAX_DURATION_MS} ms (${MIN_DURATION_MS / 1e3} and ${MAX_DURATION_MS / 1e3} seconds)`); })() : DEFAULT_DURATION; return { duration, scale: browser2.isIOS && gesture === "zoom" ? scale * 10 : scale }; } // src/session/context.ts var log2 = logger2("webdriverio:context"); var COMMANDS_REQUIRING_RESET = ["deleteSession", "refresh", "switchToParentFrame"]; function getContextManager(browser2) { return SessionManager.getSessionManager(browser2, ContextManager); } var ContextManager = class _ContextManager extends SessionManager { #browser; #currentContext; #mobileContext; #isNativeContext; #getContextSupport = true; constructor(browser2) { super(browser2, _ContextManager.name); this.#browser = browser2; const capabilities = this.#browser.capabilities; this.#isNativeContext = getNativeContext({ capabilities, isMobile: this.#browser.isMobile }); this.#mobileContext = getMobileContext({ capabilities, isAndroid: this.#browser.isAndroid, isNativeContext: this.#isNativeContext }); this.#browser.on("result", this.#onCommandResultBidiAndClassic.bind(this)); if (!this.isEnabled() && !this.#browser.isMobile) { return; } this.#browser.on("command", this.#onCommand.bind(this)); if (this.#browser.isMobile) { this.#browser.on("result", this.#onCommandResultMobile.bind(this)); } else { this.#browser.sessionSubscribe({ events: ["browsingContext.navigationStarted"] }); this.#browser.on("browsingContext.navigationStarted", async (nav) => { if (!this.#currentContext || nav.context === this.#currentContext) { return; } const { contexts } = await this.#browser.browsingContextGetTree({}); const hasContext = this.findContext(this.#currentContext, contexts, "byContextId"); const newContext = contexts.find((context) => context.context === nav.context); if (!hasContext && newContext) { this.setCurrentContext(newContext.context); this.#browser.switchToWindow(this.#currentContext); return; } }); } } removeListeners() { super.removeListeners(); this.#browser.off("result", this.#onCommandResultBidiAndClassic.bind(this)); this.#browser.off("command", this.#onCommand.bind(this)); if (this.#browser.isMobile) { this.#browser.off("result", this.#onCommandResultMobile.bind(this)); } } #onCommandResultBidiAndClassic(event) { if (event.command === "closeWindow") { const windowHandles = event.result.value; if (windowHandles.length === 0) { throw new Error("All window handles were removed, causing WebdriverIO to close the session."); } this.#currentContext = windowHandles[0]; return this.#browser.switchToWindow(this.#currentContext); } } #onCommand(event) { if (event.command === "switchToParentFrame") { if (!this.#currentContext) { return; } return this.#browser.browsingContextGetTree({}).then(({ contexts }) => { const parentContext = this.findParentContext(this.#currentContext, contexts); if (!parentContext) { return; } this.setCurrentContext(parentContext.context); }); } if (event.command === "switchToWindow") { this.setCurrentContext(event.body.handle); } if (COMMANDS_REQUIRING_RESET.includes(event.command)) { this.#currentContext = void 0; } if (this.#browser.isMobile && event.command === "switchAppiumContext") { this.#mobileContext = event.body.name; } } #onCommandResultMobile(event) { if (event.command === "getAppiumContext") { this.setCurrentContext(event.result.value); } if (event.command === "switchAppiumContext" && event.result.value === null && this.#mobileContext) { this.setCurrentContext(this.#mobileContext); } } /** * set context at the start of the session */ async initialize() { if (process.env.WDIO_UNIT_TESTS) { return ""; } if (this.#browser.isMobile && !this.#isNativeContext && !this.#mobileContext && this.#getContextSupport) { const context = await this.#browser.getContext().catch((err) => { log2.warn( `Error getting context: ${err} WebDriver capabilities: ${JSON.stringify(this.#browser.capabilities)} Requested WebDriver capabilities: ${JSON.stringify(this.#browser.requestedCapabilities)}` ); if (err.message.includes("Request failed with status code 405")) { this.#getContextSupport = false; } return void 0; }); this.#mobileContext = typeof context === "string" ? context : typeof context === "object" ? context.id : void 0; } const windowHandle = this.#mobileContext || await this.#browser.getWindowHandle(); this.setCurrentContext(windowHandle); return windowHandle; } setCurrentContext(context) { this.#currentContext = context; if (this.#browser.isMobile) { this.#isNativeContext = context ? context === "NATIVE_APP" : this.#isNativeContext; this.#mobileContext = context || void 0; } } async getCurrentContext() { if (!this.#currentContext) { return this.initialize(); } return this.#currentContext; } get isNativeContext() { return this.#isNativeContext; } get mobileContext() { return this.#mobileContext; } /** * Get the flat context tree for the current session * @returns a flat list of all contexts in the current session */ async getFlatContextTree() { const tree = await this.#browser.browsingContextGetTree({}); const mapContext = (context) => [ context.context, ...(context.children || []).map(mapContext).flat(Infinity) ]; const allContexts = tree.contexts.map(mapContext).flat(Infinity).reduce((acc, ctx) => { const context = this.findContext(ctx, tree.contexts, "byContextId"); acc[ctx] = context; return acc; }, {}); return allContexts; } /** * Find the parent context of a given context id * @param contextId the context id you want to find the parent of * @param contexts the list of contexts to search through returned from `browsingContextGetTree` * @returns the parent context of the context with the given id */ findParentContext(contextId, contexts) { for (const context of contexts) { if (context.children?.some((child) => child.context === contextId)) { return context; } if (Array.isArray(context.children) && context.children.length > 0) { const result = this.findParentContext(contextId, context.children); if (result) { return result; } } } return void 0; } /** * Find a context by URL or ID * @param urlOrId The URL or ID of the context to find * @param contexts The list of contexts to search through returned from `browsingContextGetTree` * @param matcherType The type of matcher to use to find the context * @returns The context with the given URL or ID */ findContext(urlOrId, contexts, matcherType) { const matcher = { byUrl, byUrlContaining, byContextId }[matcherType]; for (const context of contexts || []) { if (matcher(context, urlOrId)) { return context; } if (Array.isArray(context.children) && context.children.length > 0) { const result = this.findContext(urlOrId, context.children, matcherType); if (result) { return result; } } } return void 0; } }; function byUrl(context, url2) { return context.url === url2; } function byUrlContaining(context, url2) { return context.url.includes(url2); } function byContextId(context, contextId) { return context.context === contextId; } // src/node/saveScreenshot.ts async function saveScreenshot(filepath, options) { if (typeof filepath !== "string") { throw new Error('saveScreenshot expects a filepath of type string and ".png" file ending'); } const absoluteFilepath = getAbsoluteFilepath(filepath); await assertDirectoryExists(absoluteFilepath); const screenBuffer = this.isBidi ? await takeScreenshotBidi.call(this, filepath, options) : await takeScreenshotClassic.call(this, filepath, options); const screenshot = Buffer.from(screenBuffer, "base64"); await fs6.writeFile(absoluteFilepath, screenshot); return screenshot; } function takeScreenshotClassic(filepath, options) { if (options) { throw new Error("saveScreenshot does not support options in WebDriver Classic mode"); } const fileExtension = path4.extname(filepath).slice(1); if (fileExtension !== "png") { throw new Error('Invalid file extension, use ".png" for PNG format'); } return this.takeScreenshot(); } async function takeScreenshotBidi(filepath, options) { const browser2 = getBrowserObject(this); const contextManager = getContextManager(browser2); const context = await contextManager.getCurrentContext(); const tree = await this.browsingContextGetTree({}); const origin = options?.fullPage ? "document" : "viewport"; const givenFormat = options?.format || path4.extname(filepath).slice(1); const imageFormat = givenFormat === "png" ? "image/png" : givenFormat === "jpeg" || givenFormat === "jpg" ? "image/jpeg" : void 0; if (!imageFormat) { throw new Error(`Invalid image format, use 'png', 'jpg' or 'jpeg', got '${options?.format}'`); } if (imageFormat === "image/jpeg" && path4.extname(filepath) !== ".jpeg" && path4.extname(filepath) !== ".jpg") { throw new Error('Invalid file extension, use ".jpeg" or ".jpg" for JPEG format'); } else if (imageFormat === "image/png" && path4.extname(filepath) !== ".png") { throw new Error('Invalid file extension, use ".png" for PNG format'); } const quality = typeof options?.quality === "number" ? options.quality / 100 : void 0; if (typeof options?.quality === "number" && (options?.quality < 0 || options?.quality > 100)) { throw new Error(`Invalid quality, use a number between 0 and 100, got '${options?.quality}'`); } if (typeof options?.quality === "number" && imageFormat !== "image/jpeg") { throw new Error('Invalid option "quality" for PNG format'); } const format = { type: imageFormat, quality }; const clip = options?.clip ? { type: "box", x: options.clip.x, y: options.clip.y, width: options.clip.width, height: options.clip.height } : void 0; if (clip) { if (typeof clip.x !== "number" || typeof clip.y !== "number" || typeof clip.width !== "number" || typeof clip.height !== "number") { throw new Error("Invalid clip, use an object with x, y, width and height properties"); } } const { data } = contextManager.findParentContext(context, tree.contexts) ? await browser2.$("html").getElement().then( (el) => this.takeElementScreenshot(el.elementId).then((data2) => ({ data: data2 })) ) : await this.browsingContextCaptureScreenshot({ context, origin, format, clip }); return data; } // src/node/saveElementScreenshot.ts import fs7 from "node:fs/promises"; async function saveElementScreenshot(filepath) { if (typeof filepath !== "string" || !filepath.endsWith(".png")) { throw new Error('saveScreenshot expects a filepath of type string and ".png" file ending'); } const absoluteFilepath = getAbsoluteFilepath(filepath); await assertDirectoryExists(absoluteFilepath); const screenBuffer = await this.takeElementScreenshot(this.elementId); const screenshot = Buffer.from(screenBuffer, "base64"); await fs7.writeFile(absoluteFilepath, screenshot); return screenshot; } // src/index.ts import logger28 from "@wdio/logger"; import WebDriver, { DEFAULTS } from "webdriver"; import { validateConfig } from "@wdio/config"; import { enableFileLogging, wrapCommand as wrapCommand3, isBidi } 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_KEY21 } from "webdriver"; import { getBrowserObject as getBrowserObject37 } from "@wdio/utils"; // src/utils/implicitWait.ts import logger3 from "@wdio/logger"; import { getBrowserObject as getBrowserObject2 } from "@wdio/utils"; var log3 = logger3("webdriverio"); async function implicitWait(currentElement, commandName) { const browser2 = getBrowserObject2(currentElement); const skipForMobileScroll = browser2.isMobile && await browser2.isNativeContext && (commandName === "scrollIntoView" || commandName === "tap"); if (!currentElement.elementId && !/(waitUntil|waitFor|isExisting|is?\w+Displayed|is?\w+Clickable)/.test(commandName) && !skipForMobileScroll) { log3.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 cssValue from "css-value"; import rgb2hex from "rgb2hex"; import GraphemeSplitter from "grapheme-splitter"; import logger27 from "@wdio/logger"; import isPlainObject from "is-plain-obj"; import { ELEMENT_KEY as ELEMENT_KEY20 } from "webdriver"; import { UNICODE_CHARACTERS as UNICODE_CHARACTERS2, asyncIterators, getBrowserObject as getBrowserObject36 } 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, deepLink: () => deepLink, deleteCookies: () => deleteCookies, downloadFile: () => downloadFile2, emulate: () => emulate, execute: () => execute, executeAsync: () => executeAsync, getContext: () => getContext, getContexts: () => getContexts, getCookies: () => getCookies, getPuppeteer: () => getPuppeteer, getWindowSize: () => getWindowSize, keys: () => keys, mock: () => mock, mockClearAll: () => mockClearAll, mockRestoreAll: () => mockRestoreAll, newWindow: () => newWindow, pause: () => pause, react$: () => react$, react$$: () => react$$, relaunchActiveApp: () => relaunchActiveApp, reloadSession: () => reloadSession, restore: () => restore, savePDF: () => savePDF2, saveRecordingScreen: () => saveRecordingScreen2, saveScreenshot: () => saveScreenshot2, scroll: () => scroll, setCookies: () => setCookies, setTimeout: () => setTimeout2, setViewport: () => setViewport, setWindowSize: () => setWindowSize, swipe: () => swipe, switchContext: () => switchContext, switchFrame: () => switchFrame, switchWindow: () => switchWindow, tap: () => tap, throttle: () => throttle, throttleCPU: () => throttleCPU, throttleNetwork: () => throttleNetwork, touchAction: () => touchAction2, uploadFile: () => uploadFile2, url: () => url, 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 getBrowserObject3 } 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 browser2 = getBrowserObject3(this); const browserCommandKeys = Object.keys(browser_exports); const propertiesObject = { /** * filter out browser commands from object */ ...Object.entries(clone(browser2.__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) => { browser2.__propertiesObject__[name] = { value: fn }; origAddCommand(name, fn); }; return elementInstance; } var getElements = function getElements2(selector, elemResponse, props = { isReactElement: false, isShadowElement: false }) { const browser2 = getBrowserObject3(this); const browserCommandKeys = Object.keys(browser_exports); const propertiesObject = { /** * filter out browser commands from object */ ...Object.entries(clone(browser2.__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) => { browser2.__propertiesObject__[name] = { value: fn }; origAddCommand(name, fn); }; return elementInstance; }); return elements; }; // src/constants.ts import { UNICODE_CHARACTERS, HOOK_DEFINITION } from "@wdio/utils"; var WDIO_DEFAULTS = { /** * allows to specify automation protocol */ automationProtocol: { type: "string", default: "webdriver", validate: (param) => { if (typeof param !== "string") { throw new Error("automationProtocol should be a string"); } if (typeof import.meta.resolve !== "function") { return; } try { import.meta.resolve(param); } catch (err) { const error = err instanceof Error ? err : new Error("unknown error"); throw new Error(`Couldn't find automation protocol "${param}": ${error.message}`); } } }, /** * 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: 100 }, /** * 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/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/environment.ts var isNode = !!(typeof process !== "undefined" && process.version); var environment = { value: { get readFileSync() { throw new Error("Can't read files form file system in this environment"); }, get downloadFile() { throw new Error("The `downloadFile` command is not available in this environment"); }, get savePDF() { throw new Error("The `savePDF` command is not available in this environment"); }, get saveRecordingScreen() { throw new Error("The `saveRecordingScreen` command is not available in this environment"); }, get uploadFile() { throw new Error("The `uploadFile` command is not available in this environment"); }, get saveScreenshot() { throw new Error("The `saveScreenshot` command for WebdriverIO.Browser is not available in this environment"); }, get saveElementScreenshot() { throw new Error("The `saveScreenshot` command for WebdriverIO.Element is not available in this environment"); }, get osType() { return () => "browser"; } } }; // 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) && environment.value.osType().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/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 resqScript = `!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.resq=e():(t.window=t.window||{},t.window.resq=e())}(window,(function(){return function(t){var e={};function r(n){if(e[n])return e[n].exports;var o=e[n]={i:n,l:!1,exports:{}};return t[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}return r.m=t,r.c=e,r.d=function(t,e,n){r.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},r.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(t,e){if(1&e&&(t=r(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)r.d(n,o,function(e){return t[e]}.bind(null,o));return n},r.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(e,"a",e),e},r.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r.p="",r(r.s=16)}([function(t,e,r){"use strict";r.d(e,"a",(function(){return m})),r.d(e,"d",(function(){return j})),r.d(e,"b",(function(){return M})),r.d(e,"c",(function(){return P}));var n=r(1),o=r.n(n),u=r(14),i=r.n(u),c=r(2),f=r.n(c),s=r(15),a=r.n(s);function l(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(t);e&&(n=n.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),r.push.apply(r,n)}return r}var p=Array.isArray,d=Object.keys;function x(t){return"function"==typeof t}function y(t){return t instanceof HTMLElement||t instanceof Text}function h(t){return"object"===f()(t)&&!p(t)}function b(t){if(!t||"string"==typeof t)return t;var e=function(t){for(var e=1;e<arguments.length;e++){var r=null!=arguments[e]?arguments[e]:{};e%2?l(Object(r),!0).forEach((function(e){i()(t,e,r[e])})):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(r)):l(Object(r)).forEach((function(e){Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(r,e))}))}return t}({},t);return delete e.children,e}function v(t,e){var r=arguments.length>2&&void 0!==arguments[2]&&arguments[2];return!(!p(t)||!p(e))&&(r?t.length===e.length&&!t.find((function(t){return!e.includes(t)})):t.some((function(t){return e.includes(t)})))}function _(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=arguments.length>2&&void 0!==arguments[2]&&arguments[2],n=[];if(!d(t).length)return!0;if(null===e||!d(e).length)return!1;if(r)return a()(t,e);var o=d(t).filter((function(t){return d(e).includes(t)}));return o.forEach((function(r){h(t[r])&&h(e[r])&&(n=n.concat(_(t[r],e[r]))),(t[r]===e[r]||v(t[r],e[r]))&&n.push(e)})),n.length>0&&n.filter((function(t){return t})).length===o.length}function m(t){var e,r={children:[]};if(!t)return r;r.name=x(e=t.type)?e.displayName||e.name:e,r.props=b(t.memoizedProps),r.state=function(t){if(t){var e=t.baseState;return e||t}}(t.memoizedState);var n=t.child;if(n)for(r.children.push(n);n.sibling;)r.children.push(n.sibling),n=n.sibling;return r.children=r.children.map((function(t){return m(t)})),x(t.type)&&function(t){return t.children.length>1}(r)?(r.node=function(t){return t.children.map((function(t){return t.node})).filter((function(t){return!!t}))}(r),r.isFragment=!0):r.node=function(t){return y(t.stateNode)?t.stateNode:t.child&&y(t.child.stateNode)?t.child.stateNode:null}(t),r}function g(t){for(;t.length;){var e=t.shift();if(e.node)return e.node;e.children&&Array.isArray(e.children)&&t.push.apply(t,o()(e.children))}}function O(t,e){for(var r=[];t.length;){var n=t.shift().children;n&&Array.isArray(n)&&n.forEach((function(n){e(n)&&(!n.node&&Array.isArray(n.children)&&(n.node=g(n.children.concat([]))),r.push(n)),t.push(n)}))}return r}function w(t,e){var r=function(t){if(t){var e=t.split("(");return 1===e.length?t:e.find((function(t){return t.includes(")")})).replace(/\\)*/g,"")}}(e);return new RegExp("^"+t.split("*").map((function(t){return t.replace(/([.*+?^=!:\${}()|[\\]/\\\\])/g,"\\\\$1")})).join(".+")+"$").test(r)}function j(t,e){var r=arguments.length>3?arguments[3]:void 0;return t.reduce((function(t,e){return t.concat(O(t,r&&"function"==typeof r?r:function(t){return"string"==typeof t.name?w(e,t.name):null!==t.name&&"object"===f()(t.name)&&w(e,t.name.displayName)}))}),[e])}function M(t,e,r){var n=arguments.length>3&&void 0!==arguments[3]&&arguments[3];return x(r)?(console.warn("Functions are not supported as filter matchers"),[]):t.filter((function(t){return h(r)&&_(r,t[e],n)||p(r)&&v(r,t[e],n)||t[e]===r}))}function P(t){if(t.hasOwnProperty("_reactRootContainer"))return t._reactRootContainer._internalRoot.current;var e=Object.keys(t).find((function(t){return t.startsWith("__reactInternalInstance")||t.startsWith("__reactFiber")||t.startsWith("__reactContainer")}));return e?t[e]:void 0}},function(t,e,r){var n=r(17),o=r(18),u=r(19),i=r(20);t.exports=function(t){return n(t)||o(t)||u(t)||i()},t.exports.default=t.exports,t.exports.__esModule=!0},function(t,e){function r(e){return"function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?(t.exports=r=function(t){return typeof t},t.exports.default=t.exports,t.exports.__esModule=!0):(t.exports=r=function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol