webdriverio
Version:
Next-gen browser and mobile automation test framework for Node.js
14,753 lines • 614 kB
JavaScript
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";
import process2 from "node:process";
// 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 zip = await JSZip.loadAsync(zipData);
const keys2 = Object.keys(zip.files);
for (let i = 0; i < keys2.length; i++) {
const fileData = await zip.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/extensionArchive.ts
import fs2 from "node:fs";
import path2 from "node:path";
import { ZipArchive } from "archiver";
var ARCHIVE_EXTENSIONS = /* @__PURE__ */ new Set([".zip", ".xpi", ".crx"]);
async function extensionDataFromPath(input, remoteSession) {
const resolved = path2.resolve(input);
const kind = ARCHIVE_EXTENSIONS.has(path2.extname(resolved).toLowerCase()) ? "archive" : "directory";
if (!remoteSession) {
return kind === "archive" ? { type: "archivePath", path: resolved } : { type: "path", path: resolved };
}
const value = await readExtensionBase64(resolved, kind);
return { type: "base64", value };
}
async function readExtensionBase64(absolutePath, kind) {
if (kind === "archive") {
try {
const bytes = await fs2.promises.readFile(absolutePath);
return bytes.toString("base64");
} catch (err) {
throw new Error(`installExtension could not read archive ${absolutePath} to send it to the remote browser: ${errorMessage(err)}`);
}
}
let stat;
try {
stat = await fs2.promises.stat(absolutePath);
} catch (err) {
throw new Error(`installExtension could not read directory ${absolutePath} to send it to the remote browser: ${errorMessage(err)}`);
}
if (!stat.isDirectory()) {
throw new Error(`installExtension expected a directory at ${absolutePath}`);
}
const zip = await zipDirectory(absolutePath);
return zip.toString("base64");
}
function zipDirectory(dir) {
return new Promise((resolve, reject) => {
const chunks = [];
const archive = new ZipArchive();
let settled = false;
const fail = (err) => {
if (settled) {
return;
}
settled = true;
reject(err instanceof Error ? err : new Error(errorMessage(err)));
};
archive.on("error", fail);
archive.on("data", (chunk) => chunks.push(chunk));
archive.on("end", () => {
if (settled) {
return;
}
settled = true;
resolve(Buffer.concat(chunks));
});
try {
appendDirectory(archive, dir, "", /* @__PURE__ */ new Set());
} catch (err) {
fail(err);
return;
}
archive.finalize();
});
}
function appendDirectory(archive, dir, prefix, seen) {
const realDir = fs2.realpathSync(dir);
if (seen.has(realDir)) {
return;
}
seen.add(realDir);
try {
for (const entry of fs2.readdirSync(dir, { withFileTypes: true })) {
const full = path2.join(dir, entry.name);
const name = prefix ? `${prefix}/${entry.name}` : entry.name;
let stat;
try {
stat = fs2.statSync(full);
} catch (err) {
if (isNotFound(err)) {
continue;
}
throw new Error(`installExtension could not read ${full}: ${errorMessage(err)}`);
}
if (stat.isDirectory()) {
appendDirectory(archive, full, name, seen);
} else if (stat.isFile()) {
archive.append(fs2.createReadStream(full), { name });
}
}
} finally {
seen.delete(realDir);
}
}
function isNotFound(err) {
return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
}
function errorMessage(err) {
return err instanceof Error ? err.message : String(err);
}
// src/node/savePDF.ts
import fs4 from "node:fs";
import path4 from "node:path";
import { getBrowserObject as getBrowserObject2 } from "@wdio/utils";
// src/session/context.ts
import logger2 from "@wdio/logger";
// 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 extensionDataFromPath() {
throw new Error("installExtension cannot read extension paths 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";
},
get variables() {
return isNode ? process.env : {};
}
}
};
// src/session/session.ts
var sessionManager = /* @__PURE__ */ new Map();
var listenerRegisteredSession = /* @__PURE__ */ new Set();
var SessionManager = class {
#browser;
#scope;
/**
* the marker this instance added, or undefined when another instance
* already owned the key and this one registered no listener
*/
#ownedRegistrationId;
/**
* 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(browser, scope) {
this.#browser = browser;
this.#scope = scope;
const registrationId = `${this.#browser.sessionId}-${this.#scope}`;
if (!listenerRegisteredSession.has(registrationId)) {
this.#browser.on("command", this.#onCommandListener);
listenerRegisteredSession.add(registrationId);
this.#ownedRegistrationId = registrationId;
}
}
#onCommandListener = this.#onCommand.bind(this);
#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.#onCommandListener);
if (this.#ownedRegistrationId) {
listenerRegisteredSession.delete(this.#ownedRegistrationId);
this.#ownedRegistrationId = void 0;
}
}
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
!environment.value.variables.WDIO_UNIT_TESTS
);
}
static getSessionManager(browser, Manager) {
const scope = Manager.name;
let sessionManagerInstances = sessionManager.get(scope);
if (!sessionManagerInstances) {
sessionManagerInstances = /* @__PURE__ */ new Map();
sessionManager.set(scope, sessionManagerInstances);
}
let sessionManagerInstance = sessionManagerInstances.get(browser);
if (!sessionManagerInstance) {
sessionManagerInstance = new Manager(browser);
sessionManagerInstances.set(browser, sessionManagerInstance);
}
return sessionManagerInstance;
}
};
// src/utils/mobile.ts
var APPIUM3_UPGRADE_HINT = "WebdriverIO 10 requires Appium 3 and a current official driver. Install or upgrade with `npm i -D appium@^3`, then run `appium driver update installed`. Stay on WebdriverIO 9 if you cannot upgrade the server.";
function isUnknownMethodError(err) {
if (!(err instanceof Error)) {
return false;
}
const msg = err.message.toLowerCase();
return msg.includes("unknown method") || msg.includes("unknown command");
}
async function executeMobile(browser, script, args) {
try {
return await browser.executeScript(
script,
args === void 0 ? [] : [args]
);
} catch (err) {
if (!isUnknownMethodError(err)) {
throw err;
}
const original = err instanceof Error ? err.message : String(err);
throw new Error(
`The \`${script}\` execute method is not supported by your Appium driver (${original}). ${APPIUM3_UPGRADE_HINT} If you are already on Appium 3 with current drivers, this platform may not implement \`${script}\`.`
);
}
}
function getNativeContext({ capabilities, isMobile }) {
if (!capabilities || typeof capabilities !== "object" || !isMobile) {
return false;
}
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 && isMobile && 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, duration, scale }) {
const deviceScreenSize = (browser.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, 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 = browser.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 ? browser.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: browser.isIOS && gesture === "zoom" ? scale * 10 : scale
};
}
// src/session/context.ts
var log2 = logger2("webdriverio:context");
var COMMANDS_REQUIRING_RESET = ["deleteSession", "refresh", "switchToParentFrame"];
function getContextManager(browser) {
return SessionManager.getSessionManager(browser, ContextManager);
}
var ContextManager = class _ContextManager extends SessionManager {
#browser;
#currentContext;
#mobileContext;
#isNativeContext;
#getContextSupport = true;
#currentWindowHandle;
#onCommandResultBidiAndClassicListener;
#onCommandListener;
#onCommandResultMobileListener;
#navigationStartedListener;
#destroyedListener;
constructor(browser) {
super(browser, _ContextManager.name);
this.#browser = browser;
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.#onCommandResultBidiAndClassicListener = this.#onCommandResultBidiAndClassic.bind(this);
this.#onCommandListener = this.#onCommand.bind(this);
this.#onCommandResultMobileListener = this.#onCommandResultMobile.bind(this);
this.#navigationStartedListener = this.#navigationStarted.bind(this);
this.#destroyedListener = this.#contextDestroyed.bind(this);
this.#browser.on("result", this.#onCommandResultBidiAndClassicListener);
if (!this.isEnabled() && !this.#browser.isMobile) {
return;
}
this.#browser.on("command", this.#onCommandListener);
if (this.#browser.isMobile) {
this.#browser.on("result", this.#onCommandResultMobileListener);
} else {
this.#browser.sessionSubscribe({
events: ["browsingContext.navigationStarted", "browsingContext.contextDestroyed"]
});
this.#browser.on("browsingContext.navigationStarted", this.#navigationStartedListener);
this.#browser.on("browsingContext.contextDestroyed", this.#destroyedListener);
}
}
removeListeners() {
super.removeListeners();
this.#browser.off("result", this.#onCommandResultBidiAndClassicListener);
this.#browser.off("command", this.#onCommandListener);
if (this.#browser.isMobile) {
this.#browser.off("result", this.#onCommandResultMobileListener);
} else {
this.#browser.off("browsingContext.navigationStarted", this.#navigationStartedListener);
this.#browser.off("browsingContext.contextDestroyed", this.#destroyedListener);
}
}
async #navigationStarted(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);
await this.#browser.switchToWindow(this.#currentContext);
return;
}
}
async #contextDestroyed(destroyed) {
if (!this.#currentContext || destroyed.context !== this.#currentContext) {
return;
}
this.#currentWindowHandle = void 0;
this.#currentContext = void 0;
let handle;
try {
if (destroyed.parent) {
handle = await this.#getTopLevelContext(destroyed.parent);
}
if (!handle) {
const windowHandles = await this.#browser.getWindowHandles();
handle = windowHandles.find((windowHandle) => windowHandle !== destroyed.context) ?? windowHandles[0];
}
if (!handle) {
return;
}
if (this.#currentContext) {
return;
}
await this.#browser.switchToWindow(handle);
if (this.#currentContext && this.#currentContext !== handle) {
return;
}
this.setCurrentContext(handle);
} catch (err) {
if (!this.#currentContext || this.#currentContext === handle) {
this.#currentContext = void 0;
this.#currentWindowHandle = void 0;
}
log2.warn(`Failed to switch context after "${destroyed.context}" was destroyed: ${err.message}`);
}
}
/**
* Resolve the top-level context of a given context id, e.g. the top-level
* browsing context a child frame belongs to.
* @param contextId the context id to resolve the top-level context for
* @returns the top-level context id, or undefined if it can't be found
*/
async #getTopLevelContext(contextId) {
const { contexts } = await this.#browser.browsingContextGetTree({});
return contexts.find(
(context) => this.findContext(contextId, [context], "byContextId")
)?.context;
}
#onCommandResultBidiAndClassic(event) {
if (event.command === "closeWindow") {
this.#currentWindowHandle = void 0;
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);
}
if (event.command === "getWindowHandle") {
const windowHandle = event.result.value || void 0;
this.#currentWindowHandle = windowHandle;
}
if (event.command === "switchToWindow") {
const err = event.result.error || void 0;
if (!err) {
const windowHandle = event.body.handle || void 0;
this.#currentWindowHandle = windowHandle;
}
}
}
#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) {
const stillInTree = this.findContext(this.#currentContext, contexts, "byContextId");
if (stillInTree) {
return;
}
this.#currentContext = void 0;
this.#currentWindowHandle = void 0;
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 (environment.value.variables.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;
}
/**
* Browsing context id the emulation commands target. Several BiDi emulation
* commands reject a call that is not aimed at a top-level traversable, and
* a child frame id is not one. Resolve the frame the user is in to the
* top-level context that contains it.
*/
async getCurrentTopLevelContext() {
const current = await this.getCurrentContext();
if (!current) {
return current;
}
return await this.#getTopLevelContext(current) ?? current;
}
/**
* Sets the cached current window handle value.
* @param handle current window handle to set
*/
setCurrentWindowHandle(handle) {
this.#currentWindowHandle = handle;
}
/**
* Returns the cached window handle.
*
* @returns the current window handle, or undefined if the current window is closed.
*/
getCurrentWindowHandle() {
return this.#currentWindowHandle;
}
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/session/browsingContext.ts
import { getBrowserObject, getWdioKind } from "@wdio/utils";
function isBrowsingContext(value) {
return getWdioKind(value) === "browsing-context";
}
async function contextIdOf(scope) {
let current = scope;
const seen = /* @__PURE__ */ new Set();
while (current && typeof current === "object" && !seen.has(current)) {
seen.add(current);
if (isBrowsingContext(current)) {
return current.contextId;
}
const parent = current.parent;
if (!parent || parent === current) {
break;
}
current = parent;
}
const browser = getBrowserObject(scope);
return getContextManager(browser).getCurrentContext();
}
function contextIdValue(value) {
if (typeof value === "string" && value) {
return value;
}
if (value && typeof value === "object" && typeof value.context === "string") {
return value.context;
}
throw new Error("A browsing context id is required");
}
function heldBrowsingContext(scope) {
let current = scope;
const seen = /* @__PURE__ */ new Set();
while (current && typeof current === "object" && !seen.has(current)) {
seen.add(current);
if (isBrowsingContext(current)) {
return current;
}
const parent = current.parent;
if (!parent || parent === current) {
break;
}
current = parent;
}
return void 0;
}
async function foreignContextId(scope) {
const held = heldBrowsingContext(scope);
if (!held) {
return void 0;
}
const current = contextIdValue(await getContextManager(held.browser).getCurrentContext());
return held.contextId === current ? void 0 : held.contextId;
}
function assertTopLevel(context, command) {
if (context.isFrame) {
throw new Error(`\`${command}\` is only available on a top-level browsing context`);
}
}
function containsContext(nodes, contextId) {
return nodes.some((node) => node.context === contextId || containsContext(node.children ?? [], contextId));
}
async function assertFrameAttached(frame2) {
let top = frame2;
while (top.parent) {
top = top.parent;
}
const { contexts } = await frame2.browser.browsingContextGetTree({ root: top.contextId });
if (!containsContext(contexts, frame2.contextId)) {
throw new Error(`no such frame: the frame "${frame2.contextId}" was discarded because the page it belongs to navigated away`);
}
}
// src/node/utils.ts
import path3 from "node:path";
import fs3 from "node:fs/promises";
async function assertDirectoryExists(filepath) {
const exist = await fs3.access(path3.dirname(filepath)).then(() => true, () => false);
if (!exist) {
throw new Error(`directory (${path3.dirname(filepath)}) doesn't exist`);
}
}
// 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 printOptions = options ?? {};
assertOrientation(printOptions.orientation);
const absoluteFilepath = path4.resolve(filepath);
await assertDirectoryExists(absoluteFilepath);
const base64 = this.isBidi ? await printBidi.call(this, printOptions) : await printClassic.call(this, printOptions);
const page = Buffer.from(base64, "base64");
fs4.writeFileSync(absoluteFilepath, page);
return page;
}
function assertOrientation(orientation) {
if (orientation === void 0) {
return void 0;
}
if (orientation !== "portrait" && orientation !== "landscape") {
throw new Error(`savePDF expects orientation to be "portrait" or "landscape", received "${orientation}"`);
}
return orientation;
}
function printClassic(options) {
return this.printPage(
options?.orientation,
options?.scale,
options?.background,
options?.width,
options?.height,
options?.top,
options?.bottom,
options?.left,
options?.right,
options?.shrinkToFit,
options?.pageRanges
);
}
async function printBidi(options) {
const browser = getBrowserObject2(this);
const context = await topLevelBrowsingContext(browser, await contextIdOf(this));
const { data } = await browser.browsingContextPrint(toPrintParameters(context, options));
return data;
}
async function topLevelBrowsingContext(browser, start) {
const contextManager = getContextManager(browser);
let context = start ?? await contextManager.getCurrentContext();
const { contexts } = await browser.browsingContextGetTree({});
let parent = contextManager.findParentContext(context, contexts);
const seen = /* @__PURE__ */ new Set();
while (parent && !seen.has(parent.context)) {
seen.add(context);
context = parent.context;
parent = contextManager.findParentContext(context, contexts);
}
return context;
}
function toPrintParameters(context, options) {
const printOptions = options ?? {};
const params = { context };
const orientation = assertOrientation(printOptions.orientation);
if (orientation !== void 0) {
params.orientation = orientation;
}
if (printOptions.scale !== void 0) {
params.scale = printOptions.scale;
}
if (printOptions.background !== void 0) {
params.background = printOptions.background;
}
if (printOptions.shrinkToFit !== void 0) {
params.shrinkToFit = printOptions.shrinkToFit;
}
if (printOptions.pageRanges !== void 0) {
params.pageRanges = printOptions.pageRanges;
}
const page = {};
if (printOptions.width !== void 0) {
page.width = printOptions.width;
}
if (printOptions.height !== void 0) {
page.height = printOptions.height;
}
if (printOptions.width !== void 0 || printOptions.height !== void 0) {
params.page = page;
}
const margin = {};
if (printOptions.top !== void 0) {
margin.top = printOptions.top;
}
if (printOptions.bottom !== void 0) {
margin.bottom = printOptions.bottom;
}
if (printOptions.left !== void 0) {
margin.left = printOptions.left;
}
if (printOptions.right !== void 0) {
margin.right = printOptions.right;
}
if (printOptions.top !== void 0 || printOptions.bottom !== void 0 || printOptions.left !== void 0 || printOptions.right !== void 0) {
params.margin = margin;
}
return params;
}
// src/node/saveRecordingScreen.ts
import fs5 from "node:fs";
import path5 from "node:path";
async function saveRecordingScreen(filepath) {
if (typeof filepath !== "string") {
throw new Error("saveRecordingScreen expects a filepath");
}
const absoluteFilepath = path5.resolve(filepath);
await assertDirectoryExists(absoluteFilepath);
const videoBuffer = await this.stopRecordingScreen();
const video = Buffer.from(videoBuffer, "base64");
fs5.writeFileSync(absoluteFilepath, video);
return video;
}
// src/node/saveScreenshot.ts
import fs6 from "node:fs/promises";
import path6 from "node:path";
import { getBrowserObject as getBrowserObject3 } from "@wdio/utils";
// src/utils/frameOffset.ts
async function frameRect(parent, contextId) {
const frames = await parent.$$("iframe, frame");
for (const frame2 of frames) {
const element = await frame2.getElement();
const win = await parent.execute(
(node) => node.contentWindow,
element
);
if (win?.context !== contextId) {
continue;
}
return parent.execute((node) => {
const rect = node.getBoundingClientRect();
return {
x: Math.round(rect.x + node.clientLeft),
y: Math.round(rect.y + node.clientTop),
width: Math.round(rect.width),
height: Math.round(rect.height)
};
}, element);
}
throw new Error(`Could not find a frame element for browsing context ${contextId}`);
}
async function frameOffset(frame2) {
let top = frame2;
const chain = [];
while (top.parent) {
chain.unshift(top);
top = top.parent;
}
let x = 0;
let y = 0;
let parent = top;
for (const child of chain) {
const rect = await frameRect(parent, child.contextId);
x += rect.x;
y += rect.y;
parent = child;
}
return { top, x, y };
}
// 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 = path6.resolve(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 = path6.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 browser = getBrowserObject3(this);
const contextManager = getContextManager(browser);
const context = await contextIdOf(this);
const tree = await browser.browsingContextGetTree({});
const origin = options?.fullPage ? "document" : "viewport";
const givenFormat = options?.format || path6.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" && path6.extname(filepath) !== ".jpeg" && path6.extname(filepath) !== ".jpg") {
throw new Error('Invalid file extension, use ".jpeg" or ".jpg" for JPEG format');
} else if (imageFormat === "image/png" && path6.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");
}
}
if (isBrowsingContext(this) && this.isFrame) {
const { top, x, y } = await frameOffset(this);
const html = await this.execute(() => {
const rect = document.documentElement.getBoundingClientRect();
return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
});
const shot = await browser.browsingContextCaptureScreenshot({
context: top.contextId,
origin: "viewport",
format,
clip: {
type: "box",
x: Math.round(x + html.x),
y: Math.round(y + html.y),
width: Math.round(html.width),
height: Math.round(html.height)
}
});
return shot.data;
}
const { data } = contextManager.findParentContext(context, tree.contexts) ? await browser.$("html").getElement().then(
(el) => browser.takeElementScreenshot(el.elementId).then((screen) => ({ data: screen }))
) : await browser.browsingContextCaptureScreenshot({ context, origin, format, clip });
return data;
}
// src/node/saveElementScreenshot.ts
import fs7 from "node:fs/promises";
import path7 from "node:path";
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 = path7.resolve(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 logger29 from "@wdio/logger";
import WebDriver, { DEFAULTS } from "webdriver";
import { validateConfig } from "@wdio/config";
import { enableFileLogging, wrapCommand as wrapCommand4, isBidi, resolveCustomCommandOptions as resolveCustomCommandOptions3 } from "@wdio/utils";
// src/multiRemote.ts
import clone2 from "lodash.clonedeep";
import { setWdioKind as setWdioKind5, webdriverMonad as webdriverMonad3, wrapCommand as wrapCommand3 } from "@wdio/utils";
// src/middlewares.ts
import { ELEMENT_KEY as ELEMENT_KEY20 } from "webdriver";
import { ELEMENT_ARRAY_COMMANDS, ELEMENT_ARRAY_WRAP as ELEMENT_ARRAY_WRAP2, getBrowserObject as getBrowserObject45 } from "@wdio/utils";
// src/utils/implicitWait.ts
import logger3 from "@wdio/logger";
import { getBrowserObject as getBrowserObject4 } from "@wdio/utils";
// src/utils/strictSelectorError.ts
function selectorToString(selector) {
if (typeof selector === "function") {
return selector.toString();
}
return JSON.stringify(selector);
}
var StrictSelectorError = class extends Error {
/**
* amount of elements the selector resolved to
*/
matches;
/**
* selector that was used to query the elements
*/
selector;
constructor(selector, matches) {
const printable = selectorToString(selector);
super(
`strict mode violation: \`$(${printable})\` resolved to ${matches} elements, expected 1.
Use \`$$(${printable})\` to work with all matches, \`$$(${printable})[0]\` if you explicitly want the first one, or narrow down the selector so it matches a single element.
Opt out for a single call with \`$(${printable}, { strict: false })\` or globally by setting \`strictSelectors: false\` in your WebdriverIO config.`
);
this.name = "StrictSelectorError";
this.matches = matches;
this.selector = selector;
}
};
// src/utils/findElementAgain.ts
import isPlainObject from "is-plain-obj";
import { ELEMENT_KEY } from "webdriver";
import { getWdioKind as getWdioKind2 } from "@wdio/utils";
var REACT_OPTIONS = /* @__PURE__ */ Symbol("wdio:reactOptions");
function canFindAgain(element) {
const { selector } = element;
return typeof selector === "string" || typeof selector === "function" || isPlainObject(selector) && typeof selector[ELEMENT_KEY] !== "string";
}
function selectorIn(element, parent) {
const selector = element.selector;
if (!isPlainObject(selector) || typeof selector.strategy !== "function" || getWdioKind2(element.parent) !== "element") {
return element.selector;
}
return { ...selector, strategyArguments: [...selector.strategyArguments.slice(0, -1), parent] };
}
function findAllAgain(element, parent = element.parent) {
if (element.isReactElement) {
return parent.react$$(element.selector, element[REACT_OPTIONS]);
}
if (element.isShadowElement) {
return parent.shadow$$(element.selector);
}
return parent.$$(selectorIn(element, parent));
}
async function findElementAgain(element, { parent = element.parent, wait = false } = {}) {
if (element.index !== void 0) {
if (wait) {
return findAllAgain(element, parent)[element.index]?.getElement();
}
const elements = await findAllAgain(element, parent);
return element.index < elements.length ? elements[element.index] : void 0;
}
if (element.isReactElement) {
return parent.react$(element.selector, element[REACT_OPTIONS]).getElement();
}
if (element.isShadowElement) {
return parent.shadow$(element.selector).getElement();
}
return parent.$(selectorIn(element, parent), { strict: element.strict }).getElement();
}
// src/utils/implicitWait.ts
var log3 = logger3("webdriverio");
async function implicitWait(currentElement, commandName) {
const browser = getBrowserObject4(currentElement);
const skipForMobileScroll = browser.isMobile && await browser.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();
const nextElement2 = await findElementAgain(currentElement, { wait: true });
if (!nextElement2) {
throw new Error(
`element with selector "${currentElement.selector}" has no match at index ${currentElement.index}`
);
}
return nextElement2;
} catch (err) {
if (err instanceof StrictSelectorError) {
throw err;
}
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 chain = [];
while (currentElement.elementId && currentElement.parent) {
chain.push(currentElement);
currentElement = currentElement.parent;
}
chain.reverse();
const length = chain.length;
return chain.reduce(async (elementPromise, element, currentIndex) => {
const parent = await elementPromise;
const nextElement2 = await findElementAgain(element, { parent, wait: true });
if (!nextElement2) {
throw new Error(`element with selector "${element.selector}" has no match at index ${element.index}`);
}
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 logger28 from "@wdio/logger";
import isPlainObject3 from "is-plain-obj";
import { ELEMENT_KEY as ELEMENT_KEY19 } from "webdriver";
import { UNICODE_CHARACTERS as UNICODE_CHARACTERS2, WDIO_KIND as WDIO_KIND2, getBrowserObject as getBrowserObject44, isLoadedElement as isLoadedElement2 } from "@wdio/utils";
// src/commands/browser.ts
var browser_exports = {};
__export(browser_exports, {
$: () => $,
$$: () => $$,
SESSION_MOCKS: () => SESSION_MOCKS,
acceptDialog: () => acceptDialog,
action: () => action,
actions: () => actions,
addInitScript: () => addInitScript,
back: () => back,
background: () => background,
browsingContexts: () => browsingContexts,
call: () => call,
closeApp: () => closeApp,
custom$: () => custom$,
custom$$: () => custom$$,
debug: () => debug,
deepLink: () => deepLink,
deleteCookies: () => deleteCookies,
dismissDialog: () => dismissDialog,
downloadFile: () => downloadFile2,
emulate: () => emulate,
execute: () => execute,
fingerPrint: () => fingerPrint,
forward: () => forward,
getClipboard: () => getClipboard,
getContext: () => getContext,
getContexts: () => getContexts,
getCookies: () => getCookies,
getCurrentActivity: () => getCurrentActivity,
getCurrentPackage: () => getCurrentPackage,
getDisplayDensity: () => getDisplayDensity,
getPerformanceData: () => getPerformanceData,
getPerformanceDataTypes: () => getPerformanceDataTypes,
getPuppeteer: () => getPuppeteer,
getStrings: () => getStrings,
getSystemBars: () => getSystemBars,
getWindowSize: () => getWindowSize,
gsmCall: () => gsmCall,
gsmSignal: () => gsmSignal,
gsmVoice: () => gsmVoice,
installExtension: () => installExtension,
isLocked: () => isLocked,
keys: () => keys,
launchApp: () => launchApp,
lock: () => lock,
longPressKeyCode: () => longPressKeyCode,
mock: () => mock,
mockClearAll: () => mockClearAll,
mockRestoreAll: () => mockRestoreAll,
newWindow: () => newWindow,
openNotifications: () => openNotifications,
pause: () => pause,
powerAC: () => powerAC,
powerCapacity: () => powerCapacity,
pressKeyCode: () => pressKeyCode,
react$: () => react$,
react$$: () => react$$,
relaunchActiveApp: () => relaunchActiveApp,
reloadSession: () => reloadSession,
restore: () => restore,
savePDF: () => savePDF2,
saveRecordingScreen: () => saveRecordingScreen2,
saveScreenshot: () => saveScreenshot2,
scroll: () => scroll,
sendKeyEvent: () => sendKeyEvent,
sendSms: () => sendSms,
setClipboard: () => setClipboard,
setCookies: () => setCookies,
setTimeout: () => setTimeout2,
setViewport: () => setViewport,
setWindowSize: () => setWindowSize,
shake: () => shake,
startActivity: () => startActivity,
swipe: () => swipe,
switchContext: () => switchContext,
switchFrame: () => switchFrame,
switchWindow: () => switchWindow,
tap: () => tap,
throttleCPU: () => throttleCPU,
throttleNetwork: () => throttleNetwork,
toggleAirplaneMode: () => toggleAirplaneMode,
toggleData: () => toggleData,
toggleEnrollTouchId: () => toggleEnrollTouchId,
toggleLocationServices: () => toggleLocationServices,
toggleNetworkSpeed: () => toggleNetworkSpeed,
toggleWiFi: () => toggleWiFi,
touchId: () => touchId,
uninstallExtension: () => uninstallExtension,
unlock: () => unlock,
url: () => url,
waitUntil: () => waitUntil
});
// src/utils/getElementObject.ts
import { webdriverMonad, wrapCommand } from "@wdio/utils";
import clone from "lodash.clonedeep";
import { ELEMENT_KEY as ELEMENT_KEY3 } from "webdriver";
import { getBrowserObject as getBrowserObject5 } from "@wdio/utils";
// src/utils/foreignContext.ts
import { BIDI_MASK, ELEMENT_KEY as ELEMENT_KEY2 } from "webdriver";
// src/utils/thirdParty/getVisibleText.ts
var GET_VISIBLE_TEXT = `function(){return (function(){var h=this||self;
function aa(a){var b=typeof a;if("object"==b)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null";else if("function"==
b&&"undefined"==typeof a.call)return"object";return b}function ba(a,b){function c(){}c.prototype=b.prototype;a.prototype=new c;a.prototype.constructor=a};var ca=Array.prototype.indexOf?function(a,b){return Array.prototype.indexOf.call(a,b,void 0)}:function(a,b){if("string"===typeof a)return"string"!==typeof b||1!=b.length?-1:a.indexOf(b,0);for(var c=0;c<a.length;c++)if(c in a&&a[c]===b)return c;return-1},m=Array.prototype.forEach?function(a,b){Array.prototype.forEach.call(a,b,void 0)}:function(a,b){for(var c=a.length,d="string"===typeof a?a.split(""):a,e=0;e<c;e++)e in d&&b.call(void 0,d[e],e,a)},da=Array.prototype.map?function(a,b){return Array.prototype.map.call(a,
b,void 0)}:function(a,b){for(var c=a.length,d=Array(c),e="string"===typeof a?a.split(""):a,f=0;f<c;f++)f in e&&(d[f]=b.call(void 0,e[f],f,a));return d},fa=Array.prototype.some?function(a,b){return Array.prototype.some.call(a,b,void 0)}:function(a,b){for(var c=a.length,d="string"===typeof a?a.split(""):a,e=0;e<c;e++)if(e in d&&b.call(void 0,d[e],e,a))return!0;return!1},ha=Array.prototype.every?function(a,b){return Array.prototype.every.call(a,b,void 0)}:function(a,b){for(var c=a.length,d="string"===
typeof a?a.split(""):a,e=0;e<c;e++)if(e in d&&!b.call(void 0,d[e],e,a))return!1;return!0};var ia={aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgreen:"#006400",
darkgrey:"#a9a9a9",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",
ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",green:"#008000",greenyellow:"#adff2f",grey:"#808080",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgreen:"#90ee90",lightgrey:"#d3d3d3",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",
lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370db",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",
moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#db7093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",
seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"};var ja="backgroundColor borderTopColor borderRightColor borderBottomColor borderLeftColor color outlineColor".split(" "),ka=/#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])/,la=/^#(?:[0-9a-f]{3}){1,2}$/i,ma=/^(?:rgba)?\\((\\d{1,3}),\\s?(\\d{1,3}),\\s?(\\d{1,3}),\\s?(0|1|0\\.\\d*)\\)$/i,na=/^(?:rgb)?\\((0|[1-9]\\d{0,2}),\\s?(0|[1-9]\\d{0,2}),\\s?(0|[1-9]\\d{0,2})\\)$/i;function p(a,b){this.code=a;this.a=q[a]||u;this.message=b||"";a=this.a.replace(/((?:^|\\s+)[a-z])/g,function(c){return c.toUpperCase().replace(/^[\\s\\xa0]+/g,"")});b=a.length-5;if(0>b||a.indexOf("Error",b)!=b)a+="Error";this.name=a;a=Error(this.message);a.name=this.name;this.stack=a.stack||""}ba(p,Error);var u="unknown error",q={15:"element not selectable",11:"element not visible"};q[31]=u;q[30]=u;q[24]="invalid cookie domain";q[29]="invalid element coordinates";q[12]="invalid element state";
q[32]="invalid selector";q[51]="invalid selector";q[52]="invalid selector";q[17]="javascript error";q[405]="unsupported operation";q[34]="move target out of bounds";q[27]="no such alert";q[7]="no such element";q[8]="no such frame";q[23]="no such window";q[28]="script timeout";q[33]="session not created";q[10]="stale element reference";q[21]="timeout";q[25]="unable to set cookie";q[26]="unexpected alert open";q[13]=u;q[9]="unknown command";function oa(a){var b=a.length-1;return 0<=b&&a.indexOf(" ",b)==b}var v=String.prototype.trim?function(a){return a.trim()}:function(a){return/^[\\s\\xa0]*([\\s\\S]*?)[\\s\\xa0]*$/.exec(a)[1]};
function pa(a,b){var c=0;a=v(String(a)).split(".");b=v(String(b)).split(".");for(var d=Math.max(a.length,b.length),e=0;0==c&&e<d;e++){var f=a[e]||"",g=b[e]||"";do{f=/(\\d*)(\\D*)(.*)/.exec(f)||["","","",""];g=/(\\d*)(\\D*)(.*)/.exec(g)||["","","",""];if(0==f[0].length&&0==g[0].length)break;c=w(0==f[1].length?0:parseInt(f[1],10),0==g[1].length?0:parseInt(g[1],10))||w(0==f[2].length,0==g[2].length)||w(f[2],g[2]);f=f[3];g=g[3]}while(0==c)}return c}function w(a,b){return a<b?-1:a>b?1:0};var x;a:{var qa=h.navigator;if(qa){var ra=qa.userAgent;if(ra){x=ra;break a}}x=""}function z(a){return-1!=x.indexOf(a)};function B(){return z("Firefox")||z("FxiOS")}function C(){return(z("Chrome")||z("CriOS"))&&!z("Edge")};function sa(a){return String(a).replace(/\\-([a-z])/g,function(b,c){return c.toUpperCase()})};function D(){return z("iPhone")&&!z("iPod")&&!z("iPad")};function ta(a,b){var c=ua;return Object.prototype.hasOwnProperty.call(c,a)?c[a]:c[a]=b(a)};var va=z("Opera"),E=z("Trident")||z("MSIE"),ya=z("Edge"),za=z("Gecko")&&!(-1!=x.toLowerCase().indexOf("webkit")&&!z("Edge"))&&!(z("Trident")||z("MSIE"))&&!z("Edge"),Aa=-1!=x.toLowerCase().indexOf("webkit")&&!z("Edge");function Ba(){var a=h.document;return a?a.documentMode:void 0}var F;
a:{var G="",I=function(){var a=x;if(za)return/rv:([^\\);]+)(\\)|;)/.exec(a);if(ya)return/Edge\\/([\\d\\.]+)/.exec(a);if(E)return/\\b(?:MSIE|rv)[: ]([^\\);]+)(\\)|;)/.exec(a);if(Aa)return/WebKit\\/(\\S+)/.exec(a);if(va)return/(?:Version)[ \\/]?(\\S+)/.exec(a)}();I&&(G=I?I[1]:"");if(E){var J=Ba();if(null!=J&&J>parseFloat(G)){F=String(J);break a}}F=G}var ua={};function Ca(a){return ta(a,function(){return 0<=pa(F,a)})}var Da;Da=h.document&&E?Ba():void 0;var Ea=B(),Fa=D()||z("iPod"),Ga=z("iPad"),Ha=z("Android")&&!(C()||B()||z("Opera")||z("Silk")),Ia=C(),Ja=z("Safari")&&!(C()||z("Coast")||z("Opera")||z("Edge")||z("Edg/")||z("OPR")||B()||z("Silk")||z("Android"))&&!(D()||z("iPad")||z("iPod"));function K(a){return(a=a.exec(x))?a[1]:""}(function(){if(Ea)return K(/Firefox\\/([0-9.]+)/);if(E||ya||va)return F;if(Ia)return D()||z("iPad")||z("iPod")?K(/CriOS\\/([0-9.]+)/):K(/Chrome\\/([0-9.]+)/);if(Ja&&!(D()||z("iPad")||z("iPod")))return K(/Version\\/([0-9.]+)/);if(Fa||Ga){var a=/Version\\/(\\S+).*Mobile\\/(\\S+)/.exec(x);if(a)return a[1]+"."+a[2]}else if(Ha)return(a=K(/Android\\s+([0-9.]+)/))?a:K(/Version\\/([0-9.]+)/);return""})();var Ka;if(Ka=E)Ka=!(9<=Number(Da));var La=Ka;function L(a,b){this.x=void 0!==a?a:0;this.y=void 0!==b?b:0}L.prototype.ceil=function(){this.x=Math.ceil(this.x);this.y=Math.ceil(this.y);return this};L.prototype.floor=function(){this.x=Math.floor(this.x);this.y=Math.floor(this.y);return this};L.prototype.round=function(){this.x=Math.round(this.x);this.y=Math.round(this.y);return this};function M(a,b){this.width=a;this.height=b}M.prototype.aspectRatio=function(){return this.width/this.height};M.prototype.ceil=function(){this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};M.prototype.floor=function(){this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};M.prototype.round=function(){this.width=Math.round(this.width);this.height=Math.round(this.height);return this};function Ma(a){for(;a&&1!=a.nodeType;)a=a.previousSibling;return a}function N(a){return 9==a.nodeType?a:a.ownerDocument||a.document}function Na(a,b){a&&(a=a.parentNode);for(var c=0;a;){if(b(a))return a;a=a.parentNode;c++}return null}function Oa(a){this.a=a||h.document||document};function O(a,b){b&&"string"!==typeof b&&(b=b.toString());return a instanceof HTMLFormElement?!!a&&1==a.nodeType&&(!b||"FORM"==b):!!a&&1==a.nodeType&&(!b||a.tagName.toUpperCase()==b)};function P(a,b,c,d){this.f=a;this.a=b;this.b=c;this.c=d}P.prototype.ceil=function(){this.f=Math.ceil(this.f);this.a=Math.ceil(this.a);this.b=Math.ceil(this.b);this.c=Math.ceil(this.c);return this};P.prototype.floor=function(){this.f=Math.floor(this.f);this.a=Math.floor(this.a);this.b=Math.floor(this.b);this.c=Math.floor(this.c);return this};P.prototype.round=function(){this.f=Math.round(this.f);this.a=Math.round(this.a);this.b=Math.round(this.b);this.c=Math.round(this.c);return this};function R(a,b,c,d){this.a=a;this.b=b;this.width=c;this.height=d}R.prototype.ceil=function(){this.a=Math.ceil(this.a);this.b=Math.ceil(this.b);this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};R.prototype.floor=function(){this.a=Math.floor(this.a);this.b=Math.floor(this.b);this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};
R.prototype.round=function(){this.a=Math.round(this.a);this.b=Math.round(this.b);this.width=Math.round(this.width);this.height=Math.round(this.height);return this};var Pa="function"===typeof ShadowRoot;function S(a){for(a=a.parentNode;a&&1!=a.nodeType&&9!=a.nodeType&&11!=a.nodeType;)a=a.parentNode;return O(a)?a:null}
function T(a,b){b=sa(b);if("float"==b||"cssFloat"==b||"styleFloat"==b)b=La?"styleFloat":"cssFloat";a:{var c=b;var d=N(a);if(d.defaultView&&d.defaultView.getComputedStyle&&(d=d.defaultView.getComputedStyle(a,null))){c=d[c]||d.getPropertyValue(c)||"";break a}c=""}a=c||Qa(a,b);if(null===a)a=null;else if(0<=ca(ja,b)){b:{var e=a.match(ma);if(e&&(b=Number(e[1]),c=Number(e[2]),d=Number(e[3]),e=Number(e[4]),0<=b&&255>=b&&0<=c&&255>=c&&0<=d&&255>=d&&0<=e&&1>=e)){b=[b,c,d,e];break b}b=null}if(!b)b:{if(d=a.match(na))if(b=
Number(d[1]),c=Number(d[2]),d=Number(d[3]),0<=b&&255>=b&&0<=c&&255>=c&&0<=d&&255>=d){b=[b,c,d,1];break b}b=null}if(!b)b:{b=a.toLowerCase();c=ia[b.toLowerCase()];if(!c&&(c="#"==b.charAt(0)?b:"#"+b,4==c.length&&(c=c.replace(ka,"#$1$1$2$2$3$3")),!la.test(c))){b=null;break b}b=[parseInt(c.substr(1,2),16),parseInt(c.substr(3,2),16),parseInt(c.substr(5,2),16),1]}a=b?"rgba("+b.join(", ")+")":a}return a}
function Qa(a,b){var c=a.currentStyle||a.style,d=c[b];void 0===d&&"function"==aa(c.getPropertyValue)&&(d=c.getPropertyValue(b));return"inherit"!=d?void 0!==d?d:null:(a=S(a))?Qa(a,b):null}
function Ra(a,b,c){function d(g){var k=U(g);return 0<k.height&&0<k.width?!0:O(g,"PATH")&&(0<k.height||0<k.width)?(g=T(g,"stroke-width"),!!g&&0<parseInt(g,10)):"hidden"!=T(g,"overflow")&&fa(g.childNodes,function(y){return 3==y.nodeType||O(y)&&d(y)})}function e(g){return Sa(g)==V&&ha(g.childNodes,function(k){return!O(k)||e(k)||!d(k)})}if(!O(a))throw Error("Argument to isShown must be of type Element");if(O(a,"BODY"))return!0;if(O(a,"OPTION")||O(a,"OPTGROUP"))return a=Na(a,function(g){return O(g,"SELECT")}),
!!a&&Ra(a,!0,c);var f=Ta(a);if(f)return!!f.image&&0<f.rect.width&&0<f.rect.height&&Ra(f.image,b,c);if(O(a,"INPUT")&&"hidden"==a.type.toLowerCase()||O(a,"NOSCRIPT"))return!1;f=T(a,"visibility");return"collapse"!=f&&"hidden"!=f&&c(a)&&(b||0!=Ua(a))&&d(a)?!e(a):!1}
function Va(a){function b(c){if(O(c)&&("none"==T(c,"display")||"hidden"==T(c,"content-visibility")))return!1;var d;if((d=c.parentNode)&&d.shadowRoot&&void 0!==c.assignedSlot)d=c.assignedSlot?c.assignedSlot.parentNode:null;else if(c.getDestinationInsertionPoints){var e=c.getDestinationInsertionPoints();0<e.length&&(d=e[e.length-1])}if(Pa&&d instanceof ShadowRoot){if(d.host.shadowRoot&&d.host.shadowRoot!==d)return!1;d=d.host}return!d||9!=d.nodeType&&11!=d.nodeType?d&&O(d,"DETAILS")&&!d.open&&!O(c,"SUMMARY")?
!1:!!d&&b(d):!0}return Ra(a,!1,b)}var V="hidden";
function Sa(a){function b(l){function n(ea){if(ea==g)return!0;var wa=T(ea,"display");return 0==wa.lastIndexOf("inline",0)||"contents"==wa||"absolute"==xa&&"static"==T(ea,"position")?!1:!0}var xa=T(l,"position");if("fixed"==xa)return H=!0,l==g?null:g;for(l=S(l);l&&!n(l);)l=S(l);return l}function c(l){var n=l;if("visible"==y)if(l==g&&k)n=k;else if(l==k)return{x:"visible",y:"visible"};n={x:T(n,"overflow-x"),y:T(n,"overflow-y")};l==g&&(n.x="visible"==n.x?"auto":n.x,n.y="visible"==n.y?"auto":n.y);return n}
function d(l){if(l==g){var n=(new Oa(f)).a;l=n.scrollingElement?n.scrollingElement:Aa||"CSS1Compat"!=n.compatMode?n.body||n.documentElement:n.documentElement;n=n.parentWindow||n.defaultView;l=E&&Ca("10")&&n.pageYOffset!=l.scrollTop?new L(l.scrollLeft,l.scrollTop):new L(n.pageXOffset||l.scrollLeft,n.pageYOffset||l.scrollTop)}else l=new L(l.scrollLeft,l.scrollTop);return l}var e=Wa(a),f=N(a),g=f.documentElement,k=f.body,y=T(g,"overflow"),H;for(a=b(a);a;a=b(a)){var r=c(a);if("visible"!=r.x||"visible"!=
r.y){var t=U(a);if(0==t.width||0==t.height)return V;var A=e.a<t.a,Q=e.b<t.b;if(A&&"hidden"==r.x||Q&&"hidden"==r.y)return V;if(A&&"visible"!=r.x||Q&&"visible"!=r.y){A=d(a);Q=e.b<t.b-A.y;if(e.a<t.a-A.x&&"visible"!=r.x||Q&&"visible"!=r.x)return V;e=Sa(a);return e==V?V:"scroll"}A=e.c>=t.a+t.width;t=e.f>=t.b+t.height;if(A&&"hidden"==r.x||t&&"hidden"==r.y)return V;if(A&&"visible"!=r.x||t&&"visible"!=r.y){if(H&&(r=d(a),e.c>=g.scrollWidth-r.x||e.a>=g.scrollHeight-r.y))return V;e=Sa(a);return e==V?V:"scroll"}}}return"none"}
function U(a){var b=Ta(a);if(b)return b.rect;if(O(a,"HTML"))return a=N(a),a=((a?a.parentWindow||a.defaultView:window)||window).document,a="CSS1Compat"==a.compatMode?a.documentElement:a.body,a=new M(a.clientWidth,a.clientHeight),new R(0,0,a.width,a.height);try{var c=a.getBoundingClientRect()}catch(d){return new R(0,0,0,0)}b=new R(c.left,c.top,c.right-c.left,c.bottom-c.top);E&&a.ownerDocument.body&&(a=N(a),b.a-=a.documentElement.clientLeft+a.body.clientLeft,b.b-=a.documentElement.clientTop+a.body.clientTop);
return b}
function Ta(a){var b=O(a,"MAP");if(!b&&!O(a,"AREA"))return null;var c=b?a:O(a.parentNode,"MAP")?a.parentNode:null,d=null,e=null;if(c&&c.name){d='*[usemap="#'+c.name+'"]';c=N(c);var f;if(f="function"!=aa(c.querySelector)&&E&&(E?0<=pa(Da,8):Ca(8))){f=c.querySelector;var g=typeof f;f=!("object"==g&&null!=f||"function"==g)}if(f)throw Error("CSS selection is not supported");if(!d)throw new p(32,"No selector specified");d=v(d);try{var k=c.querySelector(d)}catch(y){throw new p(32,"An invalid or illegal selector was specified");}if(d=
k&&1==k.nodeType?k:null)e=U(d),b||"default"==a.shape.toLowerCase()||(a=Xa(a),b=Math.min(Math.max(a.a,0),e.width),k=Math.min(Math.max(a.b,0),e.height),e=new R(b+e.a,k+e.b,Math.min(a.width,e.width-b),Math.min(a.height,e.height-k)))}return{image:d,rect:e||new R(0,0,0,0)}}
function Xa(a){var b=a.shape.toLowerCase();a=a.coords.split(",");if("rect"==b&&4==a.length){b=a[0];var c=a[1];return new R(b,c,a[2]-b,a[3]-c)}if("circle"==b&&3==a.length)return b=a[2],new R(a[0]-b,a[1]-b,2*b,2*b);if("poly"==b&&2<a.length){b=a[0];c=a[1];for(var d=b,e=c,f=2;f+1<a.length;f+=2)b=Math.min(b,a[f]),d=Math.max(d,a[f]),c=Math.min(c,a[f+1]),e=Math.max(e,a[f+1]);return new R(b,c,d-b,e-c)}return new R(0,0,0,0)}function Wa(a){a=U(a);return new P(a.b,a.a+a.width,a.b+a.height,a.a)}
function Ya(a){return a.replace(/^[^\\S\\xa0]+|[^\\S\\xa0]+$/g,"")}
function Za(a,b,c){if(O(a,"BR"))b.push("");else{var d=O(a,"TD"),e=T(a,"display"),f=!d&&!(0<=ca($a,e)),g=void 0!==a.previousElementSibling?a.previousElementSibling:Ma(a.previousSibling);g=g?T(g,"display"):"";var k=T(a,"float")||T(a,"cssFloat")||T(a,"styleFloat");!f||"run-in"==g&&"none"==k||/^[\\s\\xa0]*$/.test(b[b.length-1]||"")||b.push("");var y=Va(a),H=null,r=null;y&&(H=T(a,"white-space"),r=T(a,"text-transform"));m(a.childNodes,function(t){c(t,b,y,H,r)});a=b[b.length-1]||"";!d&&"table-cell"!=e||!a||
oa(a)||(b[b.length-1]+=" ");f&&"run-in"!=e&&!/^[\\s\\xa0]*$/.test(a)&&b.push("")}}function ab(a,b){Za(a,b,function(c,d,e,f,g){3==c.nodeType&&e?bb(c,d,f,g):O(c)&&ab(c,d)})}var $a="inline inline-block inline-table none table-cell table-column table-column-group".split(" ");
function bb(a,b,c,d){a=a.nodeValue.replace(/[\\u200b\\u200e\\u200f]/g,"");a=a.replace(/(\\r\\n|\\r|\\n)/g,"\\n");if("normal"==c||"nowrap"==c)a=a.replace(/\\n/g," ");a="pre"==c||"pre-wrap"==c?a.replace(/[ \\f\\t\\v\\u2028\\u2029]/g,"\\u00a0"):a.replace(/[ \\f\\t\\v\\u2028\\u2029]+/g," ");"capitalize"==d?(c=/(^|[^'_0-9A-Za-z\\u00C0-\\u02AF\\u1E00-\\u1EFF\\u24B6-\\u24E9\\u0300-\\u036F\\u1AB0-\\u1AFF\\u1DC0-\\u1DFF])([A-Za-z\\u00C0-\\u02AF\\u1E00-\\u1EFF\\u24B6-\\u24E9])/g,a=a.replace(c,function(e,f,g){return f+g.toUpperCase()}),c=/(^|[^'_0-9A-Za-z\\u00C0-\\u02AF\\u1E00-\\u1EFF\\u24B6-\\u24E9])([_*])([A-Za-z\\u00C0-\\u02AF\\u1E00-\\u1EFF\\u24D0-\\u24E9])/g,
a=a.replace(c,function(e,f,g,k){return f+g+k.toUpperCase()})):"uppercase"==d?a=a.toUpperCase():"lowercase"==d&&(a=a.toLowerCase());c=b.pop()||"";oa(c)&&0==a.lastIndexOf(" ",0)&&(a=a.substr(1));b.push(c+a)}function Ua(a){if(La){if("relative"==T(a,"position"))return 1;a=T(a,"filter");return(a=a.match(/^alpha\\(opacity=(\\d*)\\)/)||a.match(/^progid:DXImageTransform.Microsoft.Alpha\\(Opacity=(\\d*)\\)/))?Number(a[1])/100:1}return cb(a)}
function cb(a){var b=1,c=T(a,"opacity");c&&(b=Number(c));(a=S(a))&&(b*=cb(a));return b}
function W(a,b,c,d,e){if(3==a.nodeType&&c)bb(a,b,d,e);else if(O(a))if(O(a,"CONTENT")||O(a,"SLOT")){for(var f=a;f.parentNode;)f=f.parentNode;f instanceof ShadowRoot?(f=O(a,"CONTENT")?a.getDistributedNodes():a.assignedNodes(),m(0<f.length?f:a.childNodes,function(g){W(g,b,c,d,e)})):db(a,b)}else if(O(a,"SHADOW")){for(f=a;f.parentNode;)f=f.parentNode;if(f instanceof ShadowRoot&&(a=f))for(a=a.olderShadowRoot;a;)m(a.childNodes,function(g){W(g,b,c,d,e)}),a=a.olderShadowRoot}else db(a,b)}
function db(a,b){a.shadowRoot&&m(a.shadowRoot.childNodes,function(c){W(c,b,!0,null,null)});Za(a,b,function(c,d,e,f,g){var k=null;1==c.nodeType?k=c:3==c.nodeType&&(k=c);null!=k&&(null!=k.assignedSlot||k.getDestinationInsertionPoints&&0<k.getDestinationInsertionPoints().length)||W(c,d,e,f,g)})};function eb(a){var b=[];Pa?db(a,b):ab(a,b);a=da(b,Ya);return Ya(a.join("\\n")).replace(/\\xa0/g," ")}var X=["se_exportedFunctionSymbol"],Y=h;X[0]in Y||"undefined"==typeof Y.execScript||Y.execScript("var "+X[0]);for(var Z;X.length&&(Z=X.shift());)X.length||void 0===eb?Y[Z]&&Y[Z]!==Object.prototype[Z]?Y=Y[Z]:Y=Y[Z]={}:Y[Z]=eb;; return this.se_exportedFunctionSymbol.apply(null,arguments);}).apply(window, arguments);}
`;
// src/utils/foreignContext.ts
async function foreignContext(scope) {
const held = heldBrowsingContext(scope);
if (!held) {
return void 0;
}
const current = contextIdValue(await getContextManager(held.browser).getCurrentContext());
return held.contextId === current ? void 0 : held;
}
var ref = (elementId) => ({ [ELEMENT_KEY2]: elementId });
var sharedRef = (elementId) => ({ sharedId: elementId });
function driverError(code, message) {
const error = new Error(`${code}: ${message}`);
error.name = code;
return error;
}
var isFailure = (result) => Boolean(result) && typeof result === "object" && typeof result.error === "string";
function throwOnFailure(result) {
if (isFailure(result)) {
throw driverError(result.error, result.message);
}
return result;
}
async function prepareInteraction(el, mode, isFrame) {
const describe = (node) => {
const html = node.outerHTML;
const end = html.indexOf(">");
return end === -1 ? html : html.slice(0, end + 1);
};
const failure = (error, message) => ({ error, message });
const isDisabled = (node) => {
if (["option", "optgroup"].includes(node.localName) && !node.disabled) {
const parent = node.parentElement?.closest("optgroup,select");
return parent ? isDisabled(parent) : false;
}
return node.matches(":disabled");
};
const isReadOnly = (node) => ["input", "textarea"].includes(node.localName) && node.readOnly;
const MUTABLE_INPUT_TYPES = ["color", "date", "datetime-local", "email", "file", "month", "number", "password", "range", "search", "tel", "text", "time", "url", "week"];
const isMutableFormControl = (node) => !isReadOnly(node) && !isDisabled(node) && (node.localName === "textarea" || node.localName === "input" && MUTABLE_INPUT_TYPES.includes(node.type));
const isEditingHost = (node) => node.isContentEditable || node.ownerDocument.designMode === "on";
const paintTree = (node) => {
if (!node.isConnected) {
return { tree: [] };
}
const rects = Array.from(node.getClientRects());
if (rects.length === 0) {
return { tree: [] };
}
const rect = rects.find((r) => r.width > 0 && r.height > 0) || rects[0];
const view = node.ownerDocument.defaultView;
const left = Math.max(0, Math.min(rect.x, rect.x + rect.width));
const right = Math.min(view.innerWidth, Math.max(rect.x, rect.x + rect.width));
const top = Math.max(0, Math.min(rect.y, rect.y + rect.height));
const bottom = Math.min(view.innerHeight, Math.max(rect.y, rect.y + rect.height));
const point = { x: Math.floor((left + right) / 2), y: Math.floor((top + bottom) / 2) };
const root = node.getRootNode();
return { tree: root.elementsFromPoint(point.x, point.y), point };
};
const isInView = (node) => {
const pointerEvents = node.style.pointerEvents;
const style = node.getAttribute("style");
try {
node.style.pointerEvents = "auto";
const { tree } = paintTree(node);
const row = node;
if (node.localName === "tr" && row.cells?.length) {
return tree.includes(row.cells[0]);
}
return tree.includes(node);
} finally {
node.style.pointerEvents = pointerEvents;
if (style === null) {
node.removeAttribute("style");
} else if (node.getAttribute("style") !== style) {
node.setAttribute("style", style);
}
}
};
const visibleInTopLevelViewport = (node) => new Promise((resolve) => {
const timeout = setTimeout(() => {
observer.disconnect();
resolve(true);
}, 100);
const observer = new IntersectionObserver(([entry]) => {
clearTimeout(timeout);
observer.disconnect();
resolve(entry.intersectionRatio >= 1);
});
observer.observe(node);
});
const scrollIntoView2 = async (node) => {
if (!isInView(node)) {
node.scrollIntoView({ behavior: "instant", block: "end", inline: "nearest" });
}
if (isFrame && !await visibleInTopLevelViewport(node)) {
node.scrollIntoView({ behavior: "instant", block: "center", inline: "center" });
}
};
const container = ["option", "optgroup"].includes(el.localName) && el.closest("datalist,select") || el;
if (mode === "screenshot") {
await scrollIntoView2(el);
return null;
}
if (mode === "clear") {
if (isReadOnly(el) || isDisabled(el) || !(isMutableFormControl(el) || isEditingHost(el))) {
return failure("invalid element state", `Element ${describe(el)} is not editable, so it can not be cleared`);
}
await scrollIntoView2(el);
if (!isInView(el)) {
return failure("element not interactable", `Element ${describe(el)} could not be scrolled into view`);
}
return null;
}
if (mode === "click") {
if (el.localName === "input" && el.type === "file") {
return failure("invalid argument", `Cannot click ${describe(el)}, use \`setValue()\` or \`addValue()\` to upload files`);
}
await scrollIntoView2(container);
if (!isInView(container)) {
return failure("element not interactable", `Element ${describe(el)} could not be scrolled into view`);
}
const { tree, point } = paintTree(container);
if (!tree[0] || !container.contains(tree[0])) {
return failure(
"element click intercepted",
`Element ${describe(el)} is not clickable at point (${point?.x}, ${point?.y}). Other element would receive the click: ${tree[0] ? describe(tree[0]) : "none"}`
);
}
return null;
}
if (el.localName === "input" && el.type === "file") {
return null;
}
await scrollIntoView2(container);
const doc = container.ownerDocument;
if (container !== doc.body && container !== doc.documentElement) {
if (doc.activeElement !== container) {
container.focus();
}
if (doc.activeElement !== container) {
return failure("element not interactable", `Element ${describe(el)} is not reachable by keyboard`);
}
}
return null;
}
async function findInContext(held, elementId, using, value, maxNodeCount) {
if (using === "link text" || using === "partial link text") {
return findLinksInContext(held, using, value, elementId, maxNodeCount);
}
if (using === "xpath") {
const found = await held.execute((root, expression) => {
let result;
try {
result = root.ownerDocument.evaluate(expression, root, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
} catch (err) {
return { error: "invalid selector", message: `Unable to locate an element with the xpath expression ${expression}: ${err.message}` };
}
return Array.from({ length: result.snapshotLength }, (_, i) => result.snapshotItem(i)).filter((node) => node?.nodeType === Node.ELEMENT_NODE);
}, ref(elementId), value);
return throwOnFailure(found).slice(0, maxNodeCount);
}
const locator = using === "css selector" || using === "tag name" ? { type: "css", value } : void 0;
if (!locator) {
throw driverError("invalid argument", `Locator strategy "${using}" is not supported for an element of another browsing context`);
}
const { nodes } = await held.browser.browsingContextLocateNodes({
context: held.contextId,
locator,
startNodes: [sharedRef(elementId)],
...maxNodeCount ? { maxNodeCount } : {}
});
return nodes.filter((node) => node.sharedId).map((node) => ({ [ELEMENT_KEY2]: node.sharedId }));
}
async function findLinksInContext(held, using, value, elementId, maxNodeCount) {
const found = await held.execute(
`const getVisibleText = ${GET_VISIBLE_TEXT}
const [root, partial, value] = arguments
return Array.from((root || document).querySelectorAll('a')).filter((a) => {
const text = getVisibleText(a).trim()
return partial ? text.includes(value) : text === value
})`,
elementId ? ref(elementId) : null,
using === "partial link text",
value
);
return found.slice(0, maxNodeCount);
}
async function screenshotInContext(held, elementId) {
await held.execute(prepareInteraction, ref(elementId), "screenshot", held.isFrame);
const rect = await held.execute((el) => {
const { x: x2, y: y2, width, height } = el.getBoundingClientRect();
return { x: x2, y: y2, width, height };
}, ref(elementId));
const { top, x, y } = held.isFrame ? await frameOffset(held) : { top: held, x: 0, y: 0 };
const scroll2 = await top.execute(() => ({ x: window.scrollX, y: window.scrollY }));
const { data } = await held.browser.browsingContextCaptureScreenshot({
context: top.contextId,
origin: "document",
clip: { type: "box", x: scroll2.x + x + rect.x, y: scroll2.y + y + rect.y, width: rect.width, height: rect.height }
});
return data;
}
async function clickInContext(held, elementId) {
throwOnFailure(await held.execute(prepareInteraction, ref(elementId), "click", held.isFrame));
const isOption = await held.execute((el) => {
if (el.localName !== "option") {
return false;
}
const option = el;
const parent = option.closest("datalist,select") || option;
const fire = (type) => parent.dispatchEvent(new MouseEvent(type, { bubbles: true, cancelable: true, view: window }));
fire("mouseover");
fire("mousemove");
fire("mousedown");
parent.focus();
const isDisabled = (node) => {
if (["option", "optgroup"].includes(node.localName) && !node.disabled) {
const ancestor = node.parentElement?.closest("optgroup,select");
return ancestor ? isDisabled(ancestor) : false;
}
return node.matches(":disabled");
};
if (!isDisabled(option)) {
const previous = option.selected;
option.selected = parent.multiple ? !option.selected : true;
parent.dispatchEvent(new Event("input", { bubbles: true }));
if (option.selected !== previous) {
parent.dispatchEvent(new Event("change", { bubbles: true }));
}
}
fire("mouseup");
fire("click");
return true;
}, ref(elementId));
if (isOption) {
return null;
}
await held.browser.inputPerformActions({
context: held.contextId,
actions: [{
id: "pointer",
type: "pointer",
parameters: { pointerType: "mouse" },
actions: [
{ type: "pointerMove", duration: 0, x: 0, y: 0, origin: { type: "element", element: sharedRef(elementId) } },
{ type: "pointerDown", button: 0 },
{ type: "pointerUp", button: 0 }
]
}]
});
await held.browser.inputReleaseActions({ context: held.contextId }).catch((err) => {
if (!err.message.includes("no such frame")) {
throw err;
}
});
return null;
}
async function clearInContext(held, elementId) {
throwOnFailure(await held.execute(prepareInteraction, ref(elementId), "clear", held.isFrame));
await held.execute((el) => {
if (el.isContentEditable || el.ownerDocument.designMode === "on") {
if (el.innerHTML === "") {
return;
}
el.focus();
el.innerHTML = "";
el.blur();
return;
}
const field = el;
const isEmpty = field.type === "file" ? !field.files?.length : field.value === "";
if (field.validity.valid && isEmpty) {
return;
}
field.focus();
field.value = "";
field.dispatchEvent(new Event("change", { bubbles: true }));
field.blur();
}, ref(elementId));
return null;
}
async function sendKeysInContext(held, elementId, text, options = {}) {
const kind = await held.execute((el) => {
const input = el;
if (el.localName !== "input") {
return "text";
}
if (input.type === "file") {
return input.hasAttribute("multiple") ? "files" : "file";
}
return input.type === "date" || input.type === "time" ? "value" : "text";
}, ref(elementId));
if (kind === "file" || kind === "files") {
const files = text.split("\n");
if (files.length === 0 || files.some((file) => file === "")) {
throw driverError("invalid argument", "Expected one or more newline separated file paths");
}
if (kind === "file" && files.length > 1) {
throw driverError("invalid argument", "The file input does not accept multiple files");
}
await held.browser.inputSetFiles({ context: held.contextId, element: sharedRef(elementId), files });
return null;
}
const hadFocus = await held.execute((el) => el.ownerDocument.activeElement === el, ref(elementId));
throwOnFailure(await held.execute(prepareInteraction, ref(elementId), "keys", held.isFrame));
if (kind === "value") {
throwOnFailure(await held.execute((el, value) => {
if (el.readOnly || el.disabled) {
return { error: "element not interactable", message: "The element is not mutable" };
}
el.value = value;
el.dispatchEvent(new Event("input", { bubbles: true }));
el.dispatchEvent(new Event("change", { bubbles: true }));
return null;
}, ref(elementId), text));
return null;
}
if (!hadFocus) {
await held.execute((el) => {
if (el.isContentEditable) {
const selection = el.ownerDocument.getSelection();
selection?.setPosition(el, el.childNodes.length);
return;
}
const field = el;
if (typeof field.value === "string") {
try {
field.setSelectionRange(field.value.length, field.value.length);
} catch {
}
}
}, ref(elementId));
}
if (text.length === 0) {
return null;
}
const params = {
context: held.contextId,
actions: [{
id: "keyboard",
type: "key",
actions: Array.from(text).flatMap((key) => [
{ type: "keyDown", value: key },
{ type: "keyUp", value: key }
])
}]
};
if (options.mask) {
Object.assign(params, { [BIDI_MASK]: true });
}
await held.browser.inputPerformActions(params);
await held.browser.inputReleaseActions({ context: held.contextId });
return null;
}
function unsupported(endpoint) {
return async () => {
throw new Error(
`\`${endpoint}\` is not supported for an element of another browsing context: WebDriver BiDi has no equivalent. Run it on an element of the session's current context.`
);
};
}
var BOOLEAN_ATTRIBUTES = {
audio: ["autoplay", "controls", "loop", "muted"],
button: ["autofocus", "disabled", "formnovalidate"],
details: ["open"],
dialog: ["open"],
fieldset: ["disabled"],
form: ["novalidate"],
iframe: ["allowfullscreen"],
img: ["ismap"],
input: ["autofocus", "checked", "disabled", "formnovalidate", "multiple", "readonly", "required"],
ol: ["reversed"],
optgroup: ["disabled"],
option: ["disabled", "selected"],
script: ["async", "defer"],
select: ["autofocus", "disabled", "multiple", "required"],
textarea: ["autofocus", "disabled", "readonly", "required"],
track: ["default"],
video: ["autoplay", "controls", "loop", "muted"]
};
var FOREIGN_ELEMENT_ENDPOINTS = {
async findElementFromElement(held, elementId, using, value) {
const [first] = await findInContext(held, elementId, using, value, 1);
if (!first) {
throw driverError("no such element", `Unable to locate element: {"method":"${using}","selector":"${value}"}`);
}
return first;
},
findElementsFromElement: (held, elementId, using, value) => findInContext(held, elementId, using, value),
isElementSelected: (held, elementId) => held.execute((el) => {
if (el.localName === "option") {
return el.selected;
}
const type = el.localName === "input" ? el.type : "";
return (type === "checkbox" || type === "radio") && el.checked;
}, ref(elementId)),
/**
* An `<option>` or `<optgroup>` in a disabled `<optgroup>` or `<select>`
* counts as disabled, like the drivers report it. An XML document has no
* enabled form controls.
*/
isElementEnabled: (held, elementId) => held.execute((el) => {
if (["text/xml", "application/xml"].includes(el.ownerDocument.contentType)) {
return false;
}
const isDisabled = (node) => {
if (["option", "optgroup"].includes(node.localName) && !node.disabled) {
const parent = node.parentElement?.closest("optgroup,select");
return parent ? isDisabled(parent) : false;
}
return node.matches(":disabled");
};
return !isDisabled(el);
}, ref(elementId)),
getElementAttribute: (held, elementId, name) => held.execute(
(el, name2, booleanAttributes) => {
const isHTML = el.namespaceURI === "http://www.w3.org/1999/xhtml" && !["text/xml", "application/xml"].includes(el.ownerDocument.contentType);
const lowerName = name2.toLowerCase();
const isCustomElement = el.localName.includes("-");
const isBoolean = isHTML && ((lowerName === "hidden" || lowerName === "itemscope") && !isCustomElement || (booleanAttributes[el.localName] || []).includes(lowerName));
if (isBoolean) {
return el.hasAttribute(name2) ? "true" : null;
}
return el.getAttribute(name2);
},
ref(elementId),
name,
BOOLEAN_ATTRIBUTES
),
getElementProperty: (held, elementId, name) => held.execute(
(el, name2) => {
const value = el[name2];
return value === void 0 ? null : value;
},
ref(elementId),
name
),
/**
* The computed value, or `""` in an XML document. chromedriver reports
* colors as `rgba(r, g, b, a)`, so in Chromium `rgb()` gets an alpha of 1.
*/
getElementCSSValue: async (held, elementId, name) => {
const value = await held.execute(
(el, name2) => ["text/xml", "application/xml"].includes(el.ownerDocument.contentType) ? "" : getComputedStyle(el).getPropertyValue(name2),
ref(elementId),
name
);
const browserName = (held.browser.capabilities.browserName || "").toLowerCase();
const isChromium = held.browser.isChromium || ["chrome", "chromium", "chrome-headless-shell", "msedge", "microsoftedge"].includes(browserName);
return isChromium ? value.replace(/rgb\((\s*\d+\s*),(\s*\d+\s*),(\s*\d+\s*)\)/g, "rgba($1,$2,$3, 1)") : value;
},
getElementText: (held, elementId) => held.execute(
`return (${GET_VISIBLE_TEXT}).apply(null, arguments)`,
ref(elementId)
),
getElementTagName: (held, elementId) => held.execute((el) => el.tagName.toLowerCase(), ref(elementId)),
getElementRect: (held, elementId) => held.execute((el) => {
const rect = el.getBoundingClientRect();
const view = el.ownerDocument.defaultView;
return {
x: rect.x + (view?.scrollX ?? 0),
y: rect.y + (view?.scrollY ?? 0),
width: rect.width,
height: rect.height
};
}, ref(elementId)),
elementClick: (held, elementId) => clickInContext(held, elementId),
elementClear: (held, elementId) => clearInContext(held, elementId),
elementSendKeys: (held, elementId, text, runtime) => sendKeysInContext(held, elementId, String(text), {
mask: runtime?.mask
}),
takeElementScreenshot: (held, elementId) => screenshotInContext(held, elementId),
getElementComputedRole: unsupported("getElementComputedRole"),
getElementComputedLabel: unsupported("getElementComputedLabel")
};
function routeElementEndpoints(properties) {
for (const [name, endpoint] of Object.entries(FOREIGN_ELEMENT_ENDPOINTS)) {
const classic = properties[name]?.value;
if (typeof classic !== "function") {
continue;
}
properties[name] = {
...properties[name],
value: async function(elementId, ...args) {
const held = await foreignContext(this);
if (held) {
return endpoint(held, elementId, ...args);
}
return classic.call(this, elementId, ...args);
}
};
}
return properties;
}
// src/utils/getElementObject.ts
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, propsParam = {}) {
const props = { isReactElement: false, isShadowElement: false, ...propsParam };
const browser = getBrowserObject5(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" }
};
routeElementEndpoints(propertiesObject);
propertiesObject.emit = { value: this.emit.bind(this) };
const element = webdriverMonad(this.options, (client) => {
const elementId = getElementFromResponse(res);
if (elementId) {
client.elementId = elementId;
client[ELEMENT_KEY3] = 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;
if (typeof props.strict === "boolean") {
client.strict = props.strict;
}
if (props.reactOptions) {
client[REACT_OPTIONS] = props.reactOptions;
}
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 = getBrowserObject5(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")
};
routeElementEndpoints(propertiesObject);
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_KEY3] = 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;
client.strict = false;
if (props.reactOptions) {
client[REACT_OPTIONS] = props.reactOptions;
}
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/element/array.ts
import { asyncIterators, chainElementPromise, ELEMENT_ARRAY_WRAP, getBrowserObject as getBrowserObject6, registerElementArrayFactory, WDIO_KIND } from "@wdio/utils";
var states = /* @__PURE__ */ new WeakMap();
var proxyStates = /* @__PURE__ */ new WeakMap();
var RETURN_SELF = /* @__PURE__ */ new Set(["reverse", "sort", "fill", "copyWithin"]);
function stateOf(array) {
const state = states.get(array);
if (!state) {
throw new Error("Expected a WebdriverIO element array");
}
return state;
}
function normalizeSelector(selector) {
if (typeof selector === "undefined") {
return void 0;
}
if (!Array.isArray(selector)) {
return selector;
}
const elements = selector;
if (elements.length > 0 && elements.every((element) => element && element.selector && element.selector === elements[0].selector)) {
return elements[0].selector;
}
return void 0;
}
function prepareMetadata(metadata) {
metadata.selector = normalizeSelector(metadata.selector);
metadata.props = metadata.props ?? [];
metadata.foundWith = metadata.foundWith || "$$";
return metadata;
}
function cloneMetadata(metadata) {
return {
...metadata,
props: [...metadata.props]
};
}
function derivedMetadata(metadata) {
return {
...cloneMetadata(metadata),
refetch: false
};
}
function fill(array, items) {
const state = stateOf(array);
array.splice(0, array.length, ...items);
state.resolved = true;
}
async function load(array) {
const state = stateOf(array);
if (state.resolved) {
return array;
}
if (!state.loading) {
const base = async () => {
const items = state.loader ? await state.loader() : [];
fill(array, items);
return items;
};
let run2 = base;
for (const wrapper of state.wrappers) {
const inner = run2;
run2 = () => wrapper(inner);
}
state.loading = run2().then((items) => {
if (!state.resolved) {
fill(array, items);
}
return array;
});
}
await state.loading;
return array;
}
function listForIteration(array) {
return stateOf(array).self ?? array;
}
function integerIndex(index) {
const truncated = Math.trunc(index);
return Number.isNaN(truncated) ? 0 : truncated;
}
function readIndex(array, index) {
const normalized = integerIndex(index);
const state = stateOf(array);
const multiRemote2 = state.metadata.isMultiRemote === true;
if (!state.resolved) {
return chainElementPromise(elementAt(array, normalized), multiRemote2);
}
if (normalized < 0 || !Number.isFinite(normalized) || state.metadata.refetch === false) {
return Array.prototype.at.call(array, normalized);
}
if (normalized < array.length) {
return array[normalized];
}
return chainElementPromise(elementAt(array, normalized), multiRemote2);
}
async function elementAt(array, index) {
const items = await load(array);
if (index < 0) {
return items.at(index);
}
if (index < items.length) {
return items[index];
}
const { parent, foundWith, selector, props, refetch, wrapMultiRemote } = stateOf(array).metadata;
if (refetch === false || !parent) {
return void 0;
}
const timeoutMsg = `Index out of bounds! $$(${String(selector)}) returned only ${items.length} elements.`;
const findIn = async (target) => {
const query = target[foundWith];
if (typeof query !== "function") {
return false;
}
const refetched = await query.call(target, selector, ...props);
return refetched && refetched.length > index ? refetched[index] : false;
};
if (wrapMultiRemote) {
const multiRemoteParent = parent;
const elements = await multiRemoteParent.waitUntil(function() {
return findIn(this);
}, { timeoutMsg });
return wrapMultiRemote(elements);
}
const browser = getBrowserObject6(parent);
return browser.waitUntil(() => findIn(parent), {
timeout: browser.options?.waitforTimeout,
timeoutMsg
});
}
var methods = {
async map(callback, thisArg) {
const items = await load(this);
return asyncIterators.map(listForIteration(items), callback, thisArg);
},
async mapSeries(callback, thisArg) {
const items = await load(this);
return asyncIterators.mapSeries(listForIteration(items), callback, thisArg);
},
async filter(callback, thisArg) {
const state = stateOf(this);
const items = await load(this);
const matched = await asyncIterators.filter(listForIteration(items), callback, thisArg);
return fromResolved(matched, derivedMetadata(state.metadata));
},
async filterSeries(callback, thisArg) {
const state = stateOf(this);
const items = await load(this);
const matched = await asyncIterators.filterSeries(listForIteration(items), callback, thisArg);
return fromResolved(matched, derivedMetadata(state.metadata));
},
async forEach(callback, thisArg) {
const items = await load(this);
return asyncIterators.forEach(listForIteration(items), callback, thisArg);
},
async forEachSeries(callback, thisArg) {
const items = await load(this);
return asyncIterators.forEachSeries(listForIteration(items), callback, thisArg);
},
async find(callback, thisArg) {
const items = await load(this);
return asyncIterators.find(listForIteration(items), callback, thisArg);
},
async findSeries(callback, thisArg) {
const items = await load(this);
return asyncIterators.findSeries(listForIteration(items), callback, thisArg);
},
async findIndex(callback, thisArg) {
const items = await load(this);
return asyncIterators.findIndex(listForIteration(items), callback, thisArg);
},
async findIndexSeries(callback, thisArg) {
const items = await load(this);
return asyncIterators.findIndexSeries(listForIteration(items), callback, thisArg);
},
async some(callback, thisArg) {
const items = await load(this);
return asyncIterators.some(listForIteration(items), callback, thisArg);
},
async someSeries(callback, thisArg) {
const items = await load(this);
return asyncIterators.someSeries(listForIteration(items), callback, thisArg);
},
async every(callback, thisArg) {
const items = await load(this);
return asyncIterators.every(listForIteration(items), callback, thisArg);
},
async everySeries(callback, thisArg) {
const items = await load(this);
return asyncIterators.everySeries(listForIteration(items), callback, thisArg);
},
async reduce(callback, initialValue) {
const items = await load(this);
return asyncIterators.reduce(listForIteration(items), callback, initialValue);
},
entries() {
const array = this;
return (async function* entries() {
const items = await load(array);
for (let index = 0; index < items.length; index++) {
if (index in items) {
yield [index, items[index]];
}
}
})();
},
slice(start, end) {
const state = stateOf(this);
if (state.resolved) {
return fromResolved(Array.prototype.slice.call(this, start, end), derivedMetadata(state.metadata));
}
return fromAsyncCallback(async () => {
const items = await load(this);
return items.slice(start, end);
}, derivedMetadata(state.metadata));
},
at(index) {
return readIndex(this, index);
},
async getElements() {
await load(this);
return stateOf(this).self;
}
};
function proxify(array, state) {
const proxy = new Proxy(array, {
get(target, prop, receiver) {
const current = stateOf(target);
if (prop === ELEMENT_ARRAY_WRAP) {
return Reflect.get(target, prop);
}
if (prop === WDIO_KIND) {
return "element-array";
}
if (prop === "then") {
if (current.resolved) {
return void 0;
}
return (onFulfilled, onRejected) => load(target).then(
() => onFulfilled ? onFulfilled(current.self) : current.self,
onRejected
);
}
if ((prop === "catch" || prop === "finally") && !current.resolved) {
return (handler) => {
const settled = load(target).then(() => current.self);
return prop === "catch" ? settled.catch(handler) : settled.finally(handler);
};
}
if (prop === "length" && !current.resolved) {
return load(target).then(() => target.length);
}
if (prop === "selector" || prop === "parent" || prop === "foundWith" || prop === "props") {
return current.metadata[prop];
}
if (prop === "isMultiRemote") {
return current.metadata.isMultiRemote === true;
}
if (typeof prop === "string" && /^\d+$/.test(prop)) {
return readIndex(target, Number(prop));
}
if (prop === Symbol.iterator) {
return function* iterator() {
if (!stateOf(target).resolved) {
throw new Error(
"Cannot synchronously iterate over an element list that has not resolved yet. Use `for await (const el of $$('...')) { ... }` instead."
);
}
yield* Array.prototype[Symbol.iterator].call(target);
};
}
if (prop === Symbol.asyncIterator) {
return async function* asyncIterator() {
const items = await load(target);
yield* items;
};
}
if (typeof prop === "string" && RETURN_SELF.has(prop)) {
return (...args) => {
if (!stateOf(target).resolved) {
throw new Error(`Cannot ${prop} an element list that has not resolved yet`);
}
const result = Array.prototype[prop].apply(target, args);
return result === target ? current.self : result;
};
}
if (typeof prop === "string" && Object.prototype.hasOwnProperty.call(methods, prop)) {
return methods[prop].bind(target);
}
const value = Reflect.get(target, prop, receiver);
return typeof value === "function" ? value.bind(target) : value;
},
/**
* `parent`, `foundWith` and `getElements` live on the proxy, not the
* raw array. `in` does not use the get trap, and expect-webdriverio
* detects an element list with `'getElements' in elements`. Without
* this, a resolved list looks like a plain array of elements and
* `elements.map(fn).join()` calls the async map.
*/
has(target, prop) {
if (prop === WDIO_KIND || prop === "selector" || prop === "parent" || prop === "foundWith" || prop === "props" || prop === "isMultiRemote" || prop === "getElements") {
return true;
}
if (typeof prop === "string" && (RETURN_SELF.has(prop) || Object.prototype.hasOwnProperty.call(methods, prop))) {
return true;
}
return Reflect.has(target, prop);
},
set(target, prop, value) {
if (prop === "parent" || prop === "selector" || prop === "foundWith" || prop === "props") {
stateOf(target).metadata[prop] = value;
return true;
}
if (prop === "isMultiRemote") {
stateOf(target).metadata.isMultiRemote = Boolean(value);
return true;
}
return Reflect.set(target, prop, value, target);
}
});
state.self = proxy;
proxyStates.set(proxy, state);
return proxy;
}
function create(elements, loader, metadata) {
const prepared = prepareMetadata(metadata);
const array = elements ? [...elements] : [];
const state = {
metadata: prepared,
resolved: Boolean(elements),
loader,
wrappers: []
};
states.set(array, state);
Object.defineProperty(array, ELEMENT_ARRAY_WRAP, {
configurable: false,
enumerable: false,
value: (wrapper) => {
if (state.resolved || state.loading) {
return;
}
state.wrappers.push(wrapper);
}
});
return proxify(array, state);
}
var ElementArray = {
fromAsyncCallback(loader, metadata) {
return create(void 0, loader, metadata);
},
fromResolved(elements, metadata) {
return create(elements, void 0, metadata);
}
};
function fromAsyncCallback(loader, metadata) {
return ElementArray.fromAsyncCallback(loader, metadata);
}
function fromResolved(elements, metadata) {
return ElementArray.fromResolved(elements, metadata);
}
registerElementArrayFactory((loader, metadata) => {
const chainedMetadata = metadata;
return ElementArray.fromAsyncCallback(async () => {
const items = await loader();
chainedMetadata.wrapMultiRemote ??= proxyStates.get(items)?.metadata.wrapMultiRemote;
return items;
}, chainedMetadata);
});
// 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
},
/**
* Throw if `$` matches more than one element
*/
strictSelectors: {
type: "boolean",
default: true
},
/**
* Maximum size of the response body (in bytes) that `browser.mock` collects.
* Use 0 to disable data collection of the spied response payload. Without
* this entry, `remote()` drops the option in standalone mode.
*/
maxSpyCollectedBodySize: {
type: "number",
validate: (param) => {
if (!Number.isInteger(param) || param < 0) {
throw new Error("maxSpyCollectedBodySize should be an integer of 0 or more");
}
}
},
/**
* 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 ROLE_SELECTOR = "role/";
var restoreFunctions = /* @__PURE__ */ new WeakMap();
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
function $$(selector) {
const metadata = {
selector,
foundWith: "$$",
parent: this,
props: []
};
return ElementArray.fromAsyncCallback(async () => {
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);
metadata.parent = this;
return elements3;
}
const res2 = await findDeepElements.call(this, selector);
const elements2 = await getElements.call(this, selector, res2);
metadata.parent = getParent.call(this, res2);
return elements2;
}
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);
metadata.parent = getParent.call(this, res);
return elements;
}, metadata);
}
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_KEY8 } from "webdriver";
// src/utils/strictMode.ts
import { getBrowserObject as getBrowserObject7 } from "@wdio/utils";
import isPlainObject2 from "is-plain-obj";
import { ELEMENT_KEY as ELEMENT_KEY7 } from "webdriver";
// src/session/shadowRoot.ts
import logger5 from "@wdio/logger";
import customElementWrapper from "./scripts/customElement.js";
// src/utils/elementChecks.ts
import { ELEMENT_KEY as ELEMENT_KEY6 } from "webdriver";
import logger4 from "@wdio/logger";
import elementContains from "./scripts/elementContains.js";
import elementsContainedIn from "./scripts/elementsContainedIn.js";
import elementsConnected from "./scripts/elementsConnected.js";
// src/utils/bidi/index.ts
import { ELEMENT_KEY as ELEMENT_KEY5, SHADOW_ELEMENT_KEY as SHADOW_ELEMENT_KEY2 } from "webdriver";
// src/commands/constant.ts
var SCRIPT_PREFIX = "/* __wdio script__ */";
var SCRIPT_SUFFIX = "/* __wdio script end__ */";
var reactScript = '"use strict";(()=>{var l=e=>e instanceof HTMLElement||e instanceof Text,p=e=>typeof e=="object"&&!Array.isArray(e),u=e=>e!==null&&p(e),a=e=>Object.keys(e),b=e=>{var t;if(!e||typeof e=="string")return e;let n={};for(let o of Reflect.ownKeys(e))(t=Object.getOwnPropertyDescriptor(e,o))!=null&&t.enumerable&&(n[o]=e[o]);return delete n.children,n},k=e=>{if(e)return e.baseState||e},w=(e,n)=>Array.isArray(e)&&Array.isArray(n)&&e.some(t=>n.includes(t)),y=(e={},n={})=>{if(!a(e).length)return!0;if(n===null||!a(n).length)return!1;let t=e,o=n,s=a(t).filter(i=>a(o).includes(i)),r=[];for(let i of s)u(t[i])&&u(o[i])&&r.push(y(t[i],o[i])),(t[i]===o[i]||w(t[i],o[i]))&&r.push(o);return r.length>0&&r.filter(Boolean).length===s.length},m=e=>{let n={children:[]};if(!e)return n;let t=e.type;n.name=typeof t=="function"?t.displayName||t.name:t,n.props=b(e.memoizedProps),n.state=k(e.memoizedState);for(let o=e.child;o;o=o.sibling)n.children.push(m(o));return typeof e.type=="function"&&n.children.length>1?(n.node=n.children.flatMap(o=>o.node||[]),n.isFragment=!0):l(e.stateNode)?n.node=e.stateNode:n.node=e.child&&l(e.child.stateNode)?e.child.stateNode:null,n},N=e=>{for(;e.length;){let n=e.shift();if(n.node)return n.node;e.push(...n.children)}},F=(e,n)=>{let t=[...e],o=[];for(;t.length;)for(let s of t.shift().children)n(s)&&(s.node||(s.node=N([...s.children])),o.push(s)),t.push(s);return o},f=(e,n)=>{var s;let t=n;typeof t=="string"&&t.includes("(")&&(t=(s=t.split("(").find(r=>r.includes(")")))==null?void 0:s.replace(/\\)*/g,""));let o=e.split("*").map(r=>r.replace(/([.*+?^=!:${}()|[\\]/\\\\])/g,"\\\\$1")).join(".+");return new RegExp(`^${o}$`).test(t)},h=(e,n,t)=>typeof t=="function"?(console.warn("Functions are not supported as filter matchers"),[]):e.filter(o=>p(t)&&y(t,o[n])||Array.isArray(t)&&w(t,o[n])||o[n]===t),O=(e,n,t,o)=>{let r=e.split(" ").filter(Boolean).map(i=>i.trim()).reduce((i,d)=>F(i,c=>typeof c.name=="string"?f(d,c.name):c.name!==null&&typeof c.name=="object"&&f(d,c.name.displayName)),[m(o)]);return n&&a(n).length&&(r=h(r,"props",n)),t&&a(t).length&&(r=h(r,"state",t)),r},_=e=>e.startsWith("__reactFiber$")||e.startsWith("__reactInternalInstance$")||e.startsWith("__reactContainer$"),j=e=>!!e._reactRootContainer||Object.keys(e).some(n=>n.startsWith("__reactContainer$"));function*g(e){let n=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT);for(let t=n.nextNode();t;t=n.nextNode())yield t,t.shadowRoot&&(yield*g(t.shadowRoot))}var E=e=>!!(e&&(e.child||e.alternate&&!e.stateNode.pendingLanes)),T=()=>{let e=[];for(let n of g(document)){let t=j(n)?R(n):void 0;t&&!e.includes(t)&&e.push(t)}return e},R=e=>{let n=e._reactRootContainer;if(n)return(n._internalRoot||n).current;let t=Object.keys(e),o=t.find(r=>r.startsWith("__reactContainer$"))||t.find(_),s=o?e[o]:void 0;for(;s&&s.return;)s=s.return;return s&&s.tag===3?s.stateNode.current:void 0},H=e=>{let n=R(e);if(!n||n.stateNode.containerInfo===e)return n;let t=[n];for(;t.length;){let o=t.pop();if(o.stateNode===e)return o;o.sibling&&t.push(o.sibling),o.child&&t.push(o.child)}};window.__wdioReact={findRoots:T,findFiber:H,isRendered:E,query:O};})();\n';
var accessibilityScript = `"use strict";(()=>{var ge=Object.prototype.toString;function W(e){return typeof e=="function"||ge.call(e)==="[object Function]"}function ve(e){var t=Number(e);return isNaN(t)?0:t===0||!isFinite(t)?t:(t>0?1:-1)*Math.floor(Math.abs(t))}var ye=Math.pow(2,53)-1;function he(e){var t=ve(e);return Math.min(Math.max(t,0),ye)}function c(e,t){var i=Array,n=Object(e);if(e==null)throw new TypeError("Array.from requires an array-like object - not null or undefined");if(typeof t<"u"&&!W(t))throw new TypeError("Array.from: when provided, the second argument must be a function");for(var o=he(n.length),b=W(i)?Object(new i(o)):new Array(o),f=0,d;f<o;)d=n[f],t?b[f]=t(d,f):b[f]=d,f+=1;return b.length=o,b}function N(e){"@babel/helpers - typeof";return N=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(t){return typeof t}:function(t){return t&&typeof Symbol=="function"&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},N(e)}function we(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function Q(e,t){for(var i=0;i<t.length;i++){var n=t[i];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,X(n.key),n)}}function Se(e,t,i){return t&&Q(e.prototype,t),i&&Q(e,i),Object.defineProperty(e,"prototype",{writable:!1}),e}function Ee(e,t,i){return t=X(t),t in e?Object.defineProperty(e,t,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[t]=i,e}function X(e){var t=xe(e,"string");return N(t)=="symbol"?t:t+""}function xe(e,t){if(N(e)!="object"||!e)return e;var i=e[Symbol.toPrimitive];if(i!==void 0){var n=i.call(e,t||"default");if(N(n)!="object")return n;throw new TypeError("@@toPrimitive must return a primitive value.")}return(t==="string"?String:Number)(e)}var Ae=(function(){function e(){var t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:[];we(this,e),Ee(this,"items",void 0),this.items=t}return Se(e,[{key:"add",value:function(i){return this.has(i)===!1&&this.items.push(i),this}},{key:"clear",value:function(){this.items=[]}},{key:"delete",value:function(i){var n=this.items.length;return this.items=this.items.filter(function(o){return o!==i}),n!==this.items.length}},{key:"forEach",value:function(i){var n=this;this.items.forEach(function(o){i(o,o,n)})}},{key:"has",value:function(i){return this.items.indexOf(i)!==-1}},{key:"size",get:function(){return this.items.length}}])})(),J=typeof Set>"u"?Set:Ae;function s(e){var t;return(t=e.localName)!==null&&t!==void 0?t:e.tagName.toLowerCase()}var Te={article:"article",aside:"complementary",button:"button",datalist:"listbox",dd:"definition",details:"group",dialog:"dialog",dt:"term",fieldset:"group",figure:"figure",form:"form",footer:"contentinfo",h1:"heading",h2:"heading",h3:"heading",h4:"heading",h5:"heading",h6:"heading",header:"banner",hr:"separator",html:"document",legend:"legend",li:"listitem",math:"math",main:"main",menu:"list",nav:"navigation",ol:"list",optgroup:"group",option:"option",output:"status",progress:"progressbar",section:"region",summary:"button",table:"table",tbody:"rowgroup",textarea:"textbox",tfoot:"rowgroup",td:"cell",th:"columnheader",thead:"rowgroup",tr:"row",ul:"list"},Le={caption:new Set(["aria-label","aria-labelledby"]),code:new Set(["aria-label","aria-labelledby"]),deletion:new Set(["aria-label","aria-labelledby"]),emphasis:new Set(["aria-label","aria-labelledby"]),generic:new Set(["aria-label","aria-labelledby","aria-roledescription"]),insertion:new Set(["aria-label","aria-labelledby"]),none:new Set(["aria-label","aria-labelledby"]),paragraph:new Set(["aria-label","aria-labelledby"]),presentation:new Set(["aria-label","aria-labelledby"]),strong:new Set(["aria-label","aria-labelledby"]),subscript:new Set(["aria-label","aria-labelledby"]),superscript:new Set(["aria-label","aria-labelledby"])};function Ne(e,t){return["aria-atomic","aria-busy","aria-controls","aria-current","aria-description","aria-describedby","aria-details","aria-dropeffect","aria-flowto","aria-grabbed","aria-hidden","aria-keyshortcuts","aria-label","aria-labelledby","aria-live","aria-owns","aria-relevant","aria-roledescription"].some(function(i){var n;return e.hasAttribute(i)&&!((n=Le[t])!==null&&n!==void 0&&n.has(i))})}function U(e,t){return Ne(e,t)}function T(e){var t=Re(e);if(t===null||O.indexOf(t)!==-1){var i=Oe(e);if(O.indexOf(t||"")===-1||U(e,i||""))return i}return t}function Oe(e){var t=Te[s(e)];if(t!==void 0)return t;switch(s(e)){case"a":case"area":case"link":if(e.hasAttribute("href"))return"link";break;case"img":return e.getAttribute("alt")===""&&!U(e,"img")?"presentation":"img";case"input":{var i=e,n=i.type;switch(n){case"button":case"image":case"reset":case"submit":return"button";case"checkbox":case"radio":return n;case"range":return"slider";case"email":case"tel":case"text":case"url":return e.hasAttribute("list")?"combobox":"textbox";case"search":return e.hasAttribute("list")?"combobox":"searchbox";case"number":return"spinbutton";default:return null}}case"select":return e.hasAttribute("multiple")||e.size>1?"listbox":"combobox"}return null}function Re(e){var t=e.getAttribute("role");if(t!==null){var i=t.trim().split(" ")[0];if(i.length>0)return i}return null}var O=["presentation","none"];function a(e){return e!==null&&e.nodeType===e.ELEMENT_NODE}function _(e){return a(e)&&s(e)==="caption"}function R(e){return a(e)&&s(e)==="input"}function Y(e){return a(e)&&s(e)==="optgroup"}function Z(e){return a(e)&&s(e)==="select"}function F(e){return a(e)&&s(e)==="table"}function ee(e){return a(e)&&s(e)==="textarea"}function te(e){var t=e.ownerDocument===null?e:e.ownerDocument,i=t.defaultView;if(i===null)throw new TypeError("no window available");return i}function re(e){return a(e)&&s(e)==="fieldset"}function ie(e){return a(e)&&s(e)==="legend"}function ne(e){return a(e)&&s(e)==="slot"}function Ie(e){return a(e)&&e.ownerSVGElement!==void 0}function ae(e){return a(e)&&s(e)==="svg"}function oe(e){return Ie(e)&&s(e)==="title"}function I(e,t){if(a(e)&&e.hasAttribute(t)){var i=e.getAttribute(t).split(" "),n=e.getRootNode?e.getRootNode():e.ownerDocument;return i.map(function(o){return n.getElementById(o)}).filter(function(o){return o!==null})}return[]}function p(e,t){return a(e)?t.indexOf(T(e))!==-1:!1}function Ce(e){return e.trim().replace(/\\s\\s+/g," ")}function Pe(e,t){if(!a(e))return!1;if(e.hasAttribute("hidden")||e.getAttribute("aria-hidden")==="true")return!0;var i=t(e);return i.getPropertyValue("display")==="none"||i.getPropertyValue("visibility")==="hidden"}function Me(e){return p(e,["button","combobox","listbox","textbox"])||se(e,"range")}function se(e,t){if(!a(e))return!1;if(t==="range")return p(e,["meter","progressbar","scrollbar","slider","spinbutton"]);throw new TypeError("No knowledge about abstract role '".concat(t,"'. This is likely a bug :("))}function ue(e,t){var i=c(e.querySelectorAll(t));return I(e,"aria-owns").forEach(function(n){i.push.apply(i,c(n.querySelectorAll(t)))}),i}function je(e){return Z(e)?e.selectedOptions||ue(e,"[selected]"):ue(e,'[aria-selected="true"]')}function _e(e){return p(e,O)}function He(e){return _(e)}function De(e){return p(e,["button","cell","checkbox","columnheader","gridcell","heading","label","legend","link","menuitem","menuitemcheckbox","menuitemradio","option","radio","row","rowheader","switch","tab","tooltip","treeitem"])}function ke(e){return!1}function Ve(e){return R(e)||ee(e)?e.value:e.textContent||""}function le(e){var t=e.getPropertyValue("content");return/^["'].*["']$/.test(t)?t.slice(1,-1):""}function fe(e){var t=s(e);return t==="button"||t==="input"&&e.getAttribute("type")!=="hidden"||t==="meter"||t==="output"||t==="progress"||t==="select"||t==="textarea"}function ce(e){if(fe(e))return e;var t=null;return e.childNodes.forEach(function(i){if(t===null&&a(i)){var n=ce(i);n!==null&&(t=n)}}),t}function Ge(e){if(e.control!==void 0)return e.control;var t=e.getAttribute("for");return t!==null?e.ownerDocument.getElementById(t):ce(e)}function $e(e){var t=e.labels;if(t===null)return t;if(t!==void 0)return c(t);if(!fe(e))return null;var i=e.ownerDocument;return c(i.querySelectorAll("label")).filter(function(n){return Ge(n)===e})}function qe(e){var t=e.assignedNodes();return t.length===0?c(e.childNodes):t}function H(e){var t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},i=new J,n=typeof Map>"u"?void 0:new Map,o=te(e),b=t.compute,f=b===void 0?"name":b,d=t.computedStyleSupportsPseudoElements,k=d===void 0?t.getComputedStyle!==void 0:d,V=t.getComputedStyle,C=V===void 0?o.getComputedStyle.bind(o):V,G=t.hidden,pe=G===void 0?!1:G,$=function(u,l){if(l!==void 0)throw new Error("use uncachedGetComputedStyle directly for pseudo elements");if(n===void 0)return C(u);var m=n.get(u);if(m)return m;var g=C(u,l);return n.set(u,g),g};function P(r,u){var l="";if(a(r)&&k){var m=C(r,"::before"),g=le(m);l="".concat(g," ").concat(l)}var y=ne(r)?qe(r):c(r.childNodes).concat(I(r,"aria-owns"));if(y.forEach(function(v){var x=w(v,{isEmbeddedInLabel:u.isEmbeddedInLabel,isReferenced:!1,recursion:!0}),h=a(v)?$(v).getPropertyValue("display"):"inline",L=h!=="inline"?" ":"";l+="".concat(L).concat(x).concat(L)}),a(r)&&k){var S=C(r,"::after"),E=le(S);l="".concat(l," ").concat(E)}return l.trim()}function A(r,u){var l=r.getAttributeNode(u);return l!==null&&!i.has(l)&&l.value.trim()!==""?(i.add(l),l.value):null}function be(r){return a(r)?A(r,"title"):null}function me(r){if(!a(r))return null;if(re(r)){i.add(r);for(var u=c(r.childNodes),l=0;l<u.length;l+=1){var m=u[l];if(ie(m))return w(m,{isEmbeddedInLabel:!1,isReferenced:!1,recursion:!1})}}else if(F(r)){i.add(r);for(var g=c(r.childNodes),y=0;y<g.length;y+=1){var S=g[y];if(_(S))return w(S,{isEmbeddedInLabel:!1,isReferenced:!1,recursion:!1})}}else if(ae(r)){i.add(r);for(var E=c(r.childNodes),v=0;v<E.length;v+=1){var x=E[v];if(oe(x))return x.textContent}return null}else if(s(r)==="img"||s(r)==="area"){var h=A(r,"alt");if(h!==null)return h}else if(Y(r)){var L=A(r,"label");if(L!==null)return L}if(R(r)&&(r.type==="button"||r.type==="submit"||r.type==="reset")){var q=A(r,"value");if(q!==null)return q;if(r.type==="submit")return"Submit";if(r.type==="reset")return"Reset"}var M=$e(r);if(M!==null&&M.length!==0)return i.add(r),c(M).map(function(j){return w(j,{isEmbeddedInLabel:!0,isReferenced:!1,recursion:!0})}).filter(function(j){return j.length>0}).join(" ");if(R(r)&&r.type==="image"){var B=A(r,"alt");if(B!==null)return B;var K=A(r,"title");return K!==null?K:"Submit Query"}if(p(r,["button"])){var z=P(r,{isEmbeddedInLabel:!1,isReferenced:!1});if(z!=="")return z}return null}function w(r,u){if(i.has(r))return"";if(!pe&&Pe(r,$)&&!u.isReferenced)return i.add(r),"";var l=a(r)?r.getAttributeNode("aria-labelledby"):null,m=l!==null&&!i.has(l)?I(r,"aria-labelledby"):[];if(f==="name"&&!u.isReferenced&&m.length>0)return i.add(l),m.map(function(h){return w(h,{isEmbeddedInLabel:u.isEmbeddedInLabel,isReferenced:!0,recursion:!1})}).join(" ");var g=u.recursion&&Me(r)&&f==="name";if(!g){var y=(a(r)&&r.getAttribute("aria-label")||"").trim();if(y!==""&&f==="name")return i.add(r),y;if(!_e(r)){var S=me(r);if(S!==null)return i.add(r),S}}if(p(r,["menu"]))return i.add(r),"";if(g||u.isEmbeddedInLabel||u.isReferenced){if(p(r,["combobox","listbox"])){i.add(r);var E=je(r);return E.length===0?R(r)?r.value:"":c(E).map(function(h){return w(h,{isEmbeddedInLabel:u.isEmbeddedInLabel,isReferenced:!1,recursion:!0})}).join(" ")}if(se(r,"range"))return i.add(r),r.hasAttribute("aria-valuetext")?r.getAttribute("aria-valuetext"):r.hasAttribute("aria-valuenow")?r.getAttribute("aria-valuenow"):r.getAttribute("value")||"";if(p(r,["textbox"]))return i.add(r),Ve(r)}if(De(r)||a(r)&&u.isReferenced||He(r)||ke(r)){var v=P(r,{isEmbeddedInLabel:u.isEmbeddedInLabel,isReferenced:!1});if(v!=="")return i.add(r),v}if(r.nodeType===r.TEXT_NODE)return i.add(r),r.textContent||"";if(u.recursion)return i.add(r),P(r,{isEmbeddedInLabel:u.isEmbeddedInLabel,isReferenced:!1});var x=be(r);return x!==null?(i.add(r),x):(i.add(r),"")}return Ce(w(e,{isEmbeddedInLabel:!1,isReferenced:f==="description",recursion:!1}))}function Be(e){return p(e,["caption","code","deletion","emphasis","generic","insertion","none","paragraph","presentation","strong","subscript","superscript"])}function D(e){var t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{};return Be(e)?"":H(e,t)}function de(e){var t,i=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},n=i.getComputedStyle,o=n===void 0?(t=e.ownerDocument.defaultView)===null||t===void 0?void 0:t.getComputedStyle:n,b=i.isSubtreeInaccessible,f=b===void 0?Ke:b;if(typeof o!="function")throw new TypeError("Owner document of the element needs to have an associated window.");if(o(e).visibility==="hidden")return!0;for(var d=e;d;){if(f(d,{getComputedStyle:o}))return!0;d=d.parentElement}return!1}function Ke(e){var t,i=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},n=i.getComputedStyle,o=n===void 0?(t=e.ownerDocument.defaultView)===null||t===void 0?void 0:t.getComputedStyle:n;if(typeof o!="function")throw new TypeError("Owner document of the element needs to have an associated window.");return e.hidden===!0||e.getAttribute("aria-hidden")==="true"||o(e).display==="none"}window.__wdioA11y={computeAccessibleName:D,getRole:T,isInaccessible:de};})();
`;
// 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") || [];
let wrapCommandIndex = -1;
for (let i = stack.length - 1; i >= 0; i--) {
if (stack[i].includes("Context.executeAsync")) {
wrapCommandIndex = i;
break;
}
}
const executeLine = stack[wrapCommandIndex - 1];
if (failureLine && executeLine) {
const line = executeLine.replace("file://", "").split(":");
const row = line.length > 3 ? line[2] : line[1];
const [errorMessage2, ...restOfStack] = stack;
const linePrefix = ` ${row} \u2502 `;
const codeLine = [
linePrefix + failureLine,
" ".repeat(linePrefix.length - 2) + "\u2575 " + "~".repeat(failureLine.length),
""
];
return [errorMessage2, 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/base64.ts
function encodeBase64(bytes) {
if (typeof Buffer !== "undefined") {
return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString("base64");
}
let binary = "";
for (let offset = 0; offset < bytes.length; offset += 32768) {
binary += String.fromCharCode(...bytes.subarray(offset, offset + 32768));
}
return btoa(binary);
}
function decodeBase64(value) {
if (typeof Buffer !== "undefined") {
return Buffer.from(value, "base64");
}
return Uint8Array.from(atob(value), (byte) => byte.charCodeAt(0));
}
// src/utils/bidi/serialize.ts
var SERIALIZED_BLOB_KEY = "__wdioSerializedBlob__";
var SERIALIZED_BLOB_KIND_BLOB = "blob";
var SERIALIZED_BLOB_KIND_FILE = "file";
var BASE64_PATTERN = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
var BLOB_TRANSFER_KEYS = /* @__PURE__ */ new Set(["data", "type", "size", "kind", "props", SERIALIZED_BLOB_KEY]);
var FILE_TRANSFER_KEYS = /* @__PURE__ */ new Set([...BLOB_TRANSFER_KEYS, "name", "lastModified"]);
var BASE64_CHUNK_SIZE = 24576;
var SERIALIZER_HELPER = `
const __wdioSerializedBlobKey = ${JSON.stringify(SERIALIZED_BLOB_KEY)};
const __wdioSerializedBlobKindBlob = ${JSON.stringify(SERIALIZED_BLOB_KIND_BLOB)};
const __wdioSerializedBlobKindFile = ${JSON.stringify(SERIALIZED_BLOB_KIND_FILE)};
const __wdioCycle = Object.create(null);
function __wdioTag(value) {
return Object.prototype.toString.call(value);
}
function __wdioIsIndexKey(key) {
return String(Number(key)) === key && Number.isInteger(Number(key)) && Number(key) >= 0;
}
function __wdioHostValue(value, key) {
let proto = Object.getPrototypeOf(value);
while (proto) {
const desc = Object.getOwnPropertyDescriptor(proto, key);
if (desc) {
return typeof desc.get === 'function' ? desc.get.call(value) : desc.value;
}
proto = Object.getPrototypeOf(proto);
}
return value[key];
}
function __wdioIsBlob(value) {
const tag = __wdioTag(value);
if (tag !== '[object Blob]' && tag !== '[object File]') {
return false;
}
if (typeof value.arrayBuffer !== 'function' || typeof value.size !== 'number' || typeof value.type !== 'string') {
return false;
}
// A real Blob/File, including one from another realm, inherits its brand.
// An object that sets the brand itself is user data.
return !Object.prototype.hasOwnProperty.call(value, Symbol.toStringTag);
}
function __wdioIsFileList(value) {
if (__wdioTag(value) !== '[object FileList]' || typeof value.length !== 'number') {
return false;
}
return !Object.prototype.hasOwnProperty.call(value, Symbol.toStringTag);
}
function __wdioArrayBufferToBase64(buffer) {
if (typeof btoa !== 'function') {
throw new Error('Unable to encode Blob data: btoa is not available');
}
const bytes = new Uint8Array(buffer);
const chunkSize = ${BASE64_CHUNK_SIZE};
const parts = [];
for (let offset = 0; offset < bytes.length; offset += chunkSize) {
const chunk = bytes.subarray(offset, offset + chunkSize);
const binary = Array.from(chunk, (byte) => String.fromCharCode(byte)).join('');
parts.push(btoa(binary));
}
return parts.join('');
}
async function __wdioSerializeBlob(blob, seen) {
const base64 = __wdioArrayBufferToBase64(await blob.arrayBuffer());
const serialized = {
[__wdioSerializedBlobKey]: true,
data: base64,
type: __wdioHostValue(blob, 'type') || '',
size: __wdioHostValue(blob, 'size'),
kind: __wdioSerializedBlobKindBlob
};
if (__wdioTag(blob) === '[object File]') {
serialized.kind = __wdioSerializedBlobKindFile;
serialized.name = __wdioHostValue(blob, 'name');
serialized.lastModified = __wdioHostValue(blob, 'lastModified');
}
const propKeys = Object.keys(blob).filter((key) => (
key !== 'size' && key !== 'type' &&
(serialized.kind !== __wdioSerializedBlobKindFile || (key !== 'name' && key !== 'lastModified'))
));
if (propKeys.length) {
seen.add(blob);
try {
const props = {};
for (const key of propKeys) {
const next = await __wdioSerializeValue(blob[key], seen);
props[key] = next === __wdioCycle ? null : next;
}
serialized.props = props;
} finally {
seen.delete(blob);
}
}
return serialized;
}
function __wdioIsHostValue(value) {
if (typeof Node !== 'undefined' && value instanceof Node) {
return true;
}
if (typeof Window !== 'undefined' && value instanceof Window) {
return true;
}
if (value instanceof Date || value instanceof RegExp) {
return true;
}
if (typeof ArrayBuffer !== 'undefined' && (value instanceof ArrayBuffer || ArrayBuffer.isView(value))) {
return true;
}
if (typeof Error !== 'undefined' && value instanceof Error) {
return true;
}
if (typeof Promise !== 'undefined' && value instanceof Promise) {
return true;
}
return false;
}
async function __wdioSerializeEntries(entries, seen) {
let changed = false;
const copy = [];
for (const entry of entries) {
const next = await __wdioSerializeValue(entry, seen);
if (next === __wdioCycle) {
copy.push(null);
continue;
}
if (next !== entry) {
changed = true;
}
copy.push(next);
}
return { changed, copy };
}
async function __wdioSerializeValue(value, seen) {
if (value === null || typeof value !== 'object') {
return value;
}
if (seen.has(value)) {
return __wdioCycle;
}
if (__wdioIsBlob(value)) {
return __wdioSerializeBlob(value, seen);
}
if (__wdioIsHostValue(value)) {
return value;
}
seen.add(value);
try {
if (__wdioIsFileList(value)) {
const listed = [];
for (let i = 0; i < value.length; i++) {
listed.push(value[i]);
}
const { changed, copy } = await __wdioSerializeEntries(listed, seen);
const extras = {};
let hasExtras = false;
for (const key of Object.keys(value)) {
if (key === 'length' || __wdioIsIndexKey(key)) {
continue;
}
hasExtras = true;
const next = await __wdioSerializeValue(value[key], seen);
extras[key] = next === __wdioCycle ? null : next;
}
if (!changed && !hasExtras) {
return value;
}
const result = changed ? copy : listed.slice();
Object.assign(result, extras);
return result;
}
if (typeof Map !== 'undefined' && value instanceof Map) {
let changed = false;
const entries = [];
for (const [key, entry] of value) {
const nextKey = await __wdioSerializeValue(key, seen);
const nextValue = await __wdioSerializeValue(entry, seen);
if ((nextKey !== __wdioCycle && nextKey !== key) || (nextValue !== __wdioCycle && nextValue !== entry)) {
changed = true;
}
entries.push([
nextKey === __wdioCycle ? null : nextKey,
nextValue === __wdioCycle ? null : nextValue
]);
}
return changed ? new Map(entries) : value;
}
if (typeof Set !== 'undefined' && value instanceof Set) {
const { changed, copy } = await __wdioSerializeEntries(value, seen);
return changed ? new Set(copy) : value;
}
if (Array.isArray(value)) {
const { changed, copy } = await __wdioSerializeEntries(value, seen);
return changed ? copy : value;
}
let changed = false;
const copy = {};
for (const key of Object.keys(value)) {
const next = await __wdioSerializeValue(value[key], seen);
if (next === __wdioCycle) {
copy[key] = null;
continue;
}
if (next !== value[key]) {
changed = true;
}
copy[key] = next;
}
return changed ? copy : value;
} finally {
seen.delete(value);
}
}
`;
function createBidiFunctionDeclaration(script) {
const userScript = typeof script === "string" ? new Function(script) : script;
const declaration = new Function(
`return (${SCRIPT_PREFIX}${userScript.toString()}${SCRIPT_SUFFIX}).apply(this, arguments);`
).toString();
const markerIndex = declaration.indexOf(SCRIPT_PREFIX);
const returnIndex = declaration.lastIndexOf("return (", markerIndex);
const suffixIndex = declaration.indexOf(SCRIPT_SUFFIX);
const applyToken = ").apply(this, arguments);";
const applyIndex = declaration.indexOf(applyToken, suffixIndex);
if (returnIndex === -1 || applyIndex === -1) {
throw new Error("Unable to wrap BiDi execute script");
}
const resultPrefix = "const __wdioResult = (";
const withResult = declaration.slice(0, returnIndex) + resultPrefix + declaration.slice(returnIndex + "return (".length);
const applyEnd = applyIndex + (resultPrefix.length - "return (".length) + applyToken.length;
return withResult.slice(0, applyEnd) + `
${SERIALIZER_HELPER}
return Promise.resolve(__wdioResult).then((value) => __wdioSerializeValue(value, new WeakSet()));
` + withResult.slice(applyEnd);
}
function decodedByteLength(data) {
if (data.length === 0) {
return 0;
}
const padding = data.endsWith("==") ? 2 : data.endsWith("=") ? 1 : 0;
return data.length / 4 * 3 - padding;
}
function isSerializedBlobValue(value) {
if (typeof value !== "object" || value === null) {
return false;
}
const serialized = value;
if (serialized[SERIALIZED_BLOB_KEY] !== true) {
return false;
}
if (serialized.kind !== SERIALIZED_BLOB_KIND_BLOB && serialized.kind !== SERIALIZED_BLOB_KIND_FILE) {
return false;
}
const allowedKeys = serialized.kind === SERIALIZED_BLOB_KIND_FILE ? FILE_TRANSFER_KEYS : BLOB_TRANSFER_KEYS;
if (!Object.keys(serialized).every((key) => allowedKeys.has(key))) {
return false;
}
if (typeof serialized.data !== "string" || !BASE64_PATTERN.test(serialized.data)) {
return false;
}
if (typeof serialized.type !== "string") {
return false;
}
if (typeof serialized.size !== "number" || !Number.isInteger(serialized.size) || serialized.size < 0) {
return false;
}
if (decodedByteLength(serialized.data) !== serialized.size) {
return false;
}
if (serialized.name !== void 0 && typeof serialized.name !== "string") {
return false;
}
if (serialized.lastModified !== void 0 && typeof serialized.lastModified !== "number") {
return false;
}
if (serialized.props !== void 0) {
if (typeof serialized.props !== "object" || serialized.props === null || Array.isArray(serialized.props)) {
return false;
}
}
return true;
}
function createBlobFromSerializedValue(value) {
const bytes = decodeBase64(value.data);
let blob;
if (value.kind === SERIALIZED_BLOB_KIND_FILE && typeof File === "function") {
blob = new File([bytes], value.name ?? "file", {
type: value.type,
lastModified: typeof value.lastModified === "number" ? value.lastModified : Date.now()
});
} else if (typeof Blob === "function") {
blob = new Blob([bytes], { type: value.type });
} else {
blob = bytes;
}
if (value.props) {
const target = blob;
for (const [key, prop] of Object.entries(value.props)) {
try {
target[key] = prop;
} catch {
}
}
}
return blob;
}
// src/utils/bidi/value.ts
import { ELEMENT_KEY as ELEMENT_KEY4, SHADOW_ELEMENT_KEY } from "webdriver";
var TYPE_CONSTANT = "type";
var VALUE_CONSTANT = "value";
var LocalValue = class _LocalValue {
type;
value;
constructor(type, value) {
if (type === "undefined" /* Undefined */ || type === "null" /* Null */) {
this.type = type;
} else {
this.type = type;
this.value = value;
}
}
/**
* Creates a new LocalValue object with a string value.
*
* @param {string} value - The string value to be stored in the LocalValue object.
* @returns {LocalValue} - The created LocalValue object.
*/
static createStringValue(value) {
return new _LocalValue("string" /* String */, value);
}
/**
* Creates a new LocalValue object with a number value.
*
* @param {number} value - The number value.
* @returns {LocalValue} - The created LocalValue object.
*/
static createNumberValue(value) {
return new _LocalValue("number" /* Number */, value);
}
/**
* Creates a new LocalValue object with a special number value.
*
* @param {number} value - The value of the special number.
* @returns {LocalValue} - The created LocalValue object.
*/
static createSpecialNumberValue(value) {
if (Number.isNaN(value)) {
return new _LocalValue("number" /* SpecialNumber */, "NaN");
}
if (Object.is(value, -0)) {
return new _LocalValue("number" /* SpecialNumber */, "-0");
}
if (value === Infinity) {
return new _LocalValue("number" /* SpecialNumber */, "Infinity");
}
if (value === -Infinity) {
return new _LocalValue("number" /* SpecialNumber */, "-Infinity");
}
return new _LocalValue("number" /* SpecialNumber */, value);
}
/**
* Creates a new LocalValue object with an undefined value.
* @returns {LocalValue} - The created LocalValue object.
*/
static createUndefinedValue() {
return new _LocalValue("undefined" /* Undefined */);
}
/**
* Creates a new LocalValue object with a null value.
* @returns {LocalValue} - The created LocalValue object.
*/
static createNullValue() {
return new _LocalValue("null" /* Null */);
}
/**
* Creates a new LocalValue object with a boolean value.
*
* @param {boolean} value - The boolean value.
* @returns {LocalValue} - The created LocalValue object.
*/
static createBooleanValue(value) {
return new _LocalValue("boolean" /* Boolean */, value);
}
/**
* Creates a new LocalValue object with a BigInt value.
*
* @param {BigInt} value - The BigInt value.
* @returns {LocalValue} - The created LocalValue object.
*/
static createBigIntValue(value) {
return new _LocalValue("bigint" /* BigInt */, value);
}
/**
* Creates a new LocalValue object with an array.
*
* @param {Array} value - The array.
* @returns {LocalValue} - The created LocalValue object.
*/
static createArrayValue(value) {
return new _LocalValue("array" /* Array */, value);
}
/**
* Creates a new LocalValue object with date value.
*
* @param {string} value - The date.
* @returns {LocalValue} - The created LocalValue object.
*/
static createDateValue(value) {
return new _LocalValue("date" /* Date */, value);
}
/**
* Creates a new LocalValue object of map value.
* @param {Map} map - The map.
* @returns {LocalValue} - The created LocalValue object.
*/
static createMapValue(map) {
const value = [];
Object.entries(map).forEach((entry) => {
value.push(entry);
});
return new _LocalValue("map" /* Map */, value);
}
/**
* Creates a new LocalValue object from the passed object.
*
* @param {Object} map - The object.
* @returns {LocalValue} - The created LocalValue object.
*/
static createObjectValue(object) {
const value = [];
Object.entries(object).forEach(([key, val]) => {
value.push([key, _LocalValue.getArgument(val)]);
});
return new _LocalValue("object" /* Object */, value);
}
/**
* Creates a new LocalValue object of regular expression value.
*
* @param {string} value - The value of the regular expression.
* @returns {LocalValue} - The created LocalValue object.
*/
static createRegularExpressionValue(value) {
return new _LocalValue("regexp" /* RegularExpression */, value);
}
/**
* Creates a new LocalValue object with the specified value.
* @param {Set} value - The value to be set.
* @returns {LocalValue} - The created LocalValue object.
*/
static createSetValue(value) {
return new _LocalValue("set" /* Set */, value);
}
/**
* Creates a new LocalValue object with the given channel value
*
* @param {ChannelValue} value - The channel value.
* @returns {LocalValue} - The created LocalValue object.
*/
static createChannelValue(value) {
return new _LocalValue("channel" /* Channel */, value);
}
static createReferenceValue(handle, sharedId) {
return new ReferenceValue(handle, sharedId);
}
static getArgument(argument) {
const type = typeof argument;
switch (type) {
case "string" /* String */:
return _LocalValue.createStringValue(argument);
case "number" /* Number */:
if (Number.isNaN(argument) || Object.is(argument, -0) || !Number.isFinite(argument)) {
return _LocalValue.createSpecialNumberValue(argument);
}
return _LocalValue.createNumberValue(argument);
case "boolean" /* Boolean */:
return _LocalValue.createBooleanValue(argument);
case "bigint" /* BigInt */:
return _LocalValue.createBigIntValue(argument);
case "undefined" /* Undefined */:
return _LocalValue.createUndefinedValue();
case "object" /* Object */:
if (argument === null) {
return _LocalValue.createNullValue();
}
if (argument instanceof Date) {
return _LocalValue.createDateValue(argument);
}
if (argument instanceof Map) {
const map = [];
argument.forEach((value, key) => {
const objectKey = typeof key === "string" ? key : _LocalValue.getArgument(key);
const objectValue = _LocalValue.getArgument(value);
map.push([objectKey, objectValue]);
});
return new _LocalValue("map" /* Map */, map);
}
if (argument instanceof Set) {
const set = [];
argument.forEach((value) => {
set.push(_LocalValue.getArgument(value));
});
return _LocalValue.createSetValue(set);
}
if (argument instanceof Array) {
const arr = [];
argument.forEach((value) => {
arr.push(_LocalValue.getArgument(value));
});
return _LocalValue.createArrayValue(arr);
}
if (argument instanceof RegExp) {
return _LocalValue.createRegularExpressionValue({
pattern: argument.source,
flags: argument.flags
});
}
if (argument && ELEMENT_KEY4 in argument) {
return _LocalValue.createReferenceValue(
"sharedId" /* SharedId */,
argument[ELEMENT_KEY4]
);
}
if (argument && SHADOW_ELEMENT_KEY in argument) {
return _LocalValue.createReferenceValue(
"sharedId" /* SharedId */,
argument[SHADOW_ELEMENT_KEY]
);
}
return _LocalValue.createObjectValue(argument);
}
throw new Error(`Unsupported type: ${type}`);
}
asMap() {
return {
[TYPE_CONSTANT]: this.type,
...!(this.type === "null" /* Null */ || this.type === "undefined" /* Undefined */) ? { [VALUE_CONSTANT]: this.value } : {}
};
}
};
var ReferenceValue = class {
handle;
sharedId;
/**
* Constructs a new ReferenceValue object.
* @param {string} handle - The handle value.
* @param {string} sharedId - The shared ID value.
*/
constructor(handle, sharedId) {
if (handle === "handle" /* Handle */) {
this.handle = sharedId;
} else if (handle === "sharedId" /* SharedId */) {
this.sharedId = sharedId;
} else {
this.handle = handle;
this.sharedId = sharedId;
}
}
asMap() {
const toReturn = {};
if (typeof this.handle !== "undefined") {
toReturn["handle" /* Handle */] = this.handle;
}
if (typeof this.sharedId !== "undefined") {
toReturn["sharedId" /* SharedId */] = this.sharedId;
}
return toReturn;
}
};
// src/utils/bidi/index.ts
async function executeInContext(browser, context, script, ...args) {
const params = {
functionDeclaration: createBidiFunctionDeclaration(script),
awaitPromise: true,
arguments: args.map((arg) => LocalValue.getArgument(arg)),
target: { context }
};
const result = await browser.scriptCallFunction(params);
return parseScriptResult(params, result);
}
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 */ || type === "nodelist" /* NodeList */ || type === "htmlcollection" /* HTMLCollection */) {
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 */) {
const obj = Object.fromEntries((value || []).map(([key, value2]) => {
return [typeof key === "string" ? key : deserializeValue(key), deserializeValue(value2)];
}));
if (isSerializedBlobValue(obj)) {
return createBlobFromSerializedValue(obj);
}
return obj;
}
if (type === "node" /* Node */) {
return deserializeNode(result);
}
if (type === "error" /* Error */) {
return new Error("<unserializable error>");
}
return value;
}
var ELEMENT_NODE = 1;
var DOCUMENT_NODE = 9;
var DOCUMENT_FRAGMENT_NODE = 11;
function deserializeNode(node) {
const props = node.value;
if (!props || props.nodeType === ELEMENT_NODE || props.nodeType === DOCUMENT_NODE) {
return { [ELEMENT_KEY5]: node.sharedId };
}
if (props.nodeType === DOCUMENT_FRAGMENT_NODE && props.mode) {
return { [SHADOW_ELEMENT_KEY2]: node.sharedId };
}
return { ...props };
}
// src/utils/elementChecks.ts
var log4 = logger4("webdriverio");
function run(browser, contextId) {
return (script, ...args) => contextId && browser.isBidi ? executeInContext(browser, contextId, script, ...args) : browser.execute(script, ...args);
}
async function runBatchedElementCheck(elementIds, checkName, contextId, onElementCheckError, batchCheck, perElementCheck) {
if (elementIds.length === 0) {
return [];
}
const inContext = contextId ? ` in context ${contextId}` : "";
try {
const result = await batchCheck();
if (!Array.isArray(result) || result.length !== elementIds.length) {
throw new Error(`expected ${elementIds.length} result(s) but got ${Array.isArray(result) ? result.length : typeof result}`);
}
return result;
} catch (err) {
log4.warn(`Failed to batch-check element ${checkName}${inContext}: ${err.message}. Falling back to per-element checks.`);
}
return Promise.all(elementIds.map(async (elementId) => {
try {
return await perElementCheck(elementId);
} catch (err) {
log4.warn(`Failed to check element ${checkName} for element ${elementId}${inContext}: ${err.message}`);
onElementCheckError?.(elementId);
return false;
}
}));
}
async function checkElementsContainedIn(browser, scope, elementIds, contextId, onElementCheckError) {
return runBatchedElementCheck(
elementIds,
`containment for scope ${scope}`,
contextId,
onElementCheckError,
() => run(browser, contextId)(
elementsContainedIn,
{ [ELEMENT_KEY6]: scope },
elementIds.map((elementId) => ({ [ELEMENT_KEY6]: elementId }))
),
(elementId) => run(browser, contextId)(
elementContains,
{ [ELEMENT_KEY6]: scope },
{ [ELEMENT_KEY6]: elementId }
)
);
}
async function checkElementsConnected(browser, elementIds, contextId, onElementCheckError) {
return runBatchedElementCheck(
elementIds,
"connectivity",
contextId,
onElementCheckError,
() => run(browser, contextId)(
elementsConnected,
elementIds.map((elementId) => ({ [ELEMENT_KEY6]: elementId }))
),
(elementId) => run(browser, contextId)(
(el) => el.isConnected,
{ [ELEMENT_KEY6]: elementId }
)
);
}
// src/session/shadowRoot.ts
var log5 = logger5("webdriverio:ShadowRootManager");
function getShadowRootManager(browser) {
return SessionManager.getSessionManager(browser, ShadowRootManager);
}
var ShadowRootManager = class _ShadowRootManager extends SessionManager {
#browser;
#initialize;
#shadowRoots = /* @__PURE__ */ new Map();
#currentDocumentIds = /* @__PURE__ */ new Map();
#documentElements = /* @__PURE__ */ new Map();
#frameDepth = 0;
#handleLogEntryListener = this.handleLogEntry.bind(this);
#commandResultHandlerListener = this.#commandResultHandler.bind(this);
#handleNavigationCommittedListener = this.#handleNavigationCommitted.bind(this);
constructor(browser) {
super(browser, _ShadowRootManager.name);
this.#browser = browser;
if (!this.isEnabled()) {
this.#initialize = Promise.resolve(true);
return;
}
this.#initialize = this.#browser.sessionSubscribe({
events: ["log.entryAdded", "browsingContext.navigationCommitted"]
}).then(() => true, () => false);
this.#browser.on("log.entryAdded", this.#handleLogEntryListener);
this.#browser.on("result", this.#commandResultHandlerListener);
this.#browser.on("browsingContext.navigationCommitted", this.#handleNavigationCommittedListener);
this.#browser.scriptAddPreloadScript({
functionDeclaration: customElementWrapper.toString()
}).catch((err) => {
log5.warn(`Could not register shadow root preload script, shadow DOM will not be pierced in this session: ${err.message}`);
});
}
removeListeners() {
super.removeListeners();
this.#browser.off("log.entryAdded", this.#handleLogEntryListener);
this.#browser.off("result", this.#commandResultHandlerListener);
this.#browser.off("browsingContext.navigationCommitted", this.#handleNavigationCommittedListener);
}
async initialize() {
return this.#initialize;
}
#handleNavigationCommitted({ context }) {
this.#clearContext(context);
}
/**
* Forget a tracked node that turned out to be gone. The root of the tree
* or the document element stands for the whole document, which happens
* when a page is shown again without a `navigationCommitted` event (e.g.
* restored from Firefox's back-forward cache). Any other node only takes
* its own subtree along, so roots registered for the current document stay.
*/
forgetNode(context, node) {
const tree = this.#shadowRoots.get(context);
if (!tree || tree.element === node || tree.shadowRoot === node || this.#documentElements.get(context)?.sharedId === node) {
this.#clearContext(context);
return;
}
const owner = tree.flat().find((entry) => entry.element === node || entry.shadowRoot === node);
if (owner) {
tree.remove(owner.element);
}
}
#clearContext(context) {
this.#shadowRoots.delete(context);
this.#currentDocumentIds.delete(context);
this.#documentElements.delete(context);
}
/**
* keep track of frame depth
*/
#commandResultHandler(result) {
const noResultError = typeof result.result === "object" && result.result && "error" in result.result && !result.result.error;
if (result.command === "switchToFrame" && noResultError) {
this.#frameDepth++;
}
if (result.command === "switchToParentFrame" && noResultError) {
this.#frameDepth = Math.max(0, this.#frameDepth - 1);
}
}
/**
* check if we are within a frame
* @returns {boolean} true if we are within a frame
*/
isWithinFrame() {
return this.#frameDepth > 0;
}
/**
* capture shadow root elements propagated through console.debug
*/
handleLogEntry(logEntry) {
const args = "args" in logEntry && logEntry.level === "debug" ? logEntry.args : void 0;
if (!args || args[0].type !== "string" || args[0].value !== "[WDIO]" || args[1].type !== "string") {
return;
}
if (!logEntry.source.context) {
return;
}
const eventType = args[1].value;
if (eventType === "newShadowRoot" && args[2].type === "node" && args[3].type === "node") {
const [
/* [WDIO] */
,
/* newShadowRoot */
,
shadowElem,
rootElem,
isDocument,
documentElement
] = args;
const ctxId = logEntry.source.context;
if (shadowElem.sharedId) {
const docMatch = shadowElem.sharedId.match(/\.d\.([A-F0-9]+)\./);
if (docMatch) {
const newDocId = docMatch[1];
const currentDocId = this.#currentDocumentIds.get(ctxId);
if (currentDocId && currentDocId !== newDocId) {
log5.info(`Document changed in context ${ctxId}: ${currentDocId} -> ${newDocId}, purging ${this.#shadowRoots.get(ctxId)?.flat().length ?? 0} stale shadow roots`);
this.#shadowRoots.delete(ctxId);
this.#documentElements.delete(ctxId);
}
this.#currentDocumentIds.set(ctxId, newDocId);
}
}
if (!this.#shadowRoots.has(logEntry.source.context)) {
if (!rootElem.sharedId) {
throw new Error(`Expected "sharedId" parameter from object ${rootElem}`);
}
this.#shadowRoots.set(logEntry.source.context, new ShadowRootTree(rootElem.sharedId));
} else if (isDocument.type === "boolean" && isDocument.value) {
if (!rootElem.sharedId) {
throw new Error(`Expected "sharedId" parameter from object ${rootElem}`);
}
const tree2 = this.#shadowRoots.get(logEntry.source.context);
if (tree2?.element !== rootElem.sharedId) {
const documentTree = new ShadowRootTree(rootElem.sharedId);
if (tree2 && documentElement?.type === "node" && documentElement.sharedId && this.#documentElements.get(logEntry.source.context)?.sharedId === documentElement.sharedId) {
for (const host of tree2.shadowRoot ? [tree2] : tree2.children) {
documentTree.addShadowElement(host);
}
}
this.#shadowRoots.set(logEntry.source.context, documentTree);
}
}
this.#documentElements.set(logEntry.source.context, documentElement);
const tree = this.#shadowRoots.get(logEntry.source.context);
if (!tree) {
throw new Error(`Couldn't find tree for context id ${logEntry.source.context}`);
}
if (
// we expect an element id
!shadowElem.sharedId || // we expect the element to have a shadow root
!shadowElem.value?.shadowRoot?.sharedId || // we expect the shadow root to have a proper type
shadowElem.value.shadowRoot.value?.nodeType !== 11
) {
return log5.warn(`Expected element with shadow root but found <${shadowElem.value?.localName} />`);
}
log5.info(`Registered new shadow root for element <${shadowElem.value.localName} /> with id ${shadowElem.value.shadowRoot.sharedId}`);
const newTree = new ShadowRootTree(
shadowElem.sharedId,
shadowElem.value.shadowRoot.sharedId,
shadowElem.value.shadowRoot.value.mode
);
if (tree.element === newTree.element) {
tree.shadowRoot = newTree.shadowRoot;
tree.mode = newTree.mode;
return;
}
if (rootElem.sharedId) {
tree.addShadowElement(rootElem.sharedId, newTree);
} else {
tree.addShadowElement(newTree);
}
return;
}
if (eventType === "removeShadowRoot" && args[2].type === "node") {
if (!args[2].sharedId) {
return log5.warn("Received removeShadowRoot event without sharedId, element may have been garbage collected");
}
const tree = this.#shadowRoots.get(logEntry.source.context);
if (!tree) {
return;
}
return tree.remove(args[2].sharedId);
}
throw new Error(`Invalid parameters for "${eventType}" event: ${args.join(", ")}`);
}
async getShadowElementsByContextId(contextId, scope) {
let tree = this.#shadowRoots.get(contextId);
if (!tree) {
return [];
}
let documentElement;
if (scope) {
const subTree = tree.find(scope);
if (!subTree) {
const containedHosts = await this.#findContainedShadowHosts(contextId, scope);
const elements2 = containedHosts.flatMap((host) => host.getAllLookupScopes());
return [...new Set(elements2).values()];
}
tree = subTree;
} else {
documentElement = this.#documentElements.get(contextId)?.sharedId;
}
const elements = tree.getAllLookupScopes();
return [
...documentElement ? [documentElement] : [],
...new Set(elements).values()
];
}
async getShadowElementPairsByContextId(contextId, scope) {
let tree = this.#shadowRoots.get(contextId);
if (!tree) {
return [];
}
if (scope) {
const subTree = tree.find(scope);
if (!subTree) {
const containedHosts = await this.#findContainedShadowHosts(contextId, scope);
return containedHosts.flatMap((host) => host.flat().map((t) => [t.element, t.shadowRoot]));
}
tree = subTree;
}
return tree.flat().map((tree2) => [tree2.element, tree2.shadowRoot]);
}
/**
* Find all tracked shadow hosts that are real DOM descendants of `scope`, even though
* `scope` itself isn't tracked in the shadow tree (e.g. a plain wrapper element around
* a web component). Hosts nested inside another contained host are excluded since
* they're already covered by that host's own (recursive) lookup scopes.
*/
async #findContainedShadowHosts(contextId, scope) {
const tree = this.#shadowRoots.get(contextId);
if (!tree) {
return [];
}
const hosts = tree.flat().filter((t) => t.shadowRoot);
if (hosts.length === 0) {
return [];
}
const staleHosts = [];
const containmentResults = await checkElementsContainedIn(
this.#browser,
scope,
hosts.map((host) => host.element),
contextId,
(elementId) => staleHosts.push(elementId)
);
const containedHosts = hosts.filter((_, i) => containmentResults[i]);
if (staleHosts.length > 0 && staleHosts.length < hosts.length) {
const containedSet = new Set(containedHosts);
for (const elementId of staleHosts) {
const staleHost = hosts.find((host) => host.element === elementId);
if (staleHost && staleHost.flat().some((descendant) => containedSet.has(descendant))) {
continue;
}
log5.info(`Pruning stale shadow host ${elementId} from shadow tree of context ${contextId}`);
tree.remove(elementId);
}
}
const covered = /* @__PURE__ */ new Set();
const topLevelContained = [];
for (const host of containedHosts) {
if (covered.has(host)) {
continue;
}
topLevelContained.push(host);
for (const descendant of host.flat()) {
covered.add(descendant);
}
}
return topLevelContained;
}
getShadowRootModeById(contextId, element) {
const tree = this.#shadowRoots.get(contextId);
if (!tree) {
return;
}
const shadowTree = tree.find(element);
if (!shadowTree) {
return;
}
return shadowTree.mode;
}
deleteShadowRoot(element, contextId) {
const tree = this.#shadowRoots.get(contextId);
if (!tree) {
return;
}
return tree.remove(element);
}
};
var ShadowRootTree = class _ShadowRootTree {
element;
shadowRoot;
mode;
children = /* @__PURE__ */ new Set();
constructor(element, shadowRoot, mode) {
this.element = element;
this.shadowRoot = shadowRoot;
this.mode = mode;
}
addShadowElement(...args) {
const [scope, treeArg] = args;
if (!scope && !treeArg) {
throw new Error('Method "addShadowElement" expects at least 2 arguments');
}
if (scope instanceof _ShadowRootTree) {
for (const child of this.children) {
if (child.element === scope.element) {
return;
}
}
this.children.add(scope);
return;
}
if (typeof scope === "string" && treeArg instanceof _ShadowRootTree) {
const tree = this.find(scope) || this.findByShadowId(scope);
if (!tree) {
return;
}
tree.addShadowElement(treeArg);
return;
}
throw new Error('Invalid arguments for "addShadowElement" method');
}
find(element) {
if (this.element === element) {
return this;
}
for (const child of this.children) {
const elem = child.find(element);
if (elem) {
return elem;
}
}
return void 0;
}
findByShadowId(shadowRoot) {
if (this.shadowRoot === shadowRoot) {
return this;
}
for (const child of this.children) {
const elem = child.findByShadowId(shadowRoot);
if (elem) {
return elem;
}
}
return void 0;
}
getAllLookupScopes() {
return [
this.shadowRoot ?? this.element,
...Array.from(this.children).map((tree) => tree.getAllLookupScopes())
].flat();
}
flat() {
return [this, ...Array.from(this.children).map((tree) => tree.flat())].flat();
}
remove(element) {
const childArray = Array.from(this.children);
for (let i = childArray.length - 1; i >= 0; i--) {
if (childArray[i].element === element) {
return this.children.delete(childArray[i]);
}
const wasFound = childArray[i].remove(element);
if (wasFound) {
return true;
}
}
return false;
}
};
// src/utils/strictMode.ts
function isStrictQuery(scope, options) {
if (options && typeof options.strict === "boolean") {
return options.strict;
}
const { strictSelectors } = getBrowserObject7(scope).options || {};
return strictSelectors !== false;
}
function isCountableSelector(selector) {
if (typeof selector === "string" || typeof selector === "function") {
return true;
}
if (isElement(selector)) {
return false;
}
if (!isPlainObject2(selector)) {
return false;
}
if (typeof selector[ELEMENT_KEY7] === "string") {
return false;
}
return true;
}
async function findStrictElement(selector) {
const browserObject = getBrowserObject7(this);
const shadowRootManager = getShadowRootManager(browserObject);
const elems = this.isBidi && typeof selector === "string" && !selector.startsWith(DEEP_SELECTOR) && !shadowRootManager.isWithinFrame() ? await findDeepElements.call(this, selector) : await findElements.call(this, selector);
if (elems.length > 1) {
throw new StrictSelectorError(selector, elems.length);
}
return elems[0] || buildNotFoundError(selector);
}
// src/commands/browser/$.ts
async function $(selector, options) {
const strict = isCountableSelector(selector) && isStrictQuery(this, options);
if (globalThis.wdio && typeof selector === "string" && !selector.startsWith(DEEP_SELECTOR)) {
if (strict) {
const matches = "elementId" in this ? await globalThis.wdio.executeWithScope("$$", this.elementId, selector) : await globalThis.wdio.execute("$$", selector);
if (matches.length > 1) {
throw new StrictSelectorError(selector, matches.length);
}
return getElement.call(
this,
selector,
matches[0] || buildNotFoundError(selector),
{ strict }
);
}
const res2 = "elementId" in this ? await globalThis.wdio.executeWithScope("$", this.elementId, selector, options) : await globalThis.wdio.execute("$", selector, options);
return getElement.call(this, selector, res2, { strict });
}
if (typeof selector === "object") {
const elementRef = selector;
if (typeof elementRef[ELEMENT_KEY8] === "string") {
return getElement.call(this, void 0, elementRef);
}
}
const res = strict ? await findStrictElement.call(this, selector) : await findElement.call(this, selector);
return getElement.call(this, selector, res, { strict });
}
// src/utils/actions/base.ts
import { ELEMENT_KEY as ELEMENT_KEY9 } from "webdriver";
import { getBrowserObject as getBrowserObject8 } from "@wdio/utils";
var keyActionIds = 0;
var pointerActionIds = 0;
var wheelActionIds = 0;
var BaseAction = class {
constructor(instance, type, params) {
this.instance = instance;
this.#instance = instance;
if (params?.id) {
this.#id = params.id;
} else {
switch (type) {
case "key":
this.#id = `key${++keyActionIds}`;
break;
case "pointer":
this.#id = `pointer${++pointerActionIds}`;
break;
case "wheel":
this.#id = `wheel${++wheelActionIds}`;
break;
default:
this.#id = `action${type}`;
}
}
this.#type = type;
this.#parameters = params?.parameters || {};
}
instance;
#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) {
let originContext;
for (const seq of this.sequence) {
if (!seq.origin || typeof seq.origin === "string") {
continue;
}
const origin = seq.origin;
if (typeof seq.origin.then === "function" || typeof origin.waitForExist === "function" && !origin.elementId) {
await origin.waitForExist();
seq.origin = await seq.origin;
}
const foreign = await foreignContextId(seq.origin);
if (foreign) {
originContext = foreign;
}
if (!seq.origin[ELEMENT_KEY9]) {
throw new Error(`Couldn't find element for "${seq.type}" action sequence`);
}
seq.origin = { [ELEMENT_KEY9]: seq.origin[ELEMENT_KEY9] };
}
const browser = getBrowserObject8(this.#instance);
const sequence = [this.toJSON()];
const contextId = isBrowsingContext(this.#instance) ? this.#instance.contextId : originContext;
if (contextId) {
const actions2 = sequence.map((source) => ({
...source,
actions: source.actions.map((action2) => {
const instant = action2.type === "pointerMove" && action2.duration === 100 ? { ...action2, duration: 0 } : action2;
const origin = instant.origin;
if (!origin || typeof origin !== "object" || !(ELEMENT_KEY9 in origin)) {
return instant;
}
return {
...instant,
origin: {
type: "element",
element: { sharedId: origin[ELEMENT_KEY9] }
}
};
})
}));
await browser.inputPerformActions({
context: contextId,
actions: actions2
});
if (!skipRelease) {
await browser.inputReleaseActions({ context: contextId });
}
return;
}
await browser.performActions(sequence);
if (!skipRelease) {
await browser.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) && 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
import { ELEMENT_KEY as ELEMENT_KEY10 } from "webdriver";
async function actions(actions2) {
const payload = actions2.map((action2) => action2.toJSON());
if (isBrowsingContext(this)) {
const bidiPayload = payload.map((source) => ({
...source,
actions: source.actions.map((action2) => {
const origin = action2.origin;
if (!origin || typeof origin !== "object" || !(ELEMENT_KEY10 in origin)) {
return action2;
}
return {
...action2,
origin: {
type: "element",
element: { sharedId: origin[ELEMENT_KEY10] }
}
};
})
}));
await this.browser.inputPerformActions({
context: this.contextId,
actions: bidiPayload
});
await this.browser.inputReleaseActions({ context: this.contextId });
return;
}
await this.performActions(payload);
await this.releaseActions();
}
// src/commands/browser/addInitScript.ts
import { getBrowserObject as getBrowserObject9 } from "@wdio/utils";
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 (isBrowsingContext(this)) {
assertTopLevel(this, "addInitScript");
}
const browser = getBrowserObject9(this);
if (!browser.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 = isBrowsingContext(this) ? this.contextId : await browser.getWindowHandle();
const src = "return " + script.toString();
const fn = `(emit) => {
const closure = new Function(${JSON.stringify(src)})
return closure()(${serializedParameters.length ? `${serializedParameters.join(", ")}, emit` : "emit"})
}`;
const channel = btoa(fn.toString());
const result = await browser.scriptAddPreloadScript({
functionDeclaration: fn,
arguments: [{
type: "channel",
value: { channel }
}],
contexts: [context]
});
await browser.sessionSubscribe({
events: ["script.message"]
});
const eventHandler = /* @__PURE__ */ new Map();
const messageHandler = (msg) => {
if (msg.channel === channel) {
const handler = eventHandler.get("data") || [];
return handler.forEach((fn2) => fn2(deserialize(msg.data)));
}
};
browser.on("script.message", messageHandler);
const resetFn = (() => {
eventHandler.clear();
browser.off("script.message", messageHandler);
return browser.scriptRemovePreloadScript({ script: result.script });
});
const returnVal = {
remove: resetFn,
on: (event, listener) => {
if (!eventHandler.has(event)) {
eventHandler.set(event, []);
}
eventHandler.get(event)?.push(listener);
}
};
return returnVal;
}
// src/commands/browser/back.ts
import { runClassicProtocolCommand } from "webdriver";
// src/utils/traverseHistory.ts
var DEFAULT_PAGE_LOAD_TIMEOUT = 3e5;
var READY_STATE_RETRY_MS = 50;
var HISTORY_MARKER = "__wdioHistoryTraverse";
var DOCUMENT_TOKENS = "__wdioHistoryTokens";
function historyMarkerId() {
return `t${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;
}
function installHistoryMarker(id) {
const key = JSON.stringify(id);
return `(() => {
const nav = globalThis.navigation
if (!nav || typeof nav.addEventListener !== 'function') {
return 'unsupported'
}
const events = []
const onNavigate = (event) => {
events.push(event.navigationType)
}
const root = globalThis.${HISTORY_MARKER} || (globalThis.${HISTORY_MARKER} = { entries: {} })
root.entries[${key}] = { events, onNavigate }
nav.addEventListener('navigate', onNavigate)
return 'installed'
})()`;
}
function readHistoryMarker(id) {
const key = JSON.stringify(id);
return `(() => {
const root = globalThis.${HISTORY_MARKER}
const entry = root && root.entries && root.entries[${key}]
if (!entry || !Array.isArray(entry.events) || entry.events.length === 0) {
return ''
}
return String(entry.events[entry.events.length - 1])
})()`;
}
function clearTraversalMarks(id) {
const key = JSON.stringify(id);
return `(() => {
const docRoot = document.documentElement
const tokens = docRoot && docRoot.${DOCUMENT_TOKENS}
if (tokens) {
delete tokens[${key}]
}
const root = globalThis.${HISTORY_MARKER}
const entry = root && root.entries && root.entries[${key}]
if (!entry) {
return
}
const nav = globalThis.navigation
if (nav && typeof nav.removeEventListener === 'function') {
nav.removeEventListener('navigate', entry.onNavigate)
}
delete root.entries[${key}]
})()`;
}
var HISTORY_NAVIGATION_EVENTS = [
"browsingContext.navigationStarted",
"browsingContext.navigationCommitted",
"browsingContext.domContentLoaded",
"browsingContext.load",
"browsingContext.fragmentNavigated",
"browsingContext.historyUpdated"
];
function historyReadiness(strategy) {
if (strategy === "none") {
return "none";
}
if (strategy === "eager") {
return "interactive";
}
return "complete";
}
async function topLevelBrowsingContext2(browser) {
const handle = getContextManager(browser).getCurrentWindowHandle();
if (handle) {
return handle;
}
return browser.getWindowHandle();
}
async function pageLoadTimeout(browser) {
const fromSession = await browser.getTimeouts().then(
(timeouts) => timeouts?.pageLoad,
() => void 0
);
if (typeof fromSession === "number" && fromSession >= 0) {
return fromSession;
}
const fromCapabilities = browser.capabilities.timeouts?.pageLoad;
if (typeof fromCapabilities === "number" && fromCapabilities >= 0) {
return fromCapabilities;
}
return DEFAULT_PAGE_LOAD_TIMEOUT;
}
function matchesContext(params, context) {
return params?.context === context;
}
async function evaluateString(browser, context, expression) {
try {
const result = await browser.scriptEvaluate({
expression,
awaitPromise: false,
target: { context }
});
if (result.type !== "success") {
return void 0;
}
const value = result.result;
if (value && typeof value === "object" && "type" in value && value.type === "string" && "value" in value && typeof value.value === "string") {
return value.value;
}
return void 0;
} catch {
return void 0;
}
}
function delay(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
function readyStateSatisfies(readiness, state) {
if (readiness === "interactive") {
return state === "interactive" || state === "complete";
}
return state === "complete";
}
function documentSnapshot(token, stamp) {
const key = JSON.stringify(token);
return `(() => {
const root = document.documentElement
${stamp ? `if (root) (root.${DOCUMENT_TOKENS} || (root.${DOCUMENT_TOKENS} = {}))[${key}] = true` : ""}
const tokens = root && root.${DOCUMENT_TOKENS}
const marked = Boolean(tokens && tokens[${key}] === true)
return JSON.stringify({ href: location.href, readyState: document.readyState, marked })
})()`;
}
function parseDocumentSnapshot(value) {
if (!value) {
return void 0;
}
try {
const parsed = JSON.parse(value);
if (typeof parsed.href !== "string" || typeof parsed.readyState !== "string" || typeof parsed.marked !== "boolean") {
return void 0;
}
return { href: parsed.href, readyState: parsed.readyState, marked: parsed.marked };
} catch {
return void 0;
}
}
async function traverseTopLevelHistory(browser, delta, targetContext) {
const context = targetContext ?? await topLevelBrowsingContext2(browser);
const readiness = historyReadiness(browser.capabilities.pageLoadStrategy);
if (readiness === "none") {
await browser.browsingContextTraverseHistory({ context, delta });
return;
}
const timeout = await pageLoadTimeout(browser);
const expectedEvent = readiness === "interactive" ? "browsingContext.domContentLoaded" : "browsingContext.load";
let subscription;
let historyMarker = "unavailable";
const markerId = historyMarkerId();
let outgoing;
let armed = false;
let settled = false;
let crossDocument = false;
let waitError;
let resolveReady = () => {
};
let rejectReady = () => {
};
const ready = new Promise((resolve, reject) => {
resolveReady = () => {
if (settled) {
return;
}
settled = true;
resolve();
};
rejectReady = (error) => {
if (settled) {
return;
}
settled = true;
waitError = error;
reject(error);
};
});
const readySettled = ready.then(() => void 0, () => void 0);
const succeed = (params) => {
if (!armed || !matchesContext(params, context)) {
return;
}
resolveReady();
};
const onNavigationStarted = (params) => {
if (!armed || !matchesContext(params, context)) {
return;
}
crossDocument = true;
void watchTraversalReadyState(true);
};
const watchTraversalReadyState = async (requireNewDocument) => {
try {
while (armed && !settled) {
const sample = parseDocumentSnapshot(await evaluateString(browser, context, documentSnapshot(markerId, false)));
if (!armed || settled) {
return;
}
if (!sample) {
await delay(READY_STATE_RETRY_MS);
continue;
}
if (requireNewDocument && (!outgoing || sample.marked)) {
await delay(READY_STATE_RETRY_MS);
continue;
}
if (readyStateSatisfies(readiness, sample.readyState)) {
resolveReady();
}
return;
}
} catch {
}
};
const onNavigationCommitted = (params) => {
if (!armed || !matchesContext(params, context)) {
return;
}
void watchTraversalReadyState(false);
};
const onReadyEvent = (params) => {
if (!armed || !matchesContext(params, context)) {
return;
}
if (!outgoing) {
succeed(params);
return;
}
void evaluateString(browser, context, documentSnapshot(markerId, false)).then((snapshot) => parseDocumentSnapshot(snapshot)).catch(() => void 0).then((sample) => {
if (!sample) {
void watchTraversalReadyState(true);
return;
}
if (!sample.marked) {
succeed(params);
}
});
};
const onDomContentLoaded = (params) => {
if (readiness === "interactive") {
onReadyEvent(params);
}
};
const onLoad = (params) => {
onReadyEvent(params);
};
const onFragmentNavigated = (params) => {
succeed(params);
};
const onHistoryUpdated = (params) => {
if (crossDocument || !armed || !matchesContext(params, context)) {
return;
}
if (historyMarker !== "installed") {
succeed(params);
return;
}
void evaluateString(browser, context, readHistoryMarker(markerId)).then((kind) => {
if (!armed || settled || crossDocument || kind !== "traverse") {
return;
}
resolveReady();
});
};
browser.on("browsingContext.navigationStarted", onNavigationStarted);
browser.on("browsingContext.navigationCommitted", onNavigationCommitted);
browser.on("browsingContext.domContentLoaded", onDomContentLoaded);
browser.on("browsingContext.load", onLoad);
browser.on("browsingContext.fragmentNavigated", onFragmentNavigated);
browser.on("browsingContext.historyUpdated", onHistoryUpdated);
const timer = setTimeout(() => {
rejectReady(new Error(
`History traversal timed out after ${timeout}ms waiting for ${expectedEvent}`
));
}, timeout);
let commandError;
try {
const subscribed = await browser.sessionSubscribe({
events: [...HISTORY_NAVIGATION_EVENTS],
contexts: [context]
});
subscription = subscribed.subscription;
if (await evaluateString(browser, context, installHistoryMarker(markerId)) === "installed") {
historyMarker = "installed";
}
for (let attempt = 0; attempt < 5 && !outgoing && !settled; attempt++) {
const stamped = parseDocumentSnapshot(await evaluateString(browser, context, documentSnapshot(markerId, true)));
if (stamped?.marked) {
outgoing = stamped;
break;
}
if (attempt < 4 && !settled) {
await delay(READY_STATE_RETRY_MS);
}
}
armed = true;
try {
await browser.browsingContextTraverseHistory({ context, delta });
} catch (err) {
commandError = err;
}
if (commandError) {
throw commandError;
}
if (waitError) {
throw waitError;
}
if (!settled) {
await readySettled;
}
if (waitError) {
throw waitError;
}
} finally {
armed = false;
clearTimeout(timer);
browser.off("browsingContext.navigationStarted", onNavigationStarted);
browser.off("browsingContext.navigationCommitted", onNavigationCommitted);
browser.off("browsingContext.domContentLoaded", onDomContentLoaded);
browser.off("browsingContext.load", onLoad);
browser.off("browsingContext.fragmentNavigated", onFragmentNavigated);
browser.off("browsingContext.historyUpdated", onHistoryUpdated);
if (!settled) {
resolveReady();
}
void evaluateString(browser, context, clearTraversalMarks(markerId));
if (subscription) {
void browser.sessionUnsubscribe({ subscriptions: [subscription] }).catch(() => void 0);
}
}
}
// src/commands/browser/back.ts
async function back() {
if (!this.isBidi) {
await runClassicProtocolCommand.call(this, "back");
return;
}
await traverseTopLevelHistory(this, -1);
}
// 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_KEY11 } from "webdriver";
function custom$$(strategyName, ...strategyArguments) {
return ElementArray.fromAsyncCallback(async () => {
const strategy = this.strategies.get(strategyName);
if (!strategy) {
throw new 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_KEY11] === "string");
return res.length ? await getElements.call(this, strategyRef, res) : [];
}, {
selector: strategyName,
foundWith: "custom$$",
parent: this,
props: strategyArguments
});
}
// src/commands/browser/custom$.ts
import { ELEMENT_KEY as ELEMENT_KEY12 } 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_KEY12] === "string") {
return await getElement.call(this, strategyRef, res, { strict: false });
}
return await getElement.call(this, strategyRef, new Error("no such element"), { strict: false });
}
// 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;
const process3 = globalThis.process;
if (!environment.value.variables.WDIO_WORKER_ID || typeof process3.send !== "function") {
console.log(WDIORepl.introMessage);
const context = {
browser: this,
driver: this,
$: this.$.bind(this),
$$: this.$$.bind(this)
};
return repl.start(context);
}
process3._debugProcess(process3.pid);
process3.send({
origin: "debugger",
name: "start",
params: { commandTimeout, introMessage }
});
let commandResolve = (
/* istanbul ignore next */
() => {
}
);
process3.on("message", (m) => {
if (m.origin !== "debugger") {
return;
}
if (m.name === "stop") {
process3._debugEnd(process3.pid);
return commandResolve();
}
if (m.name === "eval") {
repl.eval(m.content.cmd, global, void 0, (err, result) => {
if (typeof process3.send !== "function") {
return;
}
if (err) {
process3.send({
origin: "debugger",
name: "result",
params: {
error: true,
...serializeError(err)
}
});
}
if (typeof result === "function") {
result = `[Function: ${result.name}]`;
}
process3.send({
origin: "debugger",
name: "result",
params: { result }
});
});
}
});
return new Promise((resolve) => commandResolve = resolve);
}
// src/commands/browser/deleteCookies.ts
import logger6 from "@wdio/logger";
var log6 = logger6("webdriverio");
async function deleteCookies(filter) {
const filterArray = typeof filter === "undefined" ? void 0 : Array.isArray(filter) ? filter : [filter];
if (!this.isBidi) {
await deleteCookiesClassic.call(this, getNamesForClassic(filterArray));
return;
}
if (isBrowsingContext(this)) {
const partition2 = {
type: "context",
context: this.contextId
};
if (!filterArray) {
await this.browser.storageDeleteCookies({ partition: partition2 });
return;
}
const bidiFilter = filterArray.map((entry) => {
if (typeof entry === "string") {
return { name: entry };
}
if (typeof entry === "object") {
return entry;
}
throw new Error(`Invalid value for cookie filter, expected 'string' or 'remote.StorageCookieFilter' but found "${typeof entry}"`);
});
await Promise.all(bidiFilter.map((entry) => this.browser.storageDeleteCookies({ filter: entry, partition: partition2 })));
return;
}
let url2;
try {
url2 = new URL(await this.getUrl());
if (url2.origin === "null") {
await deleteCookiesClassic.call(this, getNamesForClassic(filterArray));
return;
}
} catch {
await deleteCookiesClassic.call(this, getNamesForClassic(filterArray));
return;
}
const partition = {
type: "storageKey",
sourceOrigin: url2.origin
};
try {
const { cookies } = await this.storageGetCookies({ partition });
if (cookies.length === 0 && !this.isMobile) {
await deleteCookiesClassic.call(this, getNamesForClassic(filterArray));
return;
}
} catch (err) {
log6.warn(`BiDi deleteCookies check failed, falling back to classic: ${err.message}`);
await deleteCookiesClassic.call(this, getNamesForClassic(filterArray));
return;
}
try {
if (!filterArray) {
await this.storageDeleteCookies({ partition });
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, partition })));
} catch (err) {
log6.warn(`BiDi deleteCookies failed, falling back to classic: ${err.message}`);
await deleteCookiesClassic.call(this, getNamesForClassic(filterArray));
}
return;
}
function getNamesForClassic(filterArray) {
return 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}"`);
});
}
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
async function downloadFile2(fileName, targetDirectory) {
return environment.value.downloadFile.call(this, fileName, targetDirectory);
}
// src/clock.ts
import logger7 from "@wdio/logger";
var log7 = logger7("webdriverio:ClockManager");
function installFakeTimers(options) {
window.__clock = window.__wdio_sinon.install(options);
}
function uninstallFakeTimers() {
window.__clock.uninstall();
}
var fakerScript = 'function FakeTimers () {\n/*\n * Copyright (c) 2010-2014, Christian Johansen, christian@cjohansen.no. All rights reserved.\n * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\n * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\n * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.\n * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n/**\n * @typedef {object} IdleDeadline\n * @property {boolean} didTimeout - whether or not the callback was called before reaching the optional timeout\n * @property {function():number} timeRemaining - a floating-point value providing an estimate of the number of milliseconds remaining in the current idle period\n */\n\n/**\n * Queues a function to be called during a browser\'s idle periods\n *\n * @callback RequestIdleCallback\n * @param {function(IdleDeadline)} callback\n * @param {{timeout: number}} options - an options object\n * @returns {number} the id\n */\n\n/**\n * @callback NextTick\n * @param {VoidVarArgsFunc} callback - the callback to run\n * @param {...*} args - optional arguments to call the callback with\n * @returns {void}\n */\n\n/**\n * @callback SetImmediate\n * @param {VoidVarArgsFunc} callback - the callback to run\n * @param {...*} args - optional arguments to call the callback with\n * @returns {NodeImmediate}\n */\n\n/**\n * @callback VoidVarArgsFunc\n * @param {...*} callback - the callback to run\n * @returns {void}\n */\n\n/**\n * @typedef RequestAnimationFrame\n * @property {function(number):void} requestAnimationFrame\n * @returns {number} - the id\n */\n\n/**\n * @typedef Performance\n * @property {function(): number} now\n */\n\n/* eslint-disable jsdoc/require-property-description */\n/**\n * @typedef {object} Clock\n * @property {number} now - the current time\n * @property {Date} Date - the Date constructor\n * @property {number} loopLimit - the maximum number of timers before assuming an infinite loop\n * @property {RequestIdleCallback} requestIdleCallback\n * @property {function(number):void} cancelIdleCallback\n * @property {setTimeout} setTimeout\n * @property {clearTimeout} clearTimeout\n * @property {NextTick} nextTick\n * @property {queueMicrotask} queueMicrotask\n * @property {setInterval} setInterval\n * @property {clearInterval} clearInterval\n * @property {SetImmediate} setImmediate\n * @property {function(NodeImmediate):void} clearImmediate\n * @property {function():number} countTimers\n * @property {RequestAnimationFrame} requestAnimationFrame\n * @property {function(number):void} cancelAnimationFrame\n * @property {function():void} runMicrotasks\n * @property {function(string | number): number} tick\n * @property {function(string | number): Promise<number>} tickAsync\n * @property {function(): number} next\n * @property {function(): Promise<number>} nextAsync\n * @property {function(): number} runAll\n * @property {function(): number} runToFrame\n * @property {function(): Promise<number>} runAllAsync\n * @property {function(): number} runToLast\n * @property {function(): Promise<number>} runToLastAsync\n * @property {function(): void} reset\n * @property {function(number | Date): void} setSystemTime\n * @property {function(number): void} jump\n * @property {Performance} performance\n * @property {function(number[]): number[]} hrtime - process.hrtime (legacy)\n * @property {function(): void} uninstall Uninstall the clock.\n * @property {Function[]} methods - the methods that are faked\n * @property {boolean} [shouldClearNativeTimers] inherited from config\n * @property {{methodName:string, original:any}[] | undefined} timersModuleMethods\n */\n/* eslint-enable jsdoc/require-property-description */\n\n/**\n * Configuration object for the `install` method.\n *\n * @typedef {object} Config\n * @property {number|Date} [now] a number (in milliseconds) or a Date object (default epoch)\n * @property {string[]} [toFake] names of the methods that should be faked.\n * @property {number} [loopLimit] the maximum number of timers that will be run when calling runAll()\n * @property {boolean} [shouldAdvanceTime] tells FakeTimers to increment mocked time automatically (default false)\n * @property {number} [advanceTimeDelta] increment mocked time every <<advanceTimeDelta>> ms (default: 20ms)\n * @property {boolean} [shouldClearNativeTimers] forwards clear timer calls to native functions if they are not fakes (default: false)\n */\n\n/* eslint-disable jsdoc/require-property-description */\n/**\n * The internal structure to describe a scheduled fake timer\n *\n * @typedef {object} Timer\n * @property {Function} func\n * @property {*[]} args\n * @property {number} delay\n * @property {number} callAt\n * @property {number} createdAt\n * @property {boolean} immediate\n * @property {number} id\n * @property {Error} [error]\n */\n\n/**\n * A Node timer\n *\n * @typedef {object} NodeImmediate\n * @property {function(): boolean} hasRef\n * @property {function(): NodeImmediate} ref\n * @property {function(): NodeImmediate} unref\n */\n/* eslint-enable jsdoc/require-property-description */\n\n/**\n * Mocks available features in the specified global namespace.\n *\n * @param {*} _global Namespace to mock (e.g. `window`)\n * @returns {FakeTimers}\n */\nfunction withGlobal(_global) {\n const maxTimeout = Math.pow(2, 31) - 1; //see https://heycam.github.io/webidl/#abstract-opdef-converttoint\n const idCounterStart = 1e12; // arbitrarily large number to avoid collisions with native timer IDs\n const NOOP = function () {\n return undefined;\n };\n const NOOP_ARRAY = function () {\n return [];\n };\n const timeoutResult = _global.setTimeout(NOOP, 0);\n const addTimerReturnsObject = typeof timeoutResult === "object";\n const hrtimePresent =\n _global.process && typeof _global.process.hrtime === "function";\n const hrtimeBigintPresent =\n hrtimePresent && typeof _global.process.hrtime.bigint === "function";\n const nextTickPresent =\n _global.process && typeof _global.process.nextTick === "function";\n const utilPromisify = _global.process && require("util").promisify;\n const performancePresent =\n _global.performance && typeof _global.performance.now === "function";\n const hasPerformancePrototype =\n _global.Performance &&\n (typeof _global.Performance).match(/^(function|object)$/);\n const hasPerformanceConstructorPrototype =\n _global.performance &&\n _global.performance.constructor &&\n _global.performance.constructor.prototype;\n const queueMicrotaskPresent = _global.hasOwnProperty("queueMicrotask");\n const requestAnimationFramePresent =\n _global.requestAnimationFrame &&\n typeof _global.requestAnimationFrame === "function";\n const cancelAnimationFramePresent =\n _global.cancelAnimationFrame &&\n typeof _global.cancelAnimationFrame === "function";\n const requestIdleCallbackPresent =\n _global.requestIdleCallback &&\n typeof _global.requestIdleCallback === "function";\n const cancelIdleCallbackPresent =\n _global.cancelIdleCallback &&\n typeof _global.cancelIdleCallback === "function";\n const setImmediatePresent =\n _global.setImmediate && typeof _global.setImmediate === "function";\n const intlPresent = _global.Intl && typeof _global.Intl === "object";\n\n _global.clearTimeout(timeoutResult);\n\n const NativeDate = _global.Date;\n const NativeIntl = _global.Intl;\n let uniqueTimerId = idCounterStart;\n\n /**\n * @param {number} num\n * @returns {boolean}\n */\n function isNumberFinite(num) {\n if (Number.isFinite) {\n return Number.isFinite(num);\n }\n\n return isFinite(num);\n }\n\n let isNearInfiniteLimit = false;\n\n /**\n * @param {Clock} clock\n * @param {number} i\n */\n function checkIsNearInfiniteLimit(clock, i) {\n if (clock.loopLimit && i === clock.loopLimit - 1) {\n isNearInfiniteLimit = true;\n }\n }\n\n /**\n *\n */\n function resetIsNearInfiniteLimit() {\n isNearInfiniteLimit = false;\n }\n\n /**\n * Parse strings like "01:10:00" (meaning 1 hour, 10 minutes, 0 seconds) into\n * number of milliseconds. This is used to support human-readable strings passed\n * to clock.tick()\n *\n * @param {string} str\n * @returns {number}\n */\n function parseTime(str) {\n if (!str) {\n return 0;\n }\n\n const strings = str.split(":");\n const l = strings.length;\n let i = l;\n let ms = 0;\n let parsed;\n\n if (l > 3 || !/^(\\d\\d:){0,2}\\d\\d?$/.test(str)) {\n throw new Error(\n "tick only understands numbers, \'m:s\' and \'h:m:s\'. Each part must be two digits",\n );\n }\n\n while (i--) {\n parsed = parseInt(strings[i], 10);\n\n if (parsed >= 60) {\n throw new Error(`Invalid time ${str}`);\n }\n\n ms += parsed * Math.pow(60, l - i - 1);\n }\n\n return ms * 1000;\n }\n\n /**\n * Get the decimal part of the millisecond value as nanoseconds\n *\n * @param {number} msFloat the number of milliseconds\n * @returns {number} an integer number of nanoseconds in the range [0,1e6)\n *\n * Example: nanoRemainer(123.456789) -> 456789\n */\n function nanoRemainder(msFloat) {\n const modulo = 1e6;\n const remainder = (msFloat * 1e6) % modulo;\n const positiveRemainder =\n remainder < 0 ? remainder + modulo : remainder;\n\n return Math.floor(positiveRemainder);\n }\n\n /**\n * Used to grok the `now` parameter to createClock.\n *\n * @param {Date|number} epoch the system time\n * @returns {number}\n */\n function getEpoch(epoch) {\n if (!epoch) {\n return 0;\n }\n if (typeof epoch.getTime === "function") {\n return epoch.getTime();\n }\n if (typeof epoch === "number") {\n return epoch;\n }\n throw new TypeError("now should be milliseconds since UNIX epoch");\n }\n\n /**\n * @param {number} from\n * @param {number} to\n * @param {Timer} timer\n * @returns {boolean}\n */\n function inRange(from, to, timer) {\n return timer && timer.callAt >= from && timer.callAt <= to;\n }\n\n /**\n * @param {Clock} clock\n * @param {Timer} job\n */\n function getInfiniteLoopError(clock, job) {\n const infiniteLoopError = new Error(\n `Aborting after running ${clock.loopLimit} timers, assuming an infinite loop!`,\n );\n\n if (!job.error) {\n return infiniteLoopError;\n }\n\n // pattern never matched in Node\n const computedTargetPattern = /target\\.*[<|(|[].*?[>|\\]|)]\\s*/;\n let clockMethodPattern = new RegExp(\n String(Object.keys(clock).join("|")),\n );\n\n if (addTimerReturnsObject) {\n // node.js environment\n clockMethodPattern = new RegExp(\n `\\\\s+at (Object\\\\.)?(?:${Object.keys(clock).join("|")})\\\\s+`,\n );\n }\n\n let matchedLineIndex = -1;\n job.error.stack.split("\\n").some(function (line, i) {\n // If we\'ve matched a computed target line (e.g. setTimeout) then we\n // don\'t need to look any further. Return true to stop iterating.\n const matchedComputedTarget = line.match(computedTargetPattern);\n /* istanbul ignore if */\n if (matchedComputedTarget) {\n matchedLineIndex = i;\n return true;\n }\n\n // If we\'ve matched a clock method line, then there may still be\n // others further down the trace. Return false to keep iterating.\n const matchedClockMethod = line.match(clockMethodPattern);\n if (matchedClockMethod) {\n matchedLineIndex = i;\n return false;\n }\n\n // If we haven\'t matched anything on this line, but we matched\n // previously and set the matched line index, then we can stop.\n // If we haven\'t matched previously, then we should keep iterating.\n return matchedLineIndex >= 0;\n });\n\n const stack = `${infiniteLoopError}\\n${job.type || "Microtask"} - ${\n job.func.name || "anonymous"\n }\\n${job.error.stack\n .split("\\n")\n .slice(matchedLineIndex + 1)\n .join("\\n")}`;\n\n try {\n Object.defineProperty(infiniteLoopError, "stack", {\n value: stack,\n });\n } catch (e) {\n // noop\n }\n\n return infiniteLoopError;\n }\n\n /**\n * @param {Date} target\n * @param {Date} source\n * @returns {Date} the target after modifications\n */\n function mirrorDateProperties(target, source) {\n let prop;\n for (prop in source) {\n if (source.hasOwnProperty(prop)) {\n target[prop] = source[prop];\n }\n }\n\n // set special now implementation\n if (source.now) {\n target.now = function now() {\n return target.clock.now;\n };\n } else {\n delete target.now;\n }\n\n // set special toSource implementation\n if (source.toSource) {\n target.toSource = function toSource() {\n return source.toSource();\n };\n } else {\n delete target.toSource;\n }\n\n // set special toString implementation\n target.toString = function toString() {\n return source.toString();\n };\n\n target.prototype = source.prototype;\n target.parse = source.parse;\n target.UTC = source.UTC;\n target.prototype.toUTCString = source.prototype.toUTCString;\n target.isFake = true;\n\n return target;\n }\n\n //eslint-disable-next-line jsdoc/require-jsdoc\n function createDate() {\n /**\n * @param {number} year\n * @param {number} month\n * @param {number} date\n * @param {number} hour\n * @param {number} minute\n * @param {number} second\n * @param {number} ms\n * @returns {Date}\n */\n function ClockDate(year, month, date, hour, minute, second, ms) {\n // the Date constructor called as a function, ref Ecma-262 Edition 5.1, section 15.9.2.\n // This remains so in the 10th edition of 2019 as well.\n if (!(this instanceof ClockDate)) {\n return new NativeDate(ClockDate.clock.now).toString();\n }\n\n // if Date is called as a constructor with \'new\' keyword\n // Defensive and verbose to avoid potential harm in passing\n // explicit undefined when user does not pass argument\n switch (arguments.length) {\n case 0:\n return new NativeDate(ClockDate.clock.now);\n case 1:\n return new NativeDate(year);\n case 2:\n return new NativeDate(year, month);\n case 3:\n return new NativeDate(year, month, date);\n case 4:\n return new NativeDate(year, month, date, hour);\n case 5:\n return new NativeDate(year, month, date, hour, minute);\n case 6:\n return new NativeDate(\n year,\n month,\n date,\n hour,\n minute,\n second,\n );\n default:\n return new NativeDate(\n year,\n month,\n date,\n hour,\n minute,\n second,\n ms,\n );\n }\n }\n\n return mirrorDateProperties(ClockDate, NativeDate);\n }\n\n /**\n * Mirror Intl by default on our fake implementation\n *\n * Most of the properties are the original native ones,\n * but we need to take control of those that have a\n * dependency on the current clock.\n *\n * @returns {object} the partly fake Intl implementation\n */\n function createIntl() {\n const ClockIntl = {};\n /*\n * All properties of Intl are non-enumerable, so we need\n * to do a bit of work to get them out.\n */\n Object.getOwnPropertyNames(NativeIntl).forEach(\n (property) => (ClockIntl[property] = NativeIntl[property]),\n );\n\n ClockIntl.DateTimeFormat = function (...args) {\n const realFormatter = new NativeIntl.DateTimeFormat(...args);\n const formatter = {};\n\n ["formatRange", "formatRangeToParts", "resolvedOptions"].forEach(\n (method) => {\n formatter[method] =\n realFormatter[method].bind(realFormatter);\n },\n );\n\n ["format", "formatToParts"].forEach((method) => {\n formatter[method] = function (date) {\n return realFormatter[method](date || ClockIntl.clock.now);\n };\n });\n\n return formatter;\n };\n\n ClockIntl.DateTimeFormat.prototype = Object.create(\n NativeIntl.DateTimeFormat.prototype,\n );\n\n ClockIntl.DateTimeFormat.supportedLocalesOf =\n NativeIntl.DateTimeFormat.supportedLocalesOf;\n\n return ClockIntl;\n }\n\n //eslint-disable-next-line jsdoc/require-jsdoc\n function enqueueJob(clock, job) {\n // enqueues a microtick-deferred task - ecma262/#sec-enqueuejob\n if (!clock.jobs) {\n clock.jobs = [];\n }\n clock.jobs.push(job);\n }\n\n //eslint-disable-next-line jsdoc/require-jsdoc\n function runJobs(clock) {\n // runs all microtick-deferred tasks - ecma262/#sec-runjobs\n if (!clock.jobs) {\n return;\n }\n for (let i = 0; i < clock.jobs.length; i++) {\n const job = clock.jobs[i];\n job.func.apply(null, job.args);\n\n checkIsNearInfiniteLimit(clock, i);\n if (clock.loopLimit && i > clock.loopLimit) {\n throw getInfiniteLoopError(clock, job);\n }\n }\n resetIsNearInfiniteLimit();\n clock.jobs = [];\n }\n\n /**\n * @param {Clock} clock\n * @param {Timer} timer\n * @returns {number} id of the created timer\n */\n function addTimer(clock, timer) {\n if (timer.func === undefined) {\n throw new Error("Callback must be provided to timer calls");\n }\n\n if (addTimerReturnsObject) {\n // Node.js environment\n if (typeof timer.func !== "function") {\n throw new TypeError(\n `[ERR_INVALID_CALLBACK]: Callback must be a function. Received ${\n timer.func\n } of type ${typeof timer.func}`,\n );\n }\n }\n\n if (isNearInfiniteLimit) {\n timer.error = new Error();\n }\n\n timer.type = timer.immediate ? "Immediate" : "Timeout";\n\n if (timer.hasOwnProperty("delay")) {\n if (typeof timer.delay !== "number") {\n timer.delay = parseInt(timer.delay, 10);\n }\n\n if (!isNumberFinite(timer.delay)) {\n timer.delay = 0;\n }\n timer.delay = timer.delay > maxTimeout ? 1 : timer.delay;\n timer.delay = Math.max(0, timer.delay);\n }\n\n if (timer.hasOwnProperty("interval")) {\n timer.type = "Interval";\n timer.interval = timer.interval > maxTimeout ? 1 : timer.interval;\n }\n\n if (timer.hasOwnProperty("animation")) {\n timer.type = "AnimationFrame";\n timer.animation = true;\n }\n\n if (timer.hasOwnProperty("idleCallback")) {\n timer.type = "IdleCallback";\n timer.idleCallback = true;\n }\n\n if (!clock.timers) {\n clock.timers = {};\n }\n\n timer.id = uniqueTimerId++;\n timer.createdAt = clock.now;\n timer.callAt =\n clock.now + (parseInt(timer.delay) || (clock.duringTick ? 1 : 0));\n\n clock.timers[timer.id] = timer;\n\n if (addTimerReturnsObject) {\n const res = {\n refed: true,\n ref: function () {\n this.refed = true;\n return res;\n },\n unref: function () {\n this.refed = false;\n return res;\n },\n hasRef: function () {\n return this.refed;\n },\n refresh: function () {\n timer.callAt =\n clock.now +\n (parseInt(timer.delay) || (clock.duringTick ? 1 : 0));\n\n // it _might_ have been removed, but if not the assignment is perfectly fine\n clock.timers[timer.id] = timer;\n\n return res;\n },\n [Symbol.toPrimitive]: function () {\n return timer.id;\n },\n };\n return res;\n }\n\n return timer.id;\n }\n\n /* eslint consistent-return: "off" */\n /**\n * Timer comparitor\n *\n * @param {Timer} a\n * @param {Timer} b\n * @returns {number}\n */\n function compareTimers(a, b) {\n // Sort first by absolute timing\n if (a.callAt < b.callAt) {\n return -1;\n }\n if (a.callAt > b.callAt) {\n return 1;\n }\n\n // Sort next by immediate, immediate timers take precedence\n if (a.immediate && !b.immediate) {\n return -1;\n }\n if (!a.immediate && b.immediate) {\n return 1;\n }\n\n // Sort next by creation time, earlier-created timers take precedence\n if (a.createdAt < b.createdAt) {\n return -1;\n }\n if (a.createdAt > b.createdAt) {\n return 1;\n }\n\n // Sort next by id, lower-id timers take precedence\n if (a.id < b.id) {\n return -1;\n }\n if (a.id > b.id) {\n return 1;\n }\n\n // As timer ids are unique, no fallback `0` is necessary\n }\n\n /**\n * @param {Clock} clock\n * @param {number} from\n * @param {number} to\n * @returns {Timer}\n */\n function firstTimerInRange(clock, from, to) {\n const timers = clock.timers;\n let timer = null;\n let id, isInRange;\n\n for (id in timers) {\n if (timers.hasOwnProperty(id)) {\n isInRange = inRange(from, to, timers[id]);\n\n if (\n isInRange &&\n (!timer || compareTimers(timer, timers[id]) === 1)\n ) {\n timer = timers[id];\n }\n }\n }\n\n return timer;\n }\n\n /**\n * @param {Clock} clock\n * @returns {Timer}\n */\n function firstTimer(clock) {\n const timers = clock.timers;\n let timer = null;\n let id;\n\n for (id in timers) {\n if (timers.hasOwnProperty(id)) {\n if (!timer || compareTimers(timer, timers[id]) === 1) {\n timer = timers[id];\n }\n }\n }\n\n return timer;\n }\n\n /**\n * @param {Clock} clock\n * @returns {Timer}\n */\n function lastTimer(clock) {\n const timers = clock.timers;\n let timer = null;\n let id;\n\n for (id in timers) {\n if (timers.hasOwnProperty(id)) {\n if (!timer || compareTimers(timer, timers[id]) === -1) {\n timer = timers[id];\n }\n }\n }\n\n return timer;\n }\n\n /**\n * @param {Clock} clock\n * @param {Timer} timer\n */\n function callTimer(clock, timer) {\n if (typeof timer.interval === "number") {\n clock.timers[timer.id].callAt += timer.interval;\n } else {\n delete clock.timers[timer.id];\n }\n\n if (typeof timer.func === "function") {\n timer.func.apply(null, timer.args);\n } else {\n /* eslint no-eval: "off" */\n const eval2 = eval;\n (function () {\n eval2(timer.func);\n })();\n }\n }\n\n /**\n * Gets clear handler name for a given timer type\n *\n * @param {string} ttype\n */\n function getClearHandler(ttype) {\n if (ttype === "IdleCallback" || ttype === "AnimationFrame") {\n return `cancel${ttype}`;\n }\n return `clear${ttype}`;\n }\n\n /**\n * Gets schedule handler name for a given timer type\n *\n * @param {string} ttype\n */\n function getScheduleHandler(ttype) {\n if (ttype === "IdleCallback" || ttype === "AnimationFrame") {\n return `request${ttype}`;\n }\n return `set${ttype}`;\n }\n\n /**\n * Creates an anonymous function to warn only once\n */\n function createWarnOnce() {\n let calls = 0;\n return function (msg) {\n // eslint-disable-next-line\n !calls++ && console.warn(msg);\n };\n }\n const warnOnce = createWarnOnce();\n\n /**\n * @param {Clock} clock\n * @param {number} timerId\n * @param {string} ttype\n */\n function clearTimer(clock, timerId, ttype) {\n if (!timerId) {\n // null appears to be allowed in most browsers, and appears to be\n // relied upon by some libraries, like Bootstrap carousel\n return;\n }\n\n if (!clock.timers) {\n clock.timers = {};\n }\n\n // in Node, the ID is stored as the primitive value for `Timeout` objects\n // for `Immediate` objects, no ID exists, so it gets coerced to NaN\n const id = Number(timerId);\n\n if (Number.isNaN(id) || id < idCounterStart) {\n const handlerName = getClearHandler(ttype);\n\n if (clock.shouldClearNativeTimers === true) {\n const nativeHandler = clock[`_${handlerName}`];\n return typeof nativeHandler === "function"\n ? nativeHandler(timerId)\n : undefined;\n }\n warnOnce(\n `FakeTimers: ${handlerName} was invoked to clear a native timer instead of one created by this library.` +\n "\\nTo automatically clean-up native timers, use `shouldClearNativeTimers`.",\n );\n }\n\n if (clock.timers.hasOwnProperty(id)) {\n // check that the ID matches a timer of the correct type\n const timer = clock.timers[id];\n if (\n timer.type === ttype ||\n (timer.type === "Timeout" && ttype === "Interval") ||\n (timer.type === "Interval" && ttype === "Timeout")\n ) {\n delete clock.timers[id];\n } else {\n const clear = getClearHandler(ttype);\n const schedule = getScheduleHandler(timer.type);\n throw new Error(\n `Cannot clear timer: timer created with ${schedule}() but cleared with ${clear}()`,\n );\n }\n }\n }\n\n /**\n * @param {Clock} clock\n * @param {Config} config\n * @returns {Timer[]}\n */\n function uninstall(clock, config) {\n let method, i, l;\n const installedHrTime = "_hrtime";\n const installedNextTick = "_nextTick";\n\n for (i = 0, l = clock.methods.length; i < l; i++) {\n method = clock.methods[i];\n if (method === "hrtime" && _global.process) {\n _global.process.hrtime = clock[installedHrTime];\n } else if (method === "nextTick" && _global.process) {\n _global.process.nextTick = clock[installedNextTick];\n } else if (method === "performance") {\n const originalPerfDescriptor = Object.getOwnPropertyDescriptor(\n clock,\n `_${method}`,\n );\n if (\n originalPerfDescriptor &&\n originalPerfDescriptor.get &&\n !originalPerfDescriptor.set\n ) {\n Object.defineProperty(\n _global,\n method,\n originalPerfDescriptor,\n );\n } else if (originalPerfDescriptor.configurable) {\n _global[method] = clock[`_${method}`];\n }\n } else {\n if (_global[method] && _global[method].hadOwnProperty) {\n _global[method] = clock[`_${method}`];\n } else {\n try {\n delete _global[method];\n } catch (ignore) {\n /* eslint no-empty: "off" */\n }\n }\n }\n }\n\n if (config.shouldAdvanceTime === true) {\n _global.clearInterval(clock.attachedInterval);\n }\n\n // Prevent multiple executions which will completely remove these props\n clock.methods = [];\n\n // return pending timers, to enable checking what timers remained on uninstall\n if (!clock.timers) {\n return [];\n }\n return Object.keys(clock.timers).map(function mapper(key) {\n return clock.timers[key];\n });\n }\n\n /**\n * @param {object} target the target containing the method to replace\n * @param {string} method the keyname of the method on the target\n * @param {Clock} clock\n */\n function hijackMethod(target, method, clock) {\n clock[method].hadOwnProperty = Object.prototype.hasOwnProperty.call(\n target,\n method,\n );\n clock[`_${method}`] = target[method];\n\n if (method === "Date") {\n const date = mirrorDateProperties(clock[method], target[method]);\n target[method] = date;\n } else if (method === "Intl") {\n target[method] = clock[method];\n } else if (method === "performance") {\n const originalPerfDescriptor = Object.getOwnPropertyDescriptor(\n target,\n method,\n );\n // JSDOM has a read only performance field so we have to save/copy it differently\n if (\n originalPerfDescriptor &&\n originalPerfDescriptor.get &&\n !originalPerfDescriptor.set\n ) {\n Object.defineProperty(\n clock,\n `_${method}`,\n originalPerfDescriptor,\n );\n\n const perfDescriptor = Object.getOwnPropertyDescriptor(\n clock,\n method,\n );\n Object.defineProperty(target, method, perfDescriptor);\n } else {\n target[method] = clock[method];\n }\n } else {\n target[method] = function () {\n return clock[method].apply(clock, arguments);\n };\n\n Object.defineProperties(\n target[method],\n Object.getOwnPropertyDescriptors(clock[method]),\n );\n }\n\n target[method].clock = clock;\n }\n\n /**\n * @param {Clock} clock\n * @param {number} advanceTimeDelta\n */\n function doIntervalTick(clock, advanceTimeDelta) {\n clock.tick(advanceTimeDelta);\n }\n\n /**\n * @typedef {object} Timers\n * @property {setTimeout} setTimeout\n * @property {clearTimeout} clearTimeout\n * @property {setInterval} setInterval\n * @property {clearInterval} clearInterval\n * @property {Date} Date\n * @property {Intl} Intl\n * @property {SetImmediate=} setImmediate\n * @property {function(NodeImmediate): void=} clearImmediate\n * @property {function(number[]):number[]=} hrtime\n * @property {NextTick=} nextTick\n * @property {Performance=} performance\n * @property {RequestAnimationFrame=} requestAnimationFrame\n * @property {boolean=} queueMicrotask\n * @property {function(number): void=} cancelAnimationFrame\n * @property {RequestIdleCallback=} requestIdleCallback\n * @property {function(number): void=} cancelIdleCallback\n */\n\n /** @type {Timers} */\n const timers = {\n setTimeout: _global.setTimeout,\n clearTimeout: _global.clearTimeout,\n setInterval: _global.setInterval,\n clearInterval: _global.clearInterval,\n Date: _global.Date,\n };\n\n if (setImmediatePresent) {\n timers.setImmediate = _global.setImmediate;\n timers.clearImmediate = _global.clearImmediate;\n }\n\n if (hrtimePresent) {\n timers.hrtime = _global.process.hrtime;\n }\n\n if (nextTickPresent) {\n timers.nextTick = _global.process.nextTick;\n }\n\n if (performancePresent) {\n timers.performance = _global.performance;\n }\n\n if (requestAnimationFramePresent) {\n timers.requestAnimationFrame = _global.requestAnimationFrame;\n }\n\n if (queueMicrotaskPresent) {\n timers.queueMicrotask = true;\n }\n\n if (cancelAnimationFramePresent) {\n timers.cancelAnimationFrame = _global.cancelAnimationFrame;\n }\n\n if (requestIdleCallbackPresent) {\n timers.requestIdleCallback = _global.requestIdleCallback;\n }\n\n if (cancelIdleCallbackPresent) {\n timers.cancelIdleCallback = _global.cancelIdleCallback;\n }\n\n if (intlPresent) {\n timers.Intl = _global.Intl;\n }\n\n const originalSetTimeout = _global.setImmediate || _global.setTimeout;\n\n /**\n * @param {Date|number} [start] the system time - non-integer values are floored\n * @param {number} [loopLimit] maximum number of timers that will be run when calling runAll()\n * @returns {Clock}\n */\n function createClock(start, loopLimit) {\n // eslint-disable-next-line no-param-reassign\n start = Math.floor(getEpoch(start));\n // eslint-disable-next-line no-param-reassign\n loopLimit = loopLimit || 1000;\n let nanos = 0;\n const adjustedSystemTime = [0, 0]; // [millis, nanoremainder]\n\n if (NativeDate === undefined) {\n throw new Error(\n "The global scope doesn\'t have a `Date` object" +\n " (see https://github.com/sinonjs/sinon/issues/1852#issuecomment-419622780)",\n );\n }\n\n const clock = {\n now: start,\n Date: createDate(),\n loopLimit: loopLimit,\n };\n\n clock.Date.clock = clock;\n\n //eslint-disable-next-line jsdoc/require-jsdoc\n function getTimeToNextFrame() {\n return 16 - ((clock.now - start) % 16);\n }\n\n //eslint-disable-next-line jsdoc/require-jsdoc\n function hrtime(prev) {\n const millisSinceStart = clock.now - adjustedSystemTime[0] - start;\n const secsSinceStart = Math.floor(millisSinceStart / 1000);\n const remainderInNanos =\n (millisSinceStart - secsSinceStart * 1e3) * 1e6 +\n nanos -\n adjustedSystemTime[1];\n\n if (Array.isArray(prev)) {\n if (prev[1] > 1e9) {\n throw new TypeError(\n "Number of nanoseconds can\'t exceed a billion",\n );\n }\n\n const oldSecs = prev[0];\n let nanoDiff = remainderInNanos - prev[1];\n let secDiff = secsSinceStart - oldSecs;\n\n if (nanoDiff < 0) {\n nanoDiff += 1e9;\n secDiff -= 1;\n }\n\n return [secDiff, nanoDiff];\n }\n return [secsSinceStart, remainderInNanos];\n }\n\n /**\n * A high resolution timestamp in milliseconds.\n *\n * @typedef {number} DOMHighResTimeStamp\n */\n\n /**\n * performance.now()\n *\n * @returns {DOMHighResTimeStamp}\n */\n function fakePerformanceNow() {\n const hrt = hrtime();\n const millis = hrt[0] * 1000 + hrt[1] / 1e6;\n return millis;\n }\n\n if (hrtimeBigintPresent) {\n hrtime.bigint = function () {\n const parts = hrtime();\n return BigInt(parts[0]) * BigInt(1e9) + BigInt(parts[1]); // eslint-disable-line\n };\n }\n\n if (intlPresent) {\n clock.Intl = createIntl();\n clock.Intl.clock = clock;\n }\n\n clock.requestIdleCallback = function requestIdleCallback(\n func,\n timeout,\n ) {\n let timeToNextIdlePeriod = 0;\n\n if (clock.countTimers() > 0) {\n timeToNextIdlePeriod = 50; // const for now\n }\n\n const result = addTimer(clock, {\n func: func,\n args: Array.prototype.slice.call(arguments, 2),\n delay:\n typeof timeout === "undefined"\n ? timeToNextIdlePeriod\n : Math.min(timeout, timeToNextIdlePeriod),\n idleCallback: true,\n });\n\n return Number(result);\n };\n\n clock.cancelIdleCallback = function cancelIdleCallback(timerId) {\n return clearTimer(clock, timerId, "IdleCallback");\n };\n\n clock.setTimeout = function setTimeout(func, timeout) {\n return addTimer(clock, {\n func: func,\n args: Array.prototype.slice.call(arguments, 2),\n delay: timeout,\n });\n };\n if (typeof _global.Promise !== "undefined" && utilPromisify) {\n clock.setTimeout[utilPromisify.custom] =\n function promisifiedSetTimeout(timeout, arg) {\n return new _global.Promise(function setTimeoutExecutor(\n resolve,\n ) {\n addTimer(clock, {\n func: resolve,\n args: [arg],\n delay: timeout,\n });\n });\n };\n }\n\n clock.clearTimeout = function clearTimeout(timerId) {\n return clearTimer(clock, timerId, "Timeout");\n };\n\n clock.nextTick = function nextTick(func) {\n return enqueueJob(clock, {\n func: func,\n args: Array.prototype.slice.call(arguments, 1),\n error: isNearInfiniteLimit ? new Error() : null,\n });\n };\n\n clock.queueMicrotask = function queueMicrotask(func) {\n return clock.nextTick(func); // explicitly drop additional arguments\n };\n\n clock.setInterval = function setInterval(func, timeout) {\n // eslint-disable-next-line no-param-reassign\n timeout = parseInt(timeout, 10);\n return addTimer(clock, {\n func: func,\n args: Array.prototype.slice.call(arguments, 2),\n delay: timeout,\n interval: timeout,\n });\n };\n\n clock.clearInterval = function clearInterval(timerId) {\n return clearTimer(clock, timerId, "Interval");\n };\n\n if (setImmediatePresent) {\n clock.setImmediate = function setImmediate(func) {\n return addTimer(clock, {\n func: func,\n args: Array.prototype.slice.call(arguments, 1),\n immediate: true,\n });\n };\n\n if (typeof _global.Promise !== "undefined" && utilPromisify) {\n clock.setImmediate[utilPromisify.custom] =\n function promisifiedSetImmediate(arg) {\n return new _global.Promise(\n function setImmediateExecutor(resolve) {\n addTimer(clock, {\n func: resolve,\n args: [arg],\n immediate: true,\n });\n },\n );\n };\n }\n\n clock.clearImmediate = function clearImmediate(timerId) {\n return clearTimer(clock, timerId, "Immediate");\n };\n }\n\n clock.countTimers = function countTimers() {\n return (\n Object.keys(clock.timers || {}).length +\n (clock.jobs || []).length\n );\n };\n\n clock.requestAnimationFrame = function requestAnimationFrame(func) {\n const result = addTimer(clock, {\n func: func,\n delay: getTimeToNextFrame(),\n get args() {\n return [fakePerformanceNow()];\n },\n animation: true,\n });\n\n return Number(result);\n };\n\n clock.cancelAnimationFrame = function cancelAnimationFrame(timerId) {\n return clearTimer(clock, timerId, "AnimationFrame");\n };\n\n clock.runMicrotasks = function runMicrotasks() {\n runJobs(clock);\n };\n\n /**\n * @param {number|string} tickValue milliseconds or a string parseable by parseTime\n * @param {boolean} isAsync\n * @param {Function} resolve\n * @param {Function} reject\n * @returns {number|undefined} will return the new `now` value or nothing for async\n */\n function doTick(tickValue, isAsync, resolve, reject) {\n const msFloat =\n typeof tickValue === "number"\n ? tickValue\n : parseTime(tickValue);\n const ms = Math.floor(msFloat);\n const remainder = nanoRemainder(msFloat);\n let nanosTotal = nanos + remainder;\n let tickTo = clock.now + ms;\n\n if (msFloat < 0) {\n throw new TypeError("Negative ticks are not supported");\n }\n\n // adjust for positive overflow\n if (nanosTotal >= 1e6) {\n tickTo += 1;\n nanosTotal -= 1e6;\n }\n\n nanos = nanosTotal;\n let tickFrom = clock.now;\n let previous = clock.now;\n // ESLint fails to detect this correctly\n /* eslint-disable prefer-const */\n let timer,\n firstException,\n oldNow,\n nextPromiseTick,\n compensationCheck,\n postTimerCall;\n /* eslint-enable prefer-const */\n\n clock.duringTick = true;\n\n // perform microtasks\n oldNow = clock.now;\n runJobs(clock);\n if (oldNow !== clock.now) {\n // compensate for any setSystemTime() call during microtask callback\n tickFrom += clock.now - oldNow;\n tickTo += clock.now - oldNow;\n }\n\n //eslint-disable-next-line jsdoc/require-jsdoc\n function doTickInner() {\n // perform each timer in the requested range\n timer = firstTimerInRange(clock, tickFrom, tickTo);\n // eslint-disable-next-line no-unmodified-loop-condition\n while (timer && tickFrom <= tickTo) {\n if (clock.timers[timer.id]) {\n tickFrom = timer.callAt;\n clock.now = timer.callAt;\n oldNow = clock.now;\n try {\n runJobs(clock);\n callTimer(clock, timer);\n } catch (e) {\n firstException = firstException || e;\n }\n\n if (isAsync) {\n // finish up after native setImmediate callback to allow\n // all native es6 promises to process their callbacks after\n // each timer fires.\n originalSetTimeout(nextPromiseTick);\n return;\n }\n\n compensationCheck();\n }\n\n postTimerCall();\n }\n\n // perform process.nextTick()s again\n oldNow = clock.now;\n runJobs(clock);\n if (oldNow !== clock.now) {\n // compensate for any setSystemTime() call during process.nextTick() callback\n tickFrom += clock.now - oldNow;\n tickTo += clock.now - oldNow;\n }\n clock.duringTick = false;\n\n // corner case: during runJobs new timers were scheduled which could be in the range [clock.now, tickTo]\n timer = firstTimerInRange(clock, tickFrom, tickTo);\n if (timer) {\n try {\n clock.tick(tickTo - clock.now); // do it all again - for the remainder of the requested range\n } catch (e) {\n firstException = firstException || e;\n }\n } else {\n // no timers remaining in the requested range: move the clock all the way to the end\n clock.now = tickTo;\n\n // update nanos\n nanos = nanosTotal;\n }\n if (firstException) {\n throw firstException;\n }\n\n if (isAsync) {\n resolve(clock.now);\n } else {\n return clock.now;\n }\n }\n\n nextPromiseTick =\n isAsync &&\n function () {\n try {\n compensationCheck();\n postTimerCall();\n doTickInner();\n } catch (e) {\n reject(e);\n }\n };\n\n compensationCheck = function () {\n // compensate for any setSystemTime() call during timer callback\n if (oldNow !== clock.now) {\n tickFrom += clock.now - oldNow;\n tickTo += clock.now - oldNow;\n previous += clock.now - oldNow;\n }\n };\n\n postTimerCall = function () {\n timer = firstTimerInRange(clock, previous, tickTo);\n previous = tickFrom;\n };\n\n return doTickInner();\n }\n\n /**\n * @param {string|number} tickValue number of milliseconds or a human-readable value like "01:11:15"\n * @returns {number} will return the new `now` value\n */\n clock.tick = function tick(tickValue) {\n return doTick(tickValue, false);\n };\n\n if (typeof _global.Promise !== "undefined") {\n /**\n * @param {string|number} tickValue number of milliseconds or a human-readable value like "01:11:15"\n * @returns {Promise}\n */\n clock.tickAsync = function tickAsync(tickValue) {\n return new _global.Promise(function (resolve, reject) {\n originalSetTimeout(function () {\n try {\n doTick(tickValue, true, resolve, reject);\n } catch (e) {\n reject(e);\n }\n });\n });\n };\n }\n\n clock.next = function next() {\n runJobs(clock);\n const timer = firstTimer(clock);\n if (!timer) {\n return clock.now;\n }\n\n clock.duringTick = true;\n try {\n clock.now = timer.callAt;\n callTimer(clock, timer);\n runJobs(clock);\n return clock.now;\n } finally {\n clock.duringTick = false;\n }\n };\n\n if (typeof _global.Promise !== "undefined") {\n clock.nextAsync = function nextAsync() {\n return new _global.Promise(function (resolve, reject) {\n originalSetTimeout(function () {\n try {\n const timer = firstTimer(clock);\n if (!timer) {\n resolve(clock.now);\n return;\n }\n\n let err;\n clock.duringTick = true;\n clock.now = timer.callAt;\n try {\n callTimer(clock, timer);\n } catch (e) {\n err = e;\n }\n clock.duringTick = false;\n\n originalSetTimeout(function () {\n if (err) {\n reject(err);\n } else {\n resolve(clock.now);\n }\n });\n } catch (e) {\n reject(e);\n }\n });\n });\n };\n }\n\n clock.runAll = function runAll() {\n let numTimers, i;\n runJobs(clock);\n for (i = 0; i < clock.loopLimit; i++) {\n if (!clock.timers) {\n resetIsNearInfiniteLimit();\n return clock.now;\n }\n\n numTimers = Object.keys(clock.timers).length;\n if (numTimers === 0) {\n resetIsNearInfiniteLimit();\n return clock.now;\n }\n\n clock.next();\n checkIsNearInfiniteLimit(clock, i);\n }\n\n const excessJob = firstTimer(clock);\n throw getInfiniteLoopError(clock, excessJob);\n };\n\n clock.runToFrame = function runToFrame() {\n return clock.tick(getTimeToNextFrame());\n };\n\n if (typeof _global.Promise !== "undefined") {\n clock.runAllAsync = function runAllAsync() {\n return new _global.Promise(function (resolve, reject) {\n let i = 0;\n /**\n *\n */\n function doRun() {\n originalSetTimeout(function () {\n try {\n runJobs(clock);\n\n let numTimers;\n if (i < clock.loopLimit) {\n if (!clock.timers) {\n resetIsNearInfiniteLimit();\n resolve(clock.now);\n return;\n }\n\n numTimers = Object.keys(\n clock.timers,\n ).length;\n if (numTimers === 0) {\n resetIsNearInfiniteLimit();\n resolve(clock.now);\n return;\n }\n\n clock.next();\n\n i++;\n\n doRun();\n checkIsNearInfiniteLimit(clock, i);\n return;\n }\n\n const excessJob = firstTimer(clock);\n reject(getInfiniteLoopError(clock, excessJob));\n } catch (e) {\n reject(e);\n }\n });\n }\n doRun();\n });\n };\n }\n\n clock.runToLast = function runToLast() {\n const timer = lastTimer(clock);\n if (!timer) {\n runJobs(clock);\n return clock.now;\n }\n\n return clock.tick(timer.callAt - clock.now);\n };\n\n if (typeof _global.Promise !== "undefined") {\n clock.runToLastAsync = function runToLastAsync() {\n return new _global.Promise(function (resolve, reject) {\n originalSetTimeout(function () {\n try {\n const timer = lastTimer(clock);\n if (!timer) {\n runJobs(clock);\n resolve(clock.now);\n }\n\n resolve(clock.tickAsync(timer.callAt - clock.now));\n } catch (e) {\n reject(e);\n }\n });\n });\n };\n }\n\n clock.reset = function reset() {\n nanos = 0;\n clock.timers = {};\n clock.jobs = [];\n clock.now = start;\n };\n\n clock.setSystemTime = function setSystemTime(systemTime) {\n // determine time difference\n const newNow = getEpoch(systemTime);\n const difference = newNow - clock.now;\n let id, timer;\n\n adjustedSystemTime[0] = adjustedSystemTime[0] + difference;\n adjustedSystemTime[1] = adjustedSystemTime[1] + nanos;\n // update \'system clock\'\n clock.now = newNow;\n nanos = 0;\n\n // update timers and intervals to keep them stable\n for (id in clock.timers) {\n if (clock.timers.hasOwnProperty(id)) {\n timer = clock.timers[id];\n timer.createdAt += difference;\n timer.callAt += difference;\n }\n }\n };\n\n /**\n * @param {string|number} tickValue number of milliseconds or a human-readable value like "01:11:15"\n * @returns {number} will return the new `now` value\n */\n clock.jump = function jump(tickValue) {\n const msFloat =\n typeof tickValue === "number"\n ? tickValue\n : parseTime(tickValue);\n const ms = Math.floor(msFloat);\n\n for (const timer of Object.values(clock.timers)) {\n if (clock.now + ms > timer.callAt) {\n timer.callAt = clock.now + ms;\n }\n }\n clock.tick(ms);\n };\n\n if (performancePresent) {\n clock.performance = Object.create(null);\n clock.performance.now = fakePerformanceNow;\n }\n\n if (hrtimePresent) {\n clock.hrtime = hrtime;\n }\n\n return clock;\n }\n\n /* eslint-disable complexity */\n\n /**\n * @param {Config=} [config] Optional config\n * @returns {Clock}\n */\n function install(config) {\n if (\n arguments.length > 1 ||\n config instanceof Date ||\n Array.isArray(config) ||\n typeof config === "number"\n ) {\n throw new TypeError(\n `FakeTimers.install called with ${String(\n config,\n )} install requires an object parameter`,\n );\n }\n\n if (_global.Date.isFake === true) {\n // Timers are already faked; this is a problem.\n // Make the user reset timers before continuing.\n throw new TypeError(\n "Can\'t install fake timers twice on the same global object.",\n );\n }\n\n // eslint-disable-next-line no-param-reassign\n config = typeof config !== "undefined" ? config : {};\n config.shouldAdvanceTime = config.shouldAdvanceTime || false;\n config.advanceTimeDelta = config.advanceTimeDelta || 20;\n config.shouldClearNativeTimers =\n config.shouldClearNativeTimers || false;\n\n if (config.target) {\n throw new TypeError(\n "config.target is no longer supported. Use `withGlobal(target)` instead.",\n );\n }\n\n let i, l;\n const clock = createClock(config.now, config.loopLimit);\n clock.shouldClearNativeTimers = config.shouldClearNativeTimers;\n\n clock.uninstall = function () {\n return uninstall(clock, config);\n };\n\n clock.methods = config.toFake || [];\n\n if (clock.methods.length === 0) {\n // do not fake nextTick by default - GitHub#126\n clock.methods = Object.keys(timers).filter(function (key) {\n return key !== "nextTick" && key !== "queueMicrotask";\n });\n }\n\n if (config.shouldAdvanceTime === true) {\n const intervalTick = doIntervalTick.bind(\n null,\n clock,\n config.advanceTimeDelta,\n );\n const intervalId = _global.setInterval(\n intervalTick,\n config.advanceTimeDelta,\n );\n clock.attachedInterval = intervalId;\n }\n\n if (clock.methods.includes("performance")) {\n const proto = (() => {\n if (hasPerformanceConstructorPrototype) {\n return _global.performance.constructor.prototype;\n }\n if (hasPerformancePrototype) {\n return _global.Performance.prototype;\n }\n })();\n if (proto) {\n Object.getOwnPropertyNames(proto).forEach(function (name) {\n if (name !== "now") {\n clock.performance[name] =\n name.indexOf("getEntries") === 0\n ? NOOP_ARRAY\n : NOOP;\n }\n });\n } else if ((config.toFake || []).includes("performance")) {\n // user explicitly tried to fake performance when not present\n throw new ReferenceError(\n "non-existent performance object cannot be faked",\n );\n }\n }\n for (i = 0, l = clock.methods.length; i < l; i++) {\n const nameOfMethodToReplace = clock.methods[i];\n if (nameOfMethodToReplace === "hrtime") {\n if (\n _global.process &&\n typeof _global.process.hrtime === "function"\n ) {\n hijackMethod(_global.process, nameOfMethodToReplace, clock);\n }\n } else if (nameOfMethodToReplace === "nextTick") {\n if (\n _global.process &&\n typeof _global.process.nextTick === "function"\n ) {\n hijackMethod(_global.process, nameOfMethodToReplace, clock);\n }\n } else {\n hijackMethod(_global, nameOfMethodToReplace, clock);\n }\n }\n\n return clock;\n }\n\n /* eslint-enable complexity */\n\n return {\n timers: timers,\n createClock: createClock,\n install: install,\n withGlobal: withGlobal,\n };\n}\n\n/**\n * @typedef {object} FakeTimers\n * @property {Timers} timers\n * @property {createClock} createClock\n * @property {Function} install\n * @property {withGlobal} withGlobal\n */\n\n// /** @type {FakeTimers} */\nwindow.__wdio_sinon = withGlobal(globalThis);\n\n// // exports.timers = defaultImplementation.timers;\n// // exports.createClock = defaultImplementation.createClock;\n// // exports.install = defaultImplementation.install;\n// // exports.withGlobal = withGlobal;\n}\n';
var ClockManager = class {
#browser;
/**
* Where the clock runs: the session's current context, or a held tab
* when `emulate('clock')` was called on one.
*/
#scope;
#resetFn = () => Promise.resolve();
#isInstalled = false;
constructor(scope) {
this.#scope = scope;
this.#browser = isBrowsingContext(scope) ? scope.browser : scope;
}
#execute(script, ...args) {
return this.#scope.execute(script, ...args);
}
/**
* 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 log7.warn("Fake timers are already installed");
}
if (globalThis.window) {
return;
}
const emulateOptions = options || {};
const functionDeclaration = fakerScript;
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
*/
(isBrowsingContext(this.#scope) ? this.#scope.execute(`return (${functionDeclaration}).apply(null, arguments)`) : this.#browser.executeScript(`return (${functionDeclaration}).apply(null, arguments)`, [])).then(() => this.#execute(installFakeTimers, installOptions)),
/**
* add preload script to to emulate clock for upcoming page loads
*/
this.#browser.scriptAddPreloadScript({
functionDeclaration,
...isBrowsingContext(this.#scope) ? { contexts: [this.#scope.contextId] } : {}
}),
this.#scope.addInitScript(installFakeTimers, installOptions)
]);
this.#resetFn = async () => Promise.all([
this.#browser.scriptRemovePreloadScript({ script: libScript.script }),
this.#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.#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.#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: 810
},
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true
},
"iPad Mini": {
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 Mini 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 Pro 11": {
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: 834,
height: 1194
},
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true
},
"iPad Pro 11 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: 1194,
height: 834
},
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true
},
"iPhone 6": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.0 Mobile/15A372 Safari/604.1",
viewport: {
width: 375,
height: 667
},
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true
},
"iPhone 6 landscape": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.0 Mobile/15A372 Safari/604.1",
viewport: {
width: 667,
height: 375
},
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true
},
"iPhone 6 Plus": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.0 Mobile/15A372 Safari/604.1",
viewport: {
width: 414,
height: 736
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 6 Plus landscape": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.0 Mobile/15A372 Safari/604.1",
viewport: {
width: 736,
height: 414
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 7": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.0 Mobile/15A372 Safari/604.1",
viewport: {
width: 375,
height: 667
},
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true
},
"iPhone 7 landscape": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.0 Mobile/15A372 Safari/604.1",
viewport: {
width: 667,
height: 375
},
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true
},
"iPhone 7 Plus": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.0 Mobile/15A372 Safari/604.1",
viewport: {
width: 414,
height: 736
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 7 Plus landscape": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.0 Mobile/15A372 Safari/604.1",
viewport: {
width: 736,
height: 414
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 8": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.0 Mobile/15A372 Safari/604.1",
viewport: {
width: 375,
height: 667
},
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true
},
"iPhone 8 landscape": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.0 Mobile/15A372 Safari/604.1",
viewport: {
width: 667,
height: 375
},
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true
},
"iPhone 8 Plus": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.0 Mobile/15A372 Safari/604.1",
viewport: {
width: 414,
height: 736
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 8 Plus landscape": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.0 Mobile/15A372 Safari/604.1",
viewport: {
width: 736,
height: 414
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone SE": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/18.0 Mobile/14E304 Safari/602.1",
viewport: {
width: 320,
height: 568
},
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true
},
"iPhone SE landscape": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/18.0 Mobile/14E304 Safari/602.1",
viewport: {
width: 568,
height: 320
},
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true
},
"iPhone X": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.0 Mobile/15A372 Safari/604.1",
viewport: {
width: 375,
height: 812
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone X landscape": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.0 Mobile/15A372 Safari/604.1",
viewport: {
width: 812,
height: 375
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone XR": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 414,
height: 896
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone XR landscape": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 896,
height: 414
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 11": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 414,
height: 715
},
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true
},
"iPhone 11 landscape": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 800,
height: 364
},
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true
},
"iPhone 11 Pro": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 375,
height: 635
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 11 Pro landscape": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 724,
height: 325
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 11 Pro Max": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 414,
height: 715
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 11 Pro Max landscape": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 808,
height: 364
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 12": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 390,
height: 664
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 12 landscape": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 750,
height: 340
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 12 Pro": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 390,
height: 664
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 12 Pro landscape": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 750,
height: 340
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 12 Pro Max": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 428,
height: 746
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 12 Pro Max landscape": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 832,
height: 378
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 12 Mini": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 375,
height: 629
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 12 Mini landscape": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 712,
height: 325
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 13": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 390,
height: 664
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 13 landscape": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 750,
height: 342
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 13 Pro": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 390,
height: 664
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 13 Pro landscape": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 750,
height: 342
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 13 Pro Max": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 428,
height: 746
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 13 Pro Max landscape": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 832,
height: 380
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 13 Mini": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 375,
height: 629
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 13 Mini landscape": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 712,
height: 327
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 14": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 390,
height: 664
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 14 landscape": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 750,
height: 340
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 14 Plus": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 428,
height: 746
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 14 Plus landscape": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 832,
height: 378
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 14 Pro": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 393,
height: 660
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 14 Pro landscape": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 734,
height: 343
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 14 Pro Max": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 430,
height: 740
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 14 Pro Max landscape": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 814,
height: 380
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 15": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 393,
height: 659
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 15 landscape": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 734,
height: 343
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 15 Plus": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 430,
height: 739
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 15 Plus landscape": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 814,
height: 380
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 15 Pro": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 393,
height: 659
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 15 Pro landscape": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 734,
height: 343
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 15 Pro Max": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 430,
height: 739
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"iPhone 15 Pro Max landscape": {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
viewport: {
width: 814,
height: 380
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"Kindle Fire HDX": {
userAgent: "Mozilla/5.0 (Linux; U; en-us; KFAPWI Build/JDQ39) AppleWebKit/535.19 (KHTML, like Gecko) Silk/3.13 Safari/535.19 Silk-Accelerated=true",
viewport: {
width: 800,
height: 1280
},
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true
},
"Kindle Fire HDX landscape": {
userAgent: "Mozilla/5.0 (Linux; U; en-us; KFAPWI Build/JDQ39) AppleWebKit/535.19 (KHTML, like Gecko) Silk/3.13 Safari/535.19 Silk-Accelerated=true",
viewport: {
width: 1280,
height: 800
},
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true
},
"LG Optimus L70": {
userAgent: "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/128.0.6613.18 Mobile Safari/537.36",
viewport: {
width: 384,
height: 640
},
deviceScaleFactor: 1.25,
isMobile: true,
hasTouch: true
},
"LG Optimus L70 landscape": {
userAgent: "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/128.0.6613.18 Mobile Safari/537.36",
viewport: {
width: 640,
height: 384
},
deviceScaleFactor: 1.25,
isMobile: true,
hasTouch: true
},
"Microsoft Lumia 550": {
userAgent: "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Mobile Safari/537.36 Edge/14.14263",
viewport: {
width: 640,
height: 360
},
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true
},
"Microsoft Lumia 550 landscape": {
userAgent: "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Mobile Safari/537.36 Edge/14.14263",
viewport: {
width: 360,
height: 640
},
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true
},
"Microsoft Lumia 950": {
userAgent: "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Mobile Safari/537.36 Edge/14.14263",
viewport: {
width: 360,
height: 640
},
deviceScaleFactor: 4,
isMobile: true,
hasTouch: true
},
"Microsoft Lumia 950 landscape": {
userAgent: "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Mobile Safari/537.36 Edge/14.14263",
viewport: {
width: 640,
height: 360
},
deviceScaleFactor: 4,
isMobile: true,
hasTouch: true
},
"Nexus 10": {
userAgent: "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Safari/537.36",
viewport: {
width: 800,
height: 1280
},
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true
},
"Nexus 10 landscape": {
userAgent: "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Safari/537.36",
viewport: {
width: 1280,
height: 800
},
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true
},
"Nexus 4": {
userAgent: "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Mobile Safari/537.36",
viewport: {
width: 384,
height: 640
},
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true
},
"Nexus 4 landscape": {
userAgent: "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Mobile Safari/537.36",
viewport: {
width: 640,
height: 384
},
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true
},
"Nexus 5": {
userAgent: "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) 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
},
"Nexus 5 landscape": {
userAgent: "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) 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
},
"Nexus 5X": {
userAgent: "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Mobile Safari/537.36",
viewport: {
width: 412,
height: 732
},
deviceScaleFactor: 2.625,
isMobile: true,
hasTouch: true
},
"Nexus 5X landscape": {
userAgent: "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Mobile Safari/537.36",
viewport: {
width: 732,
height: 412
},
deviceScaleFactor: 2.625,
isMobile: true,
hasTouch: true
},
"Nexus 6": {
userAgent: "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Mobile Safari/537.36",
viewport: {
width: 412,
height: 732
},
deviceScaleFactor: 3.5,
isMobile: true,
hasTouch: true
},
"Nexus 6 landscape": {
userAgent: "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Mobile Safari/537.36",
viewport: {
width: 732,
height: 412
},
deviceScaleFactor: 3.5,
isMobile: true,
hasTouch: true
},
"Nexus 6P": {
userAgent: "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Mobile Safari/537.36",
viewport: {
width: 412,
height: 732
},
deviceScaleFactor: 3.5,
isMobile: true,
hasTouch: true
},
"Nexus 6P landscape": {
userAgent: "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Mobile Safari/537.36",
viewport: {
width: 732,
height: 412
},
deviceScaleFactor: 3.5,
isMobile: true,
hasTouch: true
},
"Nexus 7": {
userAgent: "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Safari/537.36",
viewport: {
width: 600,
height: 960
},
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true
},
"Nexus 7 landscape": {
userAgent: "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Safari/537.36",
viewport: {
width: 960,
height: 600
},
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true
},
"Nokia Lumia 520": {
userAgent: "Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 520)",
viewport: {
width: 320,
height: 533
},
deviceScaleFactor: 1.5,
isMobile: true,
hasTouch: true
},
"Nokia Lumia 520 landscape": {
userAgent: "Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 520)",
viewport: {
width: 533,
height: 320
},
deviceScaleFactor: 1.5,
isMobile: true,
hasTouch: true
},
"Nokia N9": {
userAgent: "Mozilla/5.0 (MeeGo; NokiaN9) AppleWebKit/534.13 (KHTML, like Gecko) NokiaBrowser/8.5.0 Mobile Safari/534.13",
viewport: {
width: 480,
height: 854
},
deviceScaleFactor: 1,
isMobile: true,
hasTouch: true
},
"Nokia N9 landscape": {
userAgent: "Mozilla/5.0 (MeeGo; NokiaN9) AppleWebKit/534.13 (KHTML, like Gecko) NokiaBrowser/8.5.0 Mobile Safari/534.13",
viewport: {
width: 854,
height: 480
},
deviceScaleFactor: 1,
isMobile: true,
hasTouch: true
},
"Pixel 2": {
userAgent: "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Mobile Safari/537.36",
viewport: {
width: 411,
height: 731
},
deviceScaleFactor: 2.625,
isMobile: true,
hasTouch: true
},
"Pixel 2 landscape": {
userAgent: "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Mobile Safari/537.36",
viewport: {
width: 731,
height: 411
},
deviceScaleFactor: 2.625,
isMobile: true,
hasTouch: true
},
"Pixel 2 XL": {
userAgent: "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Mobile Safari/537.36",
viewport: {
width: 411,
height: 823
},
deviceScaleFactor: 3.5,
isMobile: true,
hasTouch: true
},
"Pixel 2 XL landscape": {
userAgent: "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Mobile Safari/537.36",
viewport: {
width: 823,
height: 411
},
deviceScaleFactor: 3.5,
isMobile: true,
hasTouch: true
},
"Pixel 3": {
userAgent: "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Mobile Safari/537.36",
viewport: {
width: 393,
height: 786
},
deviceScaleFactor: 2.75,
isMobile: true,
hasTouch: true
},
"Pixel 3 landscape": {
userAgent: "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Mobile Safari/537.36",
viewport: {
width: 786,
height: 393
},
deviceScaleFactor: 2.75,
isMobile: true,
hasTouch: true
},
"Pixel 4": {
userAgent: "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Mobile Safari/537.36",
viewport: {
width: 353,
height: 745
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"Pixel 4 landscape": {
userAgent: "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Mobile Safari/537.36",
viewport: {
width: 745,
height: 353
},
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
},
"Pixel 4a(5G)": {
userAgent: "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Mobile Safari/537.36",
viewport: {
width: 412,
height: 765
},
deviceScaleFactor: 2.63,
isMobile: true,
hasTouch: true
},
"Pixel 4a(5G) landscape": {
userAgent: "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Mobile Safari/537.36",
viewport: {
width: 840,
height: 312
},
deviceScaleFactor: 2.63,
isMobile: true,
hasTouch: true
},
"Pixel 5": {
userAgent: "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Mobile Safari/537.36",
viewport: {
width: 393,
height: 727
},
deviceScaleFactor: 2.75,
isMobile: true,
hasTouch: true
},
"Pixel 5 landscape": {
userAgent: "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Mobile Safari/537.36",
viewport: {
width: 802,
height: 293
},
deviceScaleFactor: 2.75,
isMobile: true,
hasTouch: true
},
"Pixel 7": {
userAgent: "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Mobile Safari/537.36",
viewport: {
width: 412,
height: 839
},
deviceScaleFactor: 2.625,
isMobile: true,
hasTouch: true
},
"Pixel 7 landscape": {
userAgent: "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Mobile Safari/537.36",
viewport: {
width: 863,
height: 360
},
deviceScaleFactor: 2.625,
isMobile: true,
hasTouch: true
},
"Moto G4": {
userAgent: "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) 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
},
"Moto G4 landscape": {
userAgent: "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) 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
},
"Desktop Chrome HiDPI": {
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Safari/537.36",
viewport: {
width: 1280,
height: 720
},
deviceScaleFactor: 2,
isMobile: false,
hasTouch: false
},
"Desktop Edge HiDPI": {
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Safari/537.36 Edg/128.0.6613.18",
viewport: {
width: 1280,
height: 720
},
deviceScaleFactor: 2,
isMobile: false,
hasTouch: false
},
"Desktop Firefox HiDPI": {
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0",
viewport: {
width: 1280,
height: 720
},
deviceScaleFactor: 2,
isMobile: false,
hasTouch: false
},
"Desktop Safari": {
userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15",
viewport: {
width: 1280,
height: 720
},
deviceScaleFactor: 2,
isMobile: false,
hasTouch: false
},
"Desktop Chrome": {
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Safari/537.36",
viewport: {
width: 1280,
height: 720
},
deviceScaleFactor: 1,
isMobile: false,
hasTouch: false
},
"Desktop Edge": {
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.18 Safari/537.36 Edg/128.0.6613.18",
viewport: {
width: 1280,
height: 720
},
deviceScaleFactor: 1,
isMobile: false,
hasTouch: false
},
"Desktop Firefox": {
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0",
viewport: {
width: 1280,
height: 720
},
deviceScaleFactor: 1,
isMobile: false,
hasTouch: false
}
};
// src/session/emulationState.ts
var overrides = /* @__PURE__ */ new WeakMap();
var latestRestore = /* @__PURE__ */ new WeakMap();
function overrideMap(browser) {
let map = overrides.get(browser);
if (!map) {
map = /* @__PURE__ */ new Map();
overrides.set(browser, map);
}
return map;
}
function rememberOverride(browser, context, patch) {
const map = overrideMap(browser);
map.set(context, { ...map.get(context), ...patch });
}
function rememberedOverride(browser, context) {
return overrideMap(browser).get(context) ?? {};
}
function claimRestore(browser, scope, contexts) {
const key = `${scope}:${contexts.join(",")}`;
let map = latestRestore.get(browser);
if (!map) {
map = /* @__PURE__ */ new Map();
latestRestore.set(browser, map);
}
const token = /* @__PURE__ */ Symbol();
map.set(key, token);
return () => map.get(key) === token;
}
// src/commands/browser/emulate.ts
var SCOPES = [
"geolocation",
"userAgent",
"colorScheme",
"media",
"onLine",
"locale",
"timezone",
"touch",
"orientation",
"screen",
"viewportMeta",
"textLayout",
"scripting",
"scrollbar",
"forcedColors",
"device",
"clock"
];
var ORIENTATION_NATURAL = ["portrait", "landscape"];
var ORIENTATION_TYPES = ["portrait-primary", "portrait-secondary", "landscape-primary", "landscape-secondary"];
function storeRestoreFunction(browser, scope, fn) {
if (!restoreFunctions.has(browser)) {
restoreFunctions.set(browser, /* @__PURE__ */ new Map());
}
const restoreFunctionsList = restoreFunctions.get(browser)?.get(scope);
const updatedList = restoreFunctionsList ? [...restoreFunctionsList, fn] : [fn];
restoreFunctions.get(browser)?.set(scope, updatedList);
}
function browserOf(target) {
return isBrowsingContext(target) ? target.browser : target;
}
async function topLevelContexts(target) {
if (isBrowsingContext(target)) {
return [target.contextId];
}
return [await getContextManager(target).getCurrentTopLevelContext()];
}
async function install(target, scope, apply, clear, memory) {
const browser = browserOf(target);
const contexts = await topLevelContexts(target);
await apply(contexts);
memory?.apply(contexts[0]);
const current = claimRestore(browser, scope, contexts);
const restore2 = async () => {
if (!current()) {
return;
}
await clear(contexts);
memory?.clear(contexts[0]);
};
storeRestoreFunction(browser, scope, restore2);
return restore2;
}
function isUnsupportedCommand(err) {
const message = err instanceof Error ? err.message : String(err);
return message.includes("unknown command") || message.includes("unsupported operation");
}
function setCapturedViewport(browser, context, viewport, devicePixelRatio) {
return browser.browsingContextSetViewport({
context,
viewport,
devicePixelRatio
});
}
function toMediaFeatureWire(features) {
const wire = {};
for (const [key, value] of Object.entries(features)) {
if (value === void 0) {
continue;
}
wire[key.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`)] = value;
}
return wire;
}
function geolocationCoordinates(options) {
if (typeof options.latitude !== "number" || typeof options.longitude !== "number") {
throw new Error('Expected geolocation emulation options to include numeric "latitude" and "longitude"');
}
const coordinates = {
latitude: options.latitude,
longitude: options.longitude
};
if (typeof options.accuracy === "number") {
coordinates.accuracy = options.accuracy;
}
if (options.altitude !== void 0) {
coordinates.altitude = options.altitude;
}
if (options.altitudeAccuracy !== void 0) {
coordinates.altitudeAccuracy = options.altitudeAccuracy;
}
if (options.heading !== void 0) {
coordinates.heading = options.heading;
}
if (options.speed !== void 0) {
coordinates.speed = options.speed;
}
return coordinates;
}
function assertNonEmptyString(scope, options) {
if (typeof options !== "string" || options.length === 0) {
throw new Error(`Expected "${scope}" emulation options to be a non-empty string, received ${typeof options === "string" ? '""' : typeof options}`);
}
return options;
}
function assertOrientation2(options) {
const value = options;
const natural = value && typeof value === "object" ? value.natural : void 0;
const type = value && typeof value === "object" ? value.type : void 0;
if (!ORIENTATION_NATURAL.includes(natural) || !ORIENTATION_TYPES.includes(type)) {
throw new Error('Expected "orientation" emulation options to be { natural: "portrait" | "landscape", type: "portrait-primary" | "portrait-secondary" | "landscape-primary" | "landscape-secondary" }');
}
return {
natural,
type
};
}
function assertScreen(options) {
const value = options;
if (!value || typeof value !== "object" || !Number.isInteger(value.width) || !Number.isInteger(value.height) || value.width < 0 || value.height < 0) {
throw new Error('Expected "screen" emulation options to be { width, height } with non-negative integers');
}
return { width: value.width, height: value.height };
}
function assertMedia(options) {
if (!options || typeof options !== "object" || Array.isArray(options)) {
throw new Error('Expected "media" emulation options to be an object of media features');
}
const entries = Object.entries(options).filter(([, value]) => value !== void 0);
if (entries.length === 0) {
throw new Error('Expected "media" emulation options to include at least one media feature');
}
return Object.fromEntries(entries);
}
async function emulateDevice(target, name) {
const browser = browserOf(target);
if (typeof name !== "string") {
throw new Error(`Expected "device" emulation options to be a string, received "${typeof name}"`);
}
const device = deviceDescriptorsSource[name];
if (!device) {
throw new Error(`Unknown device name "${name}", please use one of the following: ${Object.keys(deviceDescriptorsSource).join(", ")}`);
}
const contexts = await topLevelContexts(target);
const context = contexts[0];
const previous = rememberedOverride(browser, context);
const desktop = deviceDescriptorsSource["Desktop Chrome"];
const deviceViewport = {
width: device.viewport.width,
height: device.viewport.height,
devicePixelRatio: device.deviceScaleFactor
};
const deviceTouch = device.hasTouch ? 1 : null;
const deviceTextLayout = device.isMobile ? "mobile" : null;
const deviceViewportMeta = device.isMobile ? true : null;
const steps = [
{
apply: () => browser.emulationSetUserAgentOverride({ userAgent: device.userAgent, contexts }),
undo: () => browser.emulationSetUserAgentOverride({
userAgent: previous.userAgent === void 0 ? null : previous.userAgent,
contexts
}),
clear: () => browser.emulationSetUserAgentOverride({ userAgent: null, contexts }),
remember: { userAgent: device.userAgent },
forget: { userAgent: null }
},
{
apply: () => setCapturedViewport(browser, context, {
width: deviceViewport.width,
height: deviceViewport.height
}, deviceViewport.devicePixelRatio),
undo: () => previous.viewport ? setCapturedViewport(browser, context, {
width: previous.viewport.width,
height: previous.viewport.height
}, previous.viewport.devicePixelRatio) : setCapturedViewport(browser, context, null, null),
clear: () => setCapturedViewport(browser, context, {
width: desktop.viewport.width,
height: desktop.viewport.height
}, desktop.deviceScaleFactor),
remember: { viewport: deviceViewport },
forget: {
viewport: {
width: desktop.viewport.width,
height: desktop.viewport.height,
devicePixelRatio: desktop.deviceScaleFactor
}
}
},
{
apply: () => browser.emulationSetTouchOverride({ maxTouchPoints: deviceTouch, contexts }),
undo: () => browser.emulationSetTouchOverride({
maxTouchPoints: previous.touch === void 0 ? null : previous.touch,
contexts
}),
clear: () => browser.emulationSetTouchOverride({ maxTouchPoints: null, contexts }),
remember: { touch: deviceTouch },
forget: { touch: null },
skip: deviceTouch === null && (previous.touch ?? null) === null
},
{
apply: () => browser.emulationSetTextLayoutModeOverride({ textLayoutMode: deviceTextLayout, contexts }),
undo: () => browser.emulationSetTextLayoutModeOverride({
textLayoutMode: previous.textLayout === void 0 ? null : previous.textLayout,
contexts
}),
clear: () => browser.emulationSetTextLayoutModeOverride({ textLayoutMode: null, contexts }),
remember: { textLayout: deviceTextLayout },
forget: { textLayout: null },
skip: deviceTextLayout === null && (previous.textLayout ?? null) === null,
optional: true
},
{
apply: () => browser.emulationSetViewportMetaOverride({ viewportMeta: deviceViewportMeta, contexts }),
undo: () => browser.emulationSetViewportMetaOverride({
viewportMeta: previous.viewportMeta === void 0 ? null : previous.viewportMeta,
contexts
}),
clear: () => browser.emulationSetViewportMetaOverride({ viewportMeta: null, contexts }),
remember: { viewportMeta: deviceViewportMeta },
forget: { viewportMeta: null },
skip: deviceViewportMeta === null && (previous.viewportMeta ?? null) === null,
optional: true
}
];
const applied = [];
try {
for (const step of steps) {
if (step.skip) {
continue;
}
try {
await step.apply();
} catch (err) {
if (step.optional && isUnsupportedCommand(err)) {
continue;
}
throw err;
}
applied.push(step);
}
} catch (err) {
for (const step of [...applied].reverse()) {
await Promise.resolve(step.undo()).catch(() => {
});
}
throw err;
}
const current = claimRestore(browser, "device", contexts);
rememberOverride(browser, context, Object.assign({}, ...applied.map((step) => step.remember)));
const restore2 = async () => {
if (!current()) {
return;
}
for (const step of [...applied].reverse()) {
await step.clear();
}
rememberOverride(browser, context, Object.assign({}, ...applied.map((step) => step.forget)));
};
storeRestoreFunction(browser, "device", restore2);
return restore2;
}
async function emulate(scope, options) {
const target = this;
const browser = browserOf(target);
if (!browser.isBidi) {
throw new Error("emulate command is only supported for Bidi");
}
if (scope === "geolocation") {
if (!options || typeof options !== "object") {
throw new Error("Missing geolocation emulation options");
}
if ("error" in options) {
if (options.error !== "positionUnavailable") {
throw new Error('Expected geolocation error to be "positionUnavailable"');
}
return install(
target,
"geolocation",
(contexts) => browser.emulationSetGeolocationOverride({
error: { type: "positionUnavailable" },
contexts
}),
(contexts) => browser.emulationSetGeolocationOverride({ coordinates: null, contexts })
);
}
const coordinates = geolocationCoordinates(options);
return install(
target,
"geolocation",
(contexts) => browser.emulationSetGeolocationOverride({ coordinates, contexts }),
(contexts) => browser.emulationSetGeolocationOverride({ coordinates: null, contexts })
);
}
if (scope === "userAgent") {
if (typeof options !== "string") {
throw new Error(`Expected userAgent emulation options to be a string, received ${typeof options}`);
}
return install(
target,
"userAgent",
(contexts) => browser.emulationSetUserAgentOverride({ userAgent: options, contexts }),
(contexts) => browser.emulationSetUserAgentOverride({ userAgent: null, contexts }),
{
apply: (context) => rememberOverride(browser, context, { userAgent: options }),
clear: (context) => rememberOverride(browser, context, { userAgent: null })
}
);
}
if (scope === "clock") {
const clock = new ClockManager(target);
await clock.install(options);
storeRestoreFunction(browser, "clock", clock.restore.bind(clock));
return clock;
}
if (scope === "colorScheme") {
if (options !== "light" && options !== "dark") {
throw new Error(`Expected "colorScheme" emulation options to be either "light" or "dark", received "${options}"`);
}
const features = toMediaFeatureWire({ prefersColorScheme: options });
return install(
target,
"colorScheme",
(contexts) => browser.emulationSetMediaFeaturesOverride({ features, contexts }),
(contexts) => browser.emulationSetMediaFeaturesOverride({ features: null, contexts })
);
}
if (scope === "media") {
const features = toMediaFeatureWire(assertMedia(options));
return install(
target,
"media",
(contexts) => browser.emulationSetMediaFeaturesOverride({ features, contexts }),
(contexts) => browser.emulationSetMediaFeaturesOverride({ features: null, contexts })
);
}
if (scope === "onLine") {
if (typeof options !== "boolean") {
throw new Error(`Expected "onLine" emulation options to be a boolean, received "${typeof options}"`);
}
return install(
target,
"onLine",
(contexts) => browser.emulationSetNetworkConditions({
networkConditions: options ? null : { type: "offline" },
contexts
}),
(contexts) => browser.emulationSetNetworkConditions({ networkConditions: null, contexts })
);
}
if (scope === "locale") {
const locale = assertNonEmptyString("locale", options);
return install(
target,
"locale",
(contexts) => browser.emulationSetLocaleOverride({ locale, contexts }),
(contexts) => browser.emulationSetLocaleOverride({ locale: null, contexts })
);
}
if (scope === "timezone") {
const timezone = assertNonEmptyString("timezone", options);
return install(
target,
"timezone",
(contexts) => browser.emulationSetTimezoneOverride({ timezone, contexts }),
(contexts) => browser.emulationSetTimezoneOverride({ timezone: null, contexts })
);
}
if (scope === "touch") {
if (typeof options !== "number" || !Number.isInteger(options) || options < 1) {
throw new Error(`Expected "touch" emulation options to be an integer >= 1, received "${options}"`);
}
return install(
target,
"touch",
(contexts) => browser.emulationSetTouchOverride({ maxTouchPoints: options, contexts }),
(contexts) => browser.emulationSetTouchOverride({ maxTouchPoints: null, contexts }),
{
apply: (context) => rememberOverride(browser, context, { touch: options }),
clear: (context) => rememberOverride(browser, context, { touch: null })
}
);
}
if (scope === "orientation") {
const screenOrientation = assertOrientation2(options);
return install(
target,
"orientation",
(contexts) => browser.emulationSetScreenOrientationOverride({ screenOrientation, contexts }),
(contexts) => browser.emulationSetScreenOrientationOverride({ screenOrientation: null, contexts })
);
}
if (scope === "screen") {
const screenArea = assertScreen(options);
return install(
target,
"screen",
(contexts) => browser.emulationSetScreenSettingsOverride({ screenArea, contexts }),
(contexts) => browser.emulationSetScreenSettingsOverride({ screenArea: null, contexts })
);
}
if (scope === "viewportMeta") {
if (options !== true) {
throw new Error('Expected "viewportMeta" emulation options to be true');
}
return install(
target,
"viewportMeta",
(contexts) => browser.emulationSetViewportMetaOverride({ viewportMeta: true, contexts }),
(contexts) => browser.emulationSetViewportMetaOverride({ viewportMeta: null, contexts }),
{
apply: (context) => rememberOverride(browser, context, { viewportMeta: true }),
clear: (context) => rememberOverride(browser, context, { viewportMeta: null })
}
);
}
if (scope === "textLayout") {
if (options !== "mobile") {
throw new Error('Expected "textLayout" emulation options to be "mobile"');
}
return install(
target,
"textLayout",
(contexts) => browser.emulationSetTextLayoutModeOverride({ textLayoutMode: "mobile", contexts }),
(contexts) => browser.emulationSetTextLayoutModeOverride({ textLayoutMode: null, contexts }),
{
apply: (context) => rememberOverride(browser, context, { textLayout: "mobile" }),
clear: (context) => rememberOverride(browser, context, { textLayout: null })
}
);
}
if (scope === "scripting") {
if (options !== false) {
throw new Error('Expected "scripting" emulation options to be false');
}
return install(
target,
"scripting",
(contexts) => browser.emulationSetScriptingEnabled({ enabled: false, contexts }),
(contexts) => browser.emulationSetScriptingEnabled({ enabled: null, contexts })
);
}
if (scope === "scrollbar") {
if (options !== "classic" && options !== "overlay") {
throw new Error(`Expected "scrollbar" emulation options to be "classic" or "overlay", received "${options}"`);
}
return install(
target,
"scrollbar",
(contexts) => browser.emulationSetScrollbarTypeOverride({ scrollbarType: options, contexts }),
(contexts) => browser.emulationSetScrollbarTypeOverride({ scrollbarType: null, contexts })
);
}
if (scope === "forcedColors") {
if (options !== "light" && options !== "dark") {
throw new Error(`Expected "forcedColors" emulation options to be "light" or "dark", received "${options}"`);
}
return install(
target,
"forcedColors",
(contexts) => browser.emulationSetForcedColorsModeThemeOverride({ theme: options, contexts }),
(contexts) => browser.emulationSetForcedColorsModeThemeOverride({ theme: null, contexts })
);
}
if (scope === "device") {
return emulateDevice(target, options);
}
throw new Error(`Invalid scope "${scope}", expected one of ${SCOPES.map((name) => `"${name}"`).join(", ")}`);
}
// src/commands/browser/execute.ts
import { getBrowserObject as getBrowserObject10 } from "@wdio/utils";
import { polyfillFn } from "./scripts/polyfill.js";
async function execute(script, ...args) {
if (typeof script !== "string" && typeof script !== "function") {
throw new Error("number or type of arguments don't agree with execute protocol command");
}
const browser = getBrowserObject10(this);
const multiRemote2 = "isMultiRemote" in this && this.isMultiRemote;
if (this.isBidi && !multiRemote2) {
const held = heldBrowsingContext(this) ?? args.reduce(
(found, arg) => found ?? heldBrowsingContext(arg),
void 0
);
if (held?.isFrame) {
await assertFrameAttached(held);
}
const context = held ? held.contextId : await contextIdOf(this);
const functionDeclaration = createBidiFunctionDeclaration(script);
const params = {
functionDeclaration,
awaitPromise: true,
arguments: args.map((arg) => LocalValue.getArgument(arg)),
target: {
context
}
};
const result = await browser.scriptCallFunction(params);
return parseScriptResult(params, result);
}
const isAsyncFn = typeof script === "function" && script.constructor.name === "AsyncFunction";
if (typeof script === "function") {
script = isAsyncFn ? `
${polyfillFn}
webdriverioPolyfill();
var done = arguments[arguments.length - 1];
var args = Array.prototype.slice.call(arguments, 0, -1);
Promise.resolve((${script}).apply(null, args)).then(function (result) {
done(result)
}, function (error) {
done({
__wdioError: true,
message: error && error.message ? error.message : String(error),
stack: error && error.stack,
name: error && error.name
})
})
` : `
${polyfillFn}
webdriverioPolyfill();
return (${script}).apply(null, arguments)
`;
}
const scriptArgs = verifyArgsAndStripIfElement(args);
if (isAsyncFn) {
const result = await browser.executeAsyncScript(script, scriptArgs);
if (isScriptError(result)) {
const error = new Error(result.message);
if (result.name) {
error.name = result.name;
}
if (result.stack) {
error.stack = result.stack;
}
throw error;
}
return result;
}
return browser.executeScript(script, scriptArgs);
}
function isScriptError(result) {
return Boolean(
result && typeof result === "object" && result.__wdioError === true
);
}
// src/commands/browser/forward.ts
import { runClassicProtocolCommand as runClassicProtocolCommand2 } from "webdriver";
async function forward() {
if (!this.isBidi) {
await runClassicProtocolCommand2.call(this, "forward");
return;
}
await traverseTopLevelHistory(this, 1);
}
// src/commands/browser/getCookies.ts
import logger8 from "@wdio/logger";
var log8 = logger8("webdriverio");
async function getCookies(filter, sourceOrigin) {
assertObjectCookieFilter(filter);
if (!this.isBidi) {
return getCookiesClassic.call(this, filter);
}
if (isBrowsingContext(this)) {
return readBidiCookies(this.browser, {
...sourceOrigin === null ? {} : {
partition: { type: "context", context: this.contextId }
},
...typeof filter !== "undefined" ? { filter } : {}
}, filter);
}
let url2;
try {
url2 = new URL(await this.getUrl());
if (url2.origin === "null") {
return getCookiesClassic.call(this, filter);
}
} catch {
return getCookiesClassic.call(this, filter);
}
const params = sourceOrigin === null ? {} : {
partition: {
type: "storageKey",
sourceOrigin: sourceOrigin || url2.origin
}
};
if (typeof filter !== "undefined") {
params.filter = filter;
}
return readBidiCookies(this, params, filter);
}
async function readBidiCookies(browser, params, filter) {
try {
const { cookies } = await browser.storageGetCookies(params);
const partitionedByContext = params.partition?.type === "context";
if (cookies.length === 0 && !partitionedByContext) {
log8.debug("BiDi getCookies returned empty, falling back to classic");
return getCookiesClassic.call(browser, filter);
}
return cookies.map((cookie) => ({
...cookie,
value: cookie.value.type === "base64" ? Buffer.from(cookie.value.value, "base64").toString("utf-8") : cookie.value.value
}));
} catch (err) {
if (params.partition?.type === "context") {
throw err;
}
log8.warn(`BiDi getCookies failed, falling back to classic: ${err.message}`);
return getCookiesClassic.call(browser, filter);
}
}
var REMOVED_COOKIE_FILTER = "Passing a string or string array to `getCookies` was removed in WebdriverIO v10. Use an object filter, for example `await browser.getCookies({ name: 'session' })`.";
function assertObjectCookieFilter(filter) {
if (typeof filter === "undefined") {
return;
}
if (typeof filter === "string" || Array.isArray(filter)) {
throw new Error(REMOVED_COOKIE_FILTER);
}
if (typeof filter !== "object" || filter === null) {
throw new Error("`getCookies` only accepts a cookie filter object.");
}
}
async function getCookiesClassic(filter) {
if (!filter) {
return this.getAllCookies();
}
const allCookies = await this.getAllCookies();
const filterValue = getCookieValue(filter.value);
return allCookies.filter((cookie) => (filter.name === void 0 || filter.name === cookie.name) && (filter.value === void 0 || filterValue === cookie.value) && (filter.path === void 0 || filter.path === cookie.path) && (filter.domain === void 0 || filter.domain === cookie.domain) && (filter.sameSite === void 0 || filter.sameSite === cookie.sameSite) && (filter.expiry === void 0 || filter.expiry === cookie.expiry) && (filter.httpOnly === void 0 || filter.httpOnly === cookie.httpOnly) && (filter.secure === void 0 || filter.secure === cookie.secure));
}
function getCookieValue(value) {
if (!value) {
return;
}
return value.type === "base64" ? Buffer.from(value.value, "base64").toString("utf-8") : value.value;
}
// src/commands/browser/getPuppeteer.ts
import logger9 from "@wdio/logger";
import { userImport } from "@wdio/utils";
var log9 = logger9("webdriverio");
var DEBUG_PIPE_FLAG = "remote-debugging-pipe";
async function getPuppeteer() {
if (globalThis.wdio) {
throw new Error("Puppeteer is not supported in browser runner");
}
const puppeteer = await userImport("puppeteer-core");
if (!puppeteer) {
throw new Error(
'You need to install "puppeteer-core" package as a dependency in order to use the "getPuppeteer" method'
);
}
if (this.puppeteer?.connected) {
log9.debug("Reusing existing puppeteer session");
return this.puppeteer;
}
const { headers } = this.options;
const cdpEndpoint = this.capabilities["se:cdp"];
if (cdpEndpoint) {
this.puppeteer = await puppeteer.connect({
browserWSEndpoint: cdpEndpoint,
defaultViewport: null,
headers
});
return this.puppeteer;
}
const requestedCapabilities = this.requestedCapabilities?.alwaysMatch || this.requestedCapabilities;
const isAerokubeSession = requestedCapabilities["selenoid:options"] || requestedCapabilities["moon:options"];
if (isAerokubeSession) {
const { hostname, port } = this.options;
this.puppeteer = await puppeteer.connect({
browserWSEndpoint: `ws://${hostname}:${port}/devtools/${this.sessionId}`,
defaultViewport: null,
headers
});
return this.puppeteer;
}
const chromiumOptions = this.capabilities["goog:chromeOptions"] || this.capabilities["ms:edgeOptions"];
if (chromiumOptions && chromiumOptions.debuggerAddress) {
this.puppeteer = await puppeteer.connect({
browserURL: `http://${chromiumOptions.debuggerAddress.replace("localhost", "0.0.0.0")}`,
defaultViewport: null
});
return this.puppeteer;
} else if (
/**
* if --remote-debugging-pipe is set as Chrome flag, we can't attach to the session
* as there won't be a `debuggerAddress` available in the capabilities. Provide this
* better error message to the user.
*/
chromiumOptions && (chromiumOptions.args?.includes(DEBUG_PIPE_FLAG) || chromiumOptions.args?.includes(`--${DEBUG_PIPE_FLAG}`))
) {
throw new Error(`Cannot attach to Chrome Devtools session if --${DEBUG_PIPE_FLAG} is set as Chrome flag.`);
}
if (this.capabilities.browserName?.toLowerCase() === "firefox") {
if (!this.capabilities.browserVersion) {
throw new Error(`Can't find "browserVersion" in capabilities`);
}
const majorVersion = parseInt(this.capabilities.browserVersion.split(".").shift() || "", 10);
if (majorVersion >= 79) {
const reqCaps = this.requestedCapabilities.alwaysMatch || this.requestedCapabilities;
let browserURL;
if (this.capabilities["moz:debuggerAddress"]) {
browserURL = this.capabilities["moz:debuggerAddress"];
} else {
const ffOptions = this.capabilities["moz:firefoxOptions"];
const ffArgs = reqCaps["moz:firefoxOptions"]?.args || [];
const rdPort = ffOptions && ffOptions.debuggerAddress ? ffOptions.debuggerAddress : ffArgs[ffArgs.findIndex((arg) => arg === FF_REMOTE_DEBUG_ARG) + 1];
if (rdPort) {
browserURL = `http://localhost:${rdPort}`;
}
}
if (!browserURL) {
throw new Error(
'Could\'t find a websocket url within returned capabilities to connect to! Make sure you have "moz:debuggerAddress" set to `true` in your Firefox capabilities'
);
}
this.puppeteer = await puppeteer.connect({
browserURL,
defaultViewport: null
});
return this.puppeteer;
}
}
throw new Error(
"Using DevTools capabilities is not supported for this session. This feature is only supported for local testing on Chrome, Firefox and Chromium Edge."
);
}
// src/commands/browser/getWindowSize.ts
import { getBrowserObject as getBrowserObject11 } from "@wdio/utils";
async function getWindowSize() {
const browser = getBrowserObject11(this);
const { width, height } = await browser.getWindowRect();
return { width, height };
}
// src/utils/webExtension.ts
function annotateWebExtensionError(err, command) {
const message = err instanceof Error ? err.message : String(err);
if (!/method not available/i.test(message)) {
return err instanceof Error ? err : new Error(message);
}
const error = new Error(
`${message} Start Chrome or Edge with --enable-unsafe-extension-debugging and --remote-debugging-pipe so ${command} is available. Chrome 136 and newer also require --user-data-dir together with --remote-debugging-pipe.`
);
if (err instanceof Error) {
Object.defineProperty(error, "cause", {
configurable: true,
value: err
});
}
return error;
}
// src/commands/browser/installExtension.ts
var LOCAL_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
async function installExtension(extension) {
if (!this.isBidi) {
throw new Error("installExtension requires a WebDriver BiDi session (webExtension.install)");
}
const extensionData = await extensionDataFor.call(this, extension);
let result;
try {
result = await this.webExtensionInstall({ extensionData });
} catch (err) {
throw annotateWebExtensionError(err, "webExtension.install");
}
const id = result?.extension;
if (typeof id !== "string" || id.length === 0) {
throw new Error("webExtension.install did not return an extension id");
}
return id;
}
async function extensionDataFor(extension) {
if (isBase64Payload(extension)) {
return { type: "base64", value: extension.base64 };
}
if (typeof extension !== "string") {
throw new Error("installExtension expects a path string or { base64: string }");
}
return environment.value.extensionDataFromPath(extension, isRemoteSession(this));
}
function isBase64Payload(value) {
if (typeof value !== "object" || value === null || Array.isArray(value)) {
return false;
}
const record = value;
return Object.keys(record).length === 1 && typeof record.base64 === "string";
}
function isRemoteSession(browser) {
const { hostname, user, key } = browser.options;
if (typeof user === "string" && user.length > 0 && typeof key === "string" && key.length > 0) {
return true;
}
return typeof hostname === "string" && hostname.length > 0 && !LOCAL_HOSTS.has(hostname.toLowerCase());
}
// src/commands/browser/keys.ts
async function keys(value) {
let keySequence = [];
if (typeof value === "string") {
keySequence = checkUnicode(value);
} else if (Array.isArray(value)) {
const charArray = value;
for (const charSet of charArray) {
keySequence = keySequence.concat(checkUnicode(charSet));
}
} else {
throw new Error('"keys" command requires a string or array of strings as parameter');
}
const keyAction = this.action("key");
keySequence.forEach((value2) => keyAction.down(value2));
if (!this.isIOS) {
keyAction.pause(10);
}
keySequence.forEach((value2) => keyAction.up(value2));
return keyAction.perform(true);
}
// src/commands/browser/mock.ts
import { getBrowserObject as getBrowserObject12 } from "@wdio/utils";
// src/utils/interception/index.ts
import logger10 from "@wdio/logger";
import { setWdioKind } from "@wdio/utils";
import { URLPattern } from "urlpattern-polyfill";
// src/utils/Timer.ts
var TIMEOUT_ERROR = "timeout";
var NOOP = () => {
};
var Timer = class {
constructor(_delay, _timeout, _fn, _leading = false, _signal) {
this._delay = _delay;
this._timeout = _timeout;
this._fn = _fn;
this._leading = _leading;
this._signal = _signal;
this.#retPromise = new Promise((resolve, reject) => {
this._resolve = resolve;
this._reject = reject;
});
this._start();
}
_delay;
_timeout;
_fn;
_leading;
_signal;
#retPromise;
_conditionExecutedCnt = 0;
_resolve = NOOP;
_reject = NOOP;
_startTime;
_ticks = 0;
_timeoutId;
_mainTimeoutId;
_lastError;
then(thennable, catchable) {
return this.#retPromise.then(thennable, catchable);
}
catch(catchable) {
return this.#retPromise.catch(catchable);
}
_start() {
this._startTime = Date.now();
if (this._leading) {
this._tick();
} else {
this._timeoutId = setTimeout(this._tick.bind(this), this._delay);
}
if (this._wasConditionExecuted()) {
return;
}
this._mainTimeoutId = setTimeout(() => {
if (!this._wasConditionExecuted()) {
return;
}
const reason = this._lastError || new Error(TIMEOUT_ERROR);
this._reject(reason);
this._stop();
}, this._timeout);
}
_stop() {
if (this._timeoutId) {
clearTimeout(this._timeoutId);
}
delete this._timeoutId;
}
_stopMain() {
if (this._mainTimeoutId) {
clearTimeout(this._mainTimeoutId);
}
}
_tick() {
try {
const result = this._fn();
if (!result) {
return this._checkCondition(new Error(TIMEOUT_ERROR));
}
if (typeof result.then !== "function") {
return this._checkCondition(void 0, result);
}
result.then(
(res) => this._checkCondition(void 0, res),
(err) => this._checkCondition(err)
);
} catch (err) {
return this._checkCondition(err);
}
}
_checkCondition(err, res) {
this._lastError = err;
if (this._signal?.aborted) {
this._reject(this._lastError || new Error("Aborted"));
this._stop();
this._stopMain();
return;
}
++this._conditionExecutedCnt;
if (res) {
this._resolve(res);
this._stop();
this._stopMain();
return;
}
const diff = Date.now() - (this._startTime || 0) - this._ticks++ * this._delay;
const delay2 = Math.max(0, this._delay - diff);
this._stop();
if (this._hasTime(delay2)) {
this._timeoutId = setTimeout(this._tick.bind(this), delay2);
} else {
this._stopMain();
const reason = this._lastError || new Error(TIMEOUT_ERROR);
this._reject(reason);
}
}
_hasTime(delay2) {
return Date.now() - (this._startTime || 0) + delay2 <= this._timeout;
}
_wasConditionExecuted() {
return this._conditionExecutedCnt > 0;
}
};
var Timer_default = Timer;
// src/utils/interception/utils.ts
function parseOverwrite(overwrite, request) {
const result = {};
if ("body" in overwrite && overwrite.body) {
const bodyOverwrite = typeof overwrite.body === "function" ? overwrite.body(request) : overwrite.body;
result.body = bodyOverwrite?.type === "string" || bodyOverwrite?.type === "base64" ? bodyOverwrite : typeof bodyOverwrite === "string" ? { type: "string", value: bodyOverwrite } : { type: "base64", value: btoa(JSON.stringify(bodyOverwrite || "")) };
}
if ("headers" in overwrite) {
const headersOverwrite = typeof overwrite.headers === "function" ? overwrite.headers(request) : overwrite.headers;
result.headers = Object.entries(headersOverwrite || {}).map(([name, value]) => ({
name,
value: { type: "string", value }
}));
}
if ("cookies" in overwrite && overwrite.cookies) {
const cookieOverwrite = typeof overwrite.cookies === "function" ? overwrite.cookies(request) || [] : overwrite.cookies;
result.cookies = cookieOverwrite.map((cookie) => ({
name: cookie.name,
value: {
type: "string",
value: cookie.value
},
domain: cookie.domain,
path: cookie.path,
expires: cookie.expiry,
httpOnly: cookie.httpOnly,
secure: cookie.secure,
sameSite: cookie.sameSite?.toLowerCase()
}));
}
if ("statusCode" in overwrite && overwrite.statusCode) {
const statusCodeOverwrite = typeof overwrite.statusCode === "function" ? overwrite.statusCode(request) : overwrite.statusCode;
result.statusCode = statusCodeOverwrite;
}
if ("method" in overwrite) {
result.method = typeof overwrite.method === "function" ? overwrite.method(request) : overwrite.method;
}
if ("url" in overwrite) {
result.url = typeof overwrite.url === "function" ? overwrite.url(request) : overwrite.url;
}
return result;
}
var URL_PATTERN_SYNTAX = /[*:(){}?+\\]/;
function getPatternParam(pattern, key) {
const value = pattern[key];
if (URL_PATTERN_SYNTAX.test(value)) {
return;
}
if (key === "port" && pattern.port === "") {
return pattern.protocol === "https" ? "443" : "80";
}
return value;
}
// src/utils/interception/index.ts
var log10 = logger10("WebDriverInterception");
var DEFAULT_SPY_COLLECTED_BODY_SIZE = 10 * 1024 * 1024;
var subscribedSessions = /* @__PURE__ */ new WeakMap();
var RELEASED = /* @__PURE__ */ Symbol("wdio.interception.released");
var PENDING = /* @__PURE__ */ Symbol("wdio.interception.pending");
function isContested(event) {
return Boolean(event.intercepts && event.intercepts.length > 1);
}
function isClaimed(event) {
return Boolean(event[RELEASED]);
}
function claim(event) {
const claimable = event;
if (claimable[RELEASED]) {
return false;
}
claimable[RELEASED] = true;
return true;
}
function trackPending(event, decision) {
const marked = event;
const pending = marked[PENDING] ?? (marked[PENDING] = []);
const settled = decision.then(() => void 0, () => void 0);
pending.push(settled);
void settled.finally(() => {
const index = pending.indexOf(settled);
if (index !== -1) {
pending.splice(index, 1);
}
});
}
function pendingDecisions(event) {
const pending = event[PENDING];
return pending ? pending.slice() : [];
}
function toStringBody(payload) {
if (typeof payload === "string") {
return payload;
}
const serialized = JSON.stringify(payload);
if (typeof serialized !== "string") {
throw new Error(
`Failed to serialize mock.respond() payload of type "${typeof payload}". The response body must be a string, ArrayBufferView, ArrayBuffer, or JSON-serializable value.`
);
}
return serialized;
}
function decodeHeader(value) {
return value.type === "string" ? value.value : UTF8_DECODER.decode(decodeBase64(value.value));
}
var getArrayBufferByteLength = Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, "byteLength").get;
var UTF8_DECODER = new TextDecoder("utf-8", { ignoreBOM: true });
function isArrayBuffer(value) {
try {
getArrayBufferByteLength.call(value);
return true;
} catch {
return false;
}
}
function toNetworkBody(payload) {
if (ArrayBuffer.isView(payload)) {
const bytes = new Uint8Array(payload.buffer, payload.byteOffset, payload.byteLength);
return { type: "base64", value: encodeBase64(bytes) };
}
if (isArrayBuffer(payload)) {
return { type: "base64", value: encodeBase64(new Uint8Array(payload)) };
}
return { type: "string", value: toStringBody(payload) };
}
var WebDriverInterception = class _WebDriverInterception {
#pattern;
#patternId;
#mockId;
/**
* The `SESSION_MOCKS` key `mock()` stored this interception under
*/
#sessionKey;
/**
* Restores a mock of a held context when that context closes
*/
#onContextDestroyed;
#warnedAboutEarlyAnswer = false;
#filterOptions;
#browser;
#eventHandler = /* @__PURE__ */ new Map();
#restored = false;
#requestOverwrites = [];
#respondOverwrites = [];
#calls = [];
#overwrittenResponseBodies = /* @__PURE__ */ new Map();
#requestPostData = /* @__PURE__ */ new Map();
#isCollectingNetworkData;
#hasOneResponseCollected = false;
#hasOneBodyReadStarted = false;
#blockedRequests = /* @__PURE__ */ new Set();
#requestsRespondedWithoutFetch = /* @__PURE__ */ new Set();
constructor(pattern, mockId, filterOptions, browser, isCollectingNetworkData = false, sessionKey, contexts) {
this.#sessionKey = sessionKey;
this.#pattern = pattern;
this.#patternId = getPatternId(pattern);
this.#mockId = mockId;
this.#filterOptions = filterOptions;
this.#browser = browser;
this.#isCollectingNetworkData = isCollectingNetworkData;
browser.on("network.beforeRequestSent", this.#handleBeforeRequestSent.bind(this));
browser.on("network.responseStarted", this.#handleResponseStarted.bind(this));
browser.on("network.responseCompleted", this.#handleResponseCompleted.bind(this));
if (contexts?.length) {
this.#onContextDestroyed = ({ context }) => {
if (contexts.includes(context)) {
this.restore().catch(() => {
});
}
};
browser.on("browsingContext.contextDestroyed", this.#onContextDestroyed);
}
}
static async initiate(url2, filterOptions, browser, scope = {}) {
const pattern = parseUrlPattern(url2);
const isCollectingNetworkData = browser.options.maxSpyCollectedBodySize !== 0;
if (!subscribedSessions.has(browser) || subscribedSessions.get(browser) !== browser.sessionId) {
await browser.sessionSubscribe({
events: [
"network.beforeRequestSent",
"network.responseStarted",
"network.responseCompleted"
]
});
try {
if (isCollectingNetworkData) {
await browser.networkAddDataCollector({
dataTypes: ["request", "response"],
maxEncodedDataSize: typeof browser.options.maxSpyCollectedBodySize === "number" ? browser.options.maxSpyCollectedBodySize : DEFAULT_SPY_COLLECTED_BODY_SIZE
});
}
} catch (error) {
log10.warn(`[BiDi] network.addDataCollector not supported: ${error?.message}`);
}
log10.info("subscribed to network events");
subscribedSessions.set(browser, browser.sessionId);
}
const interception = await browser.networkAddIntercept({
...scope.contexts ? { contexts: scope.contexts } : {},
phases: ["beforeRequestSent", "responseStarted"],
urlPatterns: [{
type: "pattern",
protocol: getPatternParam(pattern, "protocol"),
hostname: getPatternParam(pattern, "hostname"),
pathname: getPatternParam(pattern, "pathname"),
port: getPatternParam(pattern, "port"),
search: getPatternParam(pattern, "search")
}]
});
return new _WebDriverInterception(pattern, interception.intercept, filterOptions, browser, isCollectingNetworkData, scope.sessionKey, scope.contexts);
}
#emit(event, args) {
if (!this.#eventHandler.has(event)) {
return;
}
const handlers = this.#eventHandler.get(event) || [];
for (const handler of handlers) {
handler(args);
}
}
#addEventHandler(event, handler) {
if (!this.#eventHandler.has(event)) {
this.#eventHandler.set(event, []);
}
const handlers = this.#eventHandler.get(event);
handlers?.push(handler);
}
/**
* Release a paused request, unless another mock listening to the same event
* has already released it.
*
* `handling` says whether this mock is the one the request belongs to. A mock
* that only releases because the request does *not* match it must not take the
* release away from a mock that does. Listeners of one event run synchronously,
* so it waits until that dispatch finishes. A `postData` match also has to
* read the body first, and that decision is recorded on the event before the
* listener yields. The declining mock waits for those decisions too, then
* releases only if nobody claimed the request.
*/
#release(event, handling, call2) {
if (!isContested(event)) {
return call2();
}
if (handling) {
return claim(event) ? call2() : void 0;
}
if (!isClaimed(event)) {
queueMicrotask(() => {
const release = () => {
if (claim(event)) {
call2();
}
};
const pending = pendingDecisions(event);
if (pending.length === 0) {
release();
return;
}
void Promise.all(pending).then(release);
});
}
return void 0;
}
#handleBeforeRequestSent(request) {
if (this.#restored) {
if (request.intercepts?.includes(this.#mockId)) {
return this.#release(request, false, () => this.#browser.networkContinueRequest({
request: request.request.request
}).catch(() => {
}));
}
return;
}
if (!this.#isRequestMatching(request)) {
if (request.intercepts?.includes(this.#mockId)) {
return this.#release(request, false, () => this.#browser.networkContinueRequest({
request: request.request.request
}));
}
return;
}
if (this.#filterOptions.postData) {
if (!this.#matchesFilterOptions(request, { includePostData: false })) {
return this.#release(request, false, () => this.#browser.networkContinueRequest({
request: request.request.request
}));
}
const decision = this.#handleBeforeRequestSentWithPostData(request);
if (isContested(request)) {
trackPending(request, decision);
}
return decision;
}
return this.#continueBeforeRequestSent(request);
}
/**
* `fetchResponse: false` answers before a response exists. Callbacks are
* typed to receive one, so give them the status this mock sends by default.
* A callback can still replace that status.
*/
#eventForEarlyResponse(request) {
return {
...request,
response: {
status: 200,
headers: []
}
};
}
async #handleBeforeRequestSentWithPostData(request) {
await this.#populateRequestPostData(request);
return this.#continueBeforeRequestSent(request, { requestFiltersMatch: true });
}
#continueBeforeRequestSent(request, { requestFiltersMatch = false } = {}) {
const matches = requestFiltersMatch ? this.#matchesPostDataFilter(request) : this.#matchesFilterOptions(request);
if (!matches) {
return this.#release(request, false, () => this.#browser.networkContinueRequest({
request: request.request.request
}));
}
const requestId = request.request.request;
this.#emit("request", request);
if (this.#requestOverwrites[0]?.abort) {
const { abort } = this.#requestOverwrites[0].once ? this.#requestOverwrites.shift() || {} : this.#requestOverwrites[0];
if (abort) {
this.#emit("fail", requestId);
return this.#release(request, true, () => this.#withBlockedRequestTracking(
requestId,
this.#browser.networkFailRequest({ request: requestId })
));
}
}
const responseOverwrite = this.#respondOverwrites[0];
if (responseOverwrite?.overwrite && ("fetchResponse" in responseOverwrite.overwrite && responseOverwrite.overwrite.fetchResponse === false || this.#mustAnswerBeforeRequestSent())) {
const { overwrite } = responseOverwrite.once ? this.#respondOverwrites.shift() || {} : responseOverwrite;
if (!overwrite) {
return;
}
this.#emit("overwrite", request);
try {
const responseData = parseOverwrite(
overwrite,
this.#eventForEarlyResponse(request)
);
if (responseData.body) {
this.#overwrittenResponseBodies.set(requestId, responseData.body);
}
this.#requestsRespondedWithoutFetch.add(requestId);
return this.#withBlockedRequestTracking(
requestId,
this.#browser.networkProvideResponse({
request: requestId,
statusCode: 200,
...responseData
}).catch((err) => {
this.#requestsRespondedWithoutFetch.delete(requestId);
return this.#handleNetworkProvideResponseError(err);
})
);
} catch (err) {
this.#requestsRespondedWithoutFetch.delete(requestId);
log10.error(`Failed to apply mock.respond() overwrite: ${err.message}`);
return this.#withBlockedRequestTracking(
requestId,
this.#browser.networkFailRequest({
request: requestId
}).catch(this.#handleNetworkProvideResponseError)
);
}
}
const hasRequestOverwrites = this.#requestOverwrites.length > 0;
if (hasRequestOverwrites) {
const { overwrite } = this.#requestOverwrites[0].once ? this.#requestOverwrites.shift() || {} : this.#requestOverwrites[0];
this.#emit("overwrite", request);
return this.#release(request, true, () => this.#withBlockedRequestTracking(
requestId,
this.#browser.networkContinueRequest({
request: requestId,
...overwrite ? parseOverwrite(overwrite, request) : {}
})
));
}
this.#emit("continue", requestId);
return this.#release(request, true, () => this.#withBlockedRequestTracking(
requestId,
this.#browser.networkContinueRequest({
request: requestId
})
));
}
#handleResponseStarted(request) {
if (this.#restored) {
if (request.intercepts?.includes(this.#mockId)) {
return this.#release(request, false, () => this.#browser.networkProvideResponse({
request: request.request.request
}).catch(() => {
}));
}
return;
}
const isHandledByThisMock = request.intercepts?.includes(this.#mockId);
const urlMatches = this.#pattern && this.#pattern.test(request.request.url);
if (!urlMatches) {
if (isHandledByThisMock && request.isBlocked) {
return this.#release(request, false, () => this.#browser.networkProvideResponse({
request: request.request.request
}).catch(this.#handleNetworkProvideResponseError));
}
return;
}
this.#attachPostData(request);
const filterMatches = this.#matchesFilterOptions(request);
if (filterMatches) {
this.#calls.push(request);
}
if (this.#requestsRespondedWithoutFetch.delete(request.request.request)) {
return;
}
if (!request.isBlocked) {
return;
}
if (!filterMatches) {
this.#emit("continue", request.request.request);
return this.#release(request, false, () => this.#browser.networkProvideResponse({
request: request.request.request
}).catch(this.#handleNetworkProvideResponseError));
}
const requestId = request.request.request;
if (this.#respondOverwrites.length === 0 || !this.#respondOverwrites[0].overwrite) {
this.#emit("continue", requestId);
return this.#release(request, true, () => this.#withBlockedRequestTracking(
requestId,
this.#browser.networkProvideResponse({
request: requestId
}).catch(this.#handleNetworkProvideResponseError)
));
}
const { overwrite } = this.#respondOverwrites[0].once ? this.#respondOverwrites.shift() || {} : this.#respondOverwrites[0];
if (overwrite) {
this.#emit("overwrite", request);
try {
const responseData = parseOverwrite(overwrite, request);
if (responseData.body) {
this.#overwrittenResponseBodies.set(requestId, responseData.body);
}
return this.#release(request, true, () => this.#withBlockedRequestTracking(
requestId,
this.#browser.networkProvideResponse({
request: requestId,
...responseData
}).catch((err) => this.#handleUnsupportedOverwrite(err, requestId))
));
} catch (err) {
log10.error(`Failed to apply mock.respond() overwrite: ${err.message}`);
return this.#release(request, true, () => this.#withBlockedRequestTracking(
requestId,
this.#browser.networkFailRequest({
request: requestId
}).catch(this.#handleNetworkProvideResponseError)
));
}
}
this.#emit("continue", requestId);
return this.#release(request, true, () => this.#withBlockedRequestTracking(
requestId,
this.#browser.networkProvideResponse({
request: requestId
}).catch(this.#handleNetworkProvideResponseError)
));
}
async #handleResponseCompleted(response) {
if (this.#restored) {
return;
}
if (this.#browser.options.maxSpyCollectedBodySize === 0 || !this.#pattern.test(response.request.url) || !this.#matchesFilterOptions(response, { includePostData: false })) {
return;
}
const requestWithPostData = await this.#populateRequestPostData(response);
if (!this.#matchesPostDataFilter(requestWithPostData)) {
this.#requestPostData.delete(response.request.request);
return;
}
const call2 = this.#getCall(response.request.request);
if (!call2) {
return;
}
this.#attachPostData(call2);
this.#hasOneBodyReadStarted = true;
try {
const { bytes } = await this.#browser.networkGetData({
request: response.request.request,
dataType: "response"
});
if (bytes) {
call2.body = bytes.value;
}
} catch (err) {
log10.debug(`Failed to get response body for ${response.request.request}: ${err.message}`);
} finally {
if (this.#calls.includes(call2)) {
this.#hasOneResponseCollected = true;
}
this.#requestPostData.delete(response.request.request);
}
}
/**
* It appears that the networkProvideResponse method may throw an "no such request" error even though the request
* is marked as "blocked", in these cases we can safely ignore the error.
* @param err Bidi message error
*/
/**
* Firefox only takes a response body in `network.provideResponse` while
* the request is blocked in `beforeRequestSent`, not once the response
* started (https://wpt.fyi/results/webdriver/tests/bidi/network/provide_response).
* Answer there, like `fetchResponse: false`, unless the mock filters on the
* response, which needs the backend.
*/
#mustAnswerBeforeRequestSent() {
if (!this.#browser.isFirefox) {
return false;
}
if (this.#filterOptions.statusCode !== void 0 || this.#filterOptions.responseHeaders !== void 0) {
return false;
}
if (!this.#warnedAboutEarlyAnswer) {
this.#warnedAboutEarlyAnswer = true;
log10.warn(
"Firefox can not replace a response body after the request was sent. mock.respond() answers before the request is sent instead, as with `fetchResponse: false`."
);
}
return true;
}
/**
* A browser that can't replace the body once the response started rejects
* `network.provideResponse`, which would leave the request blocked. Let the
* real response through and say so, instead of hanging the test.
*/
#handleUnsupportedOverwrite(err, requestId) {
if (!err.message.includes("unsupported operation")) {
return this.#handleNetworkProvideResponseError(err);
}
log10.warn(
`mock.respond() could not replace the response of request ${requestId}: ${err.message}. The page received the real response. Use \`fetchResponse: false\` to answer before the request is sent.`
);
return this.#browser.networkProvideResponse({ request: requestId }).catch(this.#handleNetworkProvideResponseError);
}
#handleNetworkProvideResponseError(err) {
if (err.message.endsWith("no such request")) {
return;
}
throw err;
}
/**
* Read a cached binary overwrite as a Buffer in Node.js or Uint8Array in browsers.
*/
getBinaryResponse(requestId) {
const body = this.#overwrittenResponseBodies.get(requestId);
if (body?.type !== "base64") {
return null;
}
if (/[^A-Za-z0-9+/=\s]/.test(body.value)) {
log10.warn(`Invalid base64 data for request ${requestId}`);
return null;
}
return decodeBase64(body.value);
}
#attachPostData(request) {
const requestWithPostData = request;
const postData = this.#requestPostData.get(request.request.request);
if (postData !== void 0) {
requestWithPostData.postData = postData;
}
return requestWithPostData;
}
async #populateRequestPostData(request) {
const requestWithPostData = this.#attachPostData(request);
if (requestWithPostData.postData !== void 0 || this.#browser.options.maxSpyCollectedBodySize === 0) {
return requestWithPostData;
}
try {
const { bytes } = await this.#browser.networkGetData({
request: request.request.request,
dataType: "request"
});
if (bytes) {
requestWithPostData.postData = bytes.value;
this.#requestPostData.set(request.request.request, bytes.value);
}
} catch (err) {
log10.debug(`Failed to get request body for ${request.request.request}: ${err.message}`);
}
return requestWithPostData;
}
/**
* Simulate a responseStarted event for testing purposes
* @param request NetworkResponseCompletedParameters to simulate
*/
simulateResponseStarted(request) {
try {
this.#handleResponseStarted(request);
} catch (e) {
console.log("DEBUG: Error in simulateResponseStarted:", e);
throw e;
}
}
debugResponseBodies() {
return this.#overwrittenResponseBodies;
}
#getCall(requestId) {
for (let index = this.#calls.length - 1; index >= 0; index--) {
const call2 = this.#calls[index];
if (call2.request.request === requestId) {
return call2;
}
}
}
#isRequestMatching(request) {
const matches = this.#pattern && this.#pattern.test(request.request.url);
return request.isBlocked && matches;
}
#matchesPostDataFilter(request) {
if (!this.#filterOptions.postData) {
return true;
}
return typeof this.#filterOptions.postData === "function" ? this.#filterOptions.postData(request.postData) : request.postData === this.#filterOptions.postData;
}
#matchesFilterOptions(request, { includePostData = true } = {}) {
let isRequestMatching = true;
if (isRequestMatching && this.#filterOptions.method) {
isRequestMatching = typeof this.#filterOptions.method === "function" ? this.#filterOptions.method(request.request.method) : this.#filterOptions.method.toLowerCase() === request.request.method.toLowerCase();
}
if (isRequestMatching && this.#filterOptions.requestHeaders) {
isRequestMatching = typeof this.#filterOptions.requestHeaders === "function" ? this.#filterOptions.requestHeaders(request.request.headers.reduce((acc, { name, value }) => {
acc[name] = decodeHeader(value);
return acc;
}, {})) : Object.entries(this.#filterOptions.requestHeaders).every(([key, value]) => {
const header = request.request.headers.find(({ name }) => name === key);
if (!header) {
return false;
}
return decodeHeader(header.value) === value;
});
}
if (isRequestMatching && includePostData) {
isRequestMatching = this.#matchesPostDataFilter(request);
}
if (isRequestMatching && this.#filterOptions.responseHeaders && "response" in request) {
isRequestMatching = typeof this.#filterOptions.responseHeaders === "function" ? this.#filterOptions.responseHeaders(request.response.headers.reduce((acc, { name, value }) => {
acc[name] = decodeHeader(value);
return acc;
}, {})) : Object.entries(this.#filterOptions.responseHeaders).every(([key, value]) => {
const header = request.response.headers.find(({ name }) => name === key);
if (!header) {
return false;
}
return decodeHeader(header.value) === value;
});
}
if (isRequestMatching && this.#filterOptions.statusCode && "response" in request) {
isRequestMatching = typeof this.#filterOptions.statusCode === "function" ? this.#filterOptions.statusCode(request.response.status) : this.#filterOptions.statusCode === request.response.status;
}
return isRequestMatching;
}
#setOverwrite = (overwriteProp, { overwrite, abort, once }) => {
return once ? [
...overwriteProp.filter(({ once: once2 }) => once2),
{ overwrite, abort, once }
] : [{ overwrite, abort }];
};
/**
* allows access to all requests made with given pattern
*/
get calls() {
return this.#calls;
}
get hasAtLeastOneResponseReceived() {
const isResponseReceived = this.calls && this.calls.length > 0;
return isResponseReceived && (!this.#isCollectingNetworkData || this.#hasOneResponseCollected);
}
/**
* Resets all information stored in the `mock.calls` set.
*/
clear() {
this.#calls = [];
this.#overwrittenResponseBodies.clear();
this.#requestPostData.clear();
this.#hasOneResponseCollected = false;
this.#hasOneBodyReadStarted = false;
this.#blockedRequests.clear();
this.#requestsRespondedWithoutFetch.clear();
return this;
}
/**
* Does what `mock.clear()` does and makes removes custom request overrides
* and response overwrites
*/
reset() {
this.clear();
this.#respondOverwrites = [];
this.#requestOverwrites = [];
return this;
}
/**
* Does everything that `mock.reset()` does, and also
* removes any mocked return values or implementations.
* Restored mock does not emit events and could not mock responses
*/
async restore() {
const blockedRequestIds = Array.from(this.#blockedRequests);
if (this.#onContextDestroyed) {
this.#browser.off("browsingContext.contextDestroyed", this.#onContextDestroyed);
this.#onContextDestroyed = void 0;
}
this.reset();
this.#respondOverwrites = [];
const handle = this.#sessionKey ?? await this.#browser.getWindowHandle();
log10.trace(`Restoring mock for ${handle}`);
SESSION_MOCKS[handle]?.delete(this);
for (const requestId of blockedRequestIds) {
try {
await this.#browser.networkContinueRequest({ request: requestId });
} catch (err) {
log10.trace(`Failed to continue in-flight request ${requestId} during restore:`, err);
}
}
this.#blockedRequests.clear();
if (this.#mockId) {
await this.#browser.networkRemoveIntercept({ intercept: this.#mockId });
}
this.#restored = true;
return this;
}
/**
* Always use request modification for the next request done by the browser.
* @param payload payload to overwrite the request
* @param once apply overwrite only once for the next request
* @returns this instance to chain commands
*/
request(overwrite, once) {
this.#ensureNotRestored();
this.#requestOverwrites = this.#setOverwrite(this.#requestOverwrites, { overwrite, once });
return this;
}
/**
* alias for `mock.request(…, true)`
*/
requestOnce(payload) {
return this.request(payload, true);
}
/**
* Always respond with same overwrite
* @param {*} payload payload to overwrite the response
* @param {*} params additional respond parameters to overwrite
* @param {boolean} once apply overwrite only once for the next request
* @returns this instance to chain commands
*/
respond(payload, params = {}, once) {
this.#ensureNotRestored();
this.#assertCanSkipFetch(params);
const body = typeof payload === "function" ? (request) => toNetworkBody(payload(request)) : toNetworkBody(payload);
const overwrite = { body, ...params };
this.#respondOverwrites = this.#setOverwrite(this.#respondOverwrites, { overwrite, once });
return this;
}
/**
* alias for `mock.respond(…, true)`
*/
respondOnce(payload, params = {}) {
return this.respond(payload, params, true);
}
/**
* Abort the request with an error code
* @param {string} errorReason error code of the response
* @param {boolean} once if request should be aborted only once for the next request
*/
abort(once) {
this.#ensureNotRestored();
this.#requestOverwrites = this.#setOverwrite(this.#requestOverwrites, { abort: true, once });
return this;
}
/**
* alias for `mock.abort(true)`
*/
abortOnce() {
return this.abort(true);
}
/**
* Redirect request to another URL
* @param {string} redirectUrl URL to redirect to
* @param {boolean} sticky if request should be redirected for all following requests
*/
redirect(redirectUrl, once) {
this.#ensureNotRestored();
const requestWith = { url: redirectUrl };
this.request(requestWith, once);
return this;
}
/**
* alias for `mock.redirect(…, true)`
*/
redirectOnce(redirectUrl) {
return this.redirect(redirectUrl, true);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
on(event, callback) {
this.#addEventHandler(event, callback);
return this;
}
#ensureNotRestored() {
if (this.#restored) {
throw new Error("This can't be done on restored mock");
}
}
/**
* A status or response-header filter matches the backend response.
* `fetchResponse: false` never calls the backend, so the filter cannot
* be decided and must not be silently ignored or bypassed by fetching.
*/
#assertCanSkipFetch(params) {
if (params.fetchResponse !== false) {
return;
}
const filtersOnResponse = this.#filterOptions.statusCode !== void 0 || this.#filterOptions.responseHeaders !== void 0;
if (!filtersOnResponse) {
return;
}
throw new Error(
"fetchResponse: false cannot be used when the mock filters on statusCode or responseHeaders. Those filters need the backend response, and this option does not call the backend."
);
}
/**
* Keep the request id tracked until the BiDi call settles so restore()
* can still continue it if cleanup races an in-flight interception.
*/
#withBlockedRequestTracking(requestId, networkCall) {
this.#blockedRequests.add(requestId);
return Promise.resolve(networkCall).finally(() => {
this.#blockedRequests.delete(requestId);
});
}
isSameDefinition(url2, filterOptions = {}) {
const pattern = parseUrlPattern(url2);
return this.#patternId === getPatternId(pattern) && areFilterOptionsEqual(this.#filterOptions, filterOptions);
}
waitForResponse({
timeout = this.#browser.options.waitforTimeout,
interval = this.#browser.options.waitforInterval,
timeoutMsg
} = {}) {
if (typeof timeout !== "number") {
timeout = this.#browser.options.waitforTimeout;
}
if (typeof interval !== "number") {
interval = this.#browser.options.waitforInterval;
}
const isResponseReceived = () => this.hasAtLeastOneResponseReceived;
const timer = new Timer_default(interval, timeout, isResponseReceived, true);
return this.#browser.call(() => timer.catch((e) => {
if (e.message === "timeout") {
if (this.#hasOneBodyReadStarted) {
log10.warn(`waitForResponse: a response was received, but its body was not collected within ${timeout}ms, continuing without it`);
return true;
}
if (typeof timeoutMsg === "string") {
throw new Error(timeoutMsg);
}
throw new Error(`waitForResponse timed out after ${timeout}ms`);
}
throw new Error(`waitForResponse failed with the following reason: ${e && e.message || e}`);
}));
}
};
setWdioKind(WebDriverInterception.prototype, "mock");
function parseUrlPattern(url2) {
if (typeof url2 === "object") {
return url2;
}
url2 = url2.replace(/(?<!\\)\*{2,}/g, "*");
if (url2.startsWith("http")) {
return new URLPattern(url2);
}
return new URLPattern({
pathname: url2
});
}
function getPatternId(pattern) {
return `${pattern.protocol}|${pattern.username}|${pattern.password}|${pattern.hostname}|${pattern.port}|${pattern.pathname}|${pattern.search}|${pattern.hash}`;
}
function areFilterOptionsEqual(a = {}, b = {}) {
const keys2 = ["method", "requestHeaders", "responseHeaders", "statusCode", "postData"];
return keys2.every((key) => isFilterOptionValueEqual(a[key], b[key]));
}
function isFilterOptionValueEqual(a, b) {
if (a === b) {
return true;
}
if (typeof a === "function" || typeof b === "function") {
return false;
}
if (a && b && typeof a === "object" && typeof b === "object") {
const aKeys = Object.keys(a).sort();
const bKeys = Object.keys(b).sort();
if (aKeys.length !== bKeys.length) {
return false;
}
return aKeys.every((k, i) => k === bKeys[i] && a[k] === b[k]);
}
return false;
}
// src/commands/browser/mock.ts
var SESSION_MOCKS = {};
async function mock(url2, filterOptions) {
if (!this.isBidi) {
throw new Error("Mocking is only supported when running tests using WebDriver Bidi");
}
if (isBrowsingContext(this)) {
assertTopLevel(this, "mock");
}
const browser = getBrowserObject12(this);
const context = await contextIdOf(this);
if (!SESSION_MOCKS[context]) {
SESSION_MOCKS[context] = /* @__PURE__ */ new Set();
}
const normalizedFilterOptions = filterOptions || {};
for (const existingMock of Array.from(SESSION_MOCKS[context])) {
if (!existingMock.isSameDefinition(url2, normalizedFilterOptions)) {
continue;
}
await existingMock.restore();
SESSION_MOCKS[context].delete(existingMock);
}
const networkInterception = await WebDriverInterception.initiate(url2, normalizedFilterOptions, browser, {
contexts: isBrowsingContext(this) ? [context] : void 0,
sessionKey: context
});
SESSION_MOCKS[context].add(networkInterception);
return networkInterception;
}
// src/commands/browser/mockClearAll.ts
import logger11 from "@wdio/logger";
var log11 = logger11("webdriverio:mockClearAll");
async function mockClearAll() {
for (const [handle, mocks] of Object.entries(SESSION_MOCKS)) {
log11.trace(`Clearing mocks for ${handle}`);
for (const mock2 of mocks) {
mock2.clear();
}
}
}
// src/commands/browser/mockRestoreAll.ts
import logger12 from "@wdio/logger";
var log12 = logger12("webdriverio:mockRestoreAll");
async function mockRestoreAll() {
for (const [handle, mocks] of Object.entries(SESSION_MOCKS)) {
log12.trace(`Clearing mocks for ${handle}`);
for (const mock2 of mocks) {
await mock2.restore();
}
}
}
// src/commands/browser/newWindow.ts
import { sleep } from "@wdio/utils";
import newWindowHelper from "./scripts/newWindow.js";
// src/browsingContext.ts
import { resolveCustomCommandOptions, setWdioKind as setWdioKind3, webdriverMonad as webdriverMonad2, wrapCommand as wrapCommand2 } from "@wdio/utils";
// src/commands/browser/react$.ts
import { waitToLoadReact, react$ as react$Script } from "./scripts/react.js";
async function react$(selector, { props = {}, state = {} } = {}) {
await this.execute(reactScript);
await this.execute(waitToLoadReact);
const res = await this.execute(
react$Script,
selector,
props,
state
);
return getElement.call(this, selector, res, { isReactElement: true, strict: false, reactOptions: { props, state } });
}
// src/commands/browser/react$$.ts
import { waitToLoadReact as waitToLoadReact2, react$$ as react$$Script } from "./scripts/react.js";
function react$$(selector, { props = {}, state = {} } = {}) {
return ElementArray.fromAsyncCallback(async () => {
await this.execute(reactScript);
await this.execute(waitToLoadReact2);
const res = await this.execute(
react$$Script,
selector,
props,
state
);
return getElements.call(this, selector, res, { isReactElement: true, reactOptions: { props, state } });
}, {
selector,
foundWith: "react$$",
parent: this,
/**
* the arguments after the selector, so that
* `parent[foundWith](selector, ...props)` runs the same query again
*/
props: [{ props, state }]
});
}
// src/commands/browser/scroll.ts
import logger13 from "@wdio/logger";
var log13 = logger13("webdriverio");
function scroll(x = 0, y = 0) {
if (!x && !y) {
log13.warn('"scroll" command was called with no parameters, skipping execution');
return Promise.resolve();
}
if (this.isMobile) {
return this.execute((x2, y2) => window.scrollBy(x2, y2), x, y);
}
return this.action("wheel").scroll({
deltaX: x,
deltaY: y,
duration: 0
}).perform();
}
// src/commands/browser/saveScreenshot.ts
async function saveScreenshot2(filepath, options) {
return environment.value.saveScreenshot.call(this, filepath, options);
}
// src/commands/browser/savePDF.ts
async function savePDF2(filepath, options) {
return environment.value.savePDF.call(this, filepath, options);
}
// src/commands/browser/setCookies.ts
import logger14 from "@wdio/logger";
var log14 = logger14("webdriverio");
async function setCookies(cookieObjs) {
const cookieObjsList = !Array.isArray(cookieObjs) ? [cookieObjs] : cookieObjs;
if (cookieObjsList.some((obj) => typeof obj !== "object")) {
throw new Error("Invalid input (see https://webdriver.io/docs/api/browser/setCookies for documentation)");
}
if (!this.isBidi) {
const classic = isBrowsingContext(this) ? this.browser : this;
await Promise.all(cookieObjsList.map((cookieObj) => classic.addCookie(cookieObj)));
return;
}
const browser = isBrowsingContext(this) ? this.browser : this;
if (isBrowsingContext(this)) {
const origin = this.url.startsWith("http") ? this.url : await this.getUrl();
const url3 = new URL(origin);
await Promise.all(cookieObjsList.map((cookie) => browser.storageSetCookie({
cookie: {
...cookie,
domain: cookie.domain || url3.hostname,
value: {
type: "string",
value: cookie.value
}
},
partition: {
type: "context",
context: this.contextId
}
})));
return;
}
let url2;
try {
url2 = new URL(await this.getUrl());
if (url2.origin === "null") {
await Promise.all(cookieObjsList.map((cookieObj) => this.addCookie(cookieObj)));
return;
}
} catch {
await Promise.all(cookieObjsList.map((cookieObj) => this.addCookie(cookieObj)));
return;
}
try {
await Promise.all(cookieObjsList.map((cookie) => this.storageSetCookie({
cookie: {
...cookie,
domain: cookie.domain || url2.hostname,
value: {
type: "string",
value: cookie.value
}
},
partition: {
type: "storageKey",
sourceOrigin: url2.origin
}
})));
} catch (err) {
log14.warn(`BiDi setCookies failed, falling back to classic: ${err.message}`);
await Promise.all(cookieObjsList.map((cookieObj) => this.addCookie(cookieObj)));
}
return;
}
// src/commands/browser/setViewport.ts
import { getBrowserObject as getBrowserObject13 } from "@wdio/utils";
var minWindowSize = 0;
var maxWindowSize = Number.MAX_SAFE_INTEGER;
async function setViewport(options) {
if (typeof options.width !== "number" || typeof options.height !== "number") {
throw new Error("setViewport expects width and height of type number");
}
if (options.width < minWindowSize || options.width > maxWindowSize || options.height < minWindowSize || options.height > maxWindowSize) {
throw new Error("setViewport expects width and height to be a number in the 0 to 2^31 \u2212 1 range");
}
if (options.devicePixelRatio && (typeof options.devicePixelRatio !== "number" || options.devicePixelRatio < 0)) {
throw new Error("setViewport expects devicePixelRatio to be a number in the 0 to 2^31 \u2212 1 range");
}
if (isBrowsingContext(this)) {
assertTopLevel(this, "setViewport");
}
const browser = getBrowserObject13(this);
const context = await contextIdOf(this);
const devicePixelRatio = options.devicePixelRatio || 1;
await browser.browsingContextSetViewport({
context,
devicePixelRatio,
viewport: {
width: options.width,
height: options.height
}
});
rememberOverride(browser, context, {
viewport: {
width: options.width,
height: options.height,
devicePixelRatio
}
});
}
// src/commands/browser/restore.ts
async function restore(scopes2) {
const scopeArray = !scopes2 || Array.isArray(scopes2) ? scopes2 : [scopes2];
const instanceRestoreFunctions = restoreFunctions.get(this);
if (!instanceRestoreFunctions) {
return;
}
await Promise.all(Array.from(instanceRestoreFunctions.entries()).map(async ([scope, restoreFunctionsList]) => {
if (!scopeArray || scopeArray.includes(scope)) {
await Promise.all(restoreFunctionsList.map((fn) => fn()));
instanceRestoreFunctions.set(scope, []);
}
}));
}
// src/commands/browser/waitUntil.ts
import { getBrowserObject as getBrowserObject14 } from "@wdio/utils";
function waitUntil(condition, {
timeout = this.options.waitforTimeout,
interval = this.options.waitforInterval,
timeoutMsg
} = {}) {
if (typeof condition !== "function") {
throw new Error("Condition is not a function");
}
if (typeof timeout !== "number") {
timeout = this.options.waitforTimeout;
}
if (typeof interval !== "number") {
interval = this.options.waitforInterval;
}
const browser = getBrowserObject14(this);
const abort = new AbortController();
const abortOnSessionEnd = (result) => {
if (result.command === "deleteSession") {
abort.abort();
}
};
browser.on("result", abortOnSessionEnd);
const fn = condition.bind(this);
const timer = new Timer_default(interval, timeout, fn, true, abort.signal);
return timer.catch((e) => {
if (e.message === "timeout") {
if (typeof timeoutMsg === "function") {
throw new Error(timeoutMsg());
}
if (typeof timeoutMsg === "string") {
throw new Error(timeoutMsg);
}
throw new Error(`waitUntil condition timed out after ${timeout}ms`);
}
const err = new Error(`waitUntil condition failed with the following reason: ${e && e.message || e}`);
const origStack = e.stack;
if (!origStack || !err.stack) {
throw err;
}
const [errMsg, ...waitUntilErrorStackLines] = err.stack.split("\n");
err.stack = [
errMsg,
...origStack.split("\n").slice(1),
" ---",
...waitUntilErrorStackLines
].filter((errorLine) => !errorLine.includes("/node_modules/webdriverio/") && !errorLine.includes("/node_modules/@wdio/")).join("\n");
throw err;
}).finally(() => {
browser.off("result", abortOnSessionEnd);
});
}
// src/commands/browser/pause.ts
function pause(milliseconds = 1e3) {
return new Promise((resolve) => setTimeout(resolve, milliseconds));
}
// src/commands/browsingContext/frame.ts
async function frame(target) {
if (typeof target === "function") {
return frameByPredicate(this, target);
}
if (isFrameQuery(target)) {
return frameByQuery(this, target);
}
if (target && typeof target === "object") {
const candidate = target;
const element = typeof candidate.getElement === "function" ? await candidate.getElement() : candidate;
return frameFromElement(this, element);
}
if (typeof target === "string") {
if (target.includes("://") || target.startsWith("/") || /^[A-Fa-f0-9-]{16,}$/.test(target)) {
const mayBeXPath = target.startsWith("/");
const waited = await this.waitUntil(
async () => await frameFromTree(this, target) || mayBeXPath && await selectorExists(this, target) || false,
{
timeout: this.options.waitforTimeout,
interval: this.options.waitforInterval,
timeoutMsg: `Could not find a frame for "${target}"`
}
).catch(() => void 0);
if (waited && waited !== true) {
return waited;
}
} else {
const element2 = await existingElement(this, target);
if (element2 && ["iframe", "frame"].includes(await element2.getTagName())) {
return frameFromElement(this, element2);
}
}
const fromTree = await frameFromTree(this, target);
if (fromTree) {
return fromTree;
}
const element = await this.$(target);
return frameFromElement(this, element);
}
throw new Error("`frame` expects a selector, an element, a url, a context id, or a function");
}
function isFrameQuery(target) {
if (!target || typeof target !== "object" || Object.getPrototypeOf(target) !== Object.prototype) {
return false;
}
const keys2 = Object.keys(target);
return keys2.length === 1 && ["selector", "url", "id"].includes(keys2[0]);
}
async function frameByQuery(caller, query) {
if ("selector" in query) {
if (typeof query.selector !== "string") {
throw new Error("`frame({ selector })` expects a string selector");
}
return frameFromElement(caller, await caller.$(query.selector));
}
let matches;
let description;
if ("id" in query) {
if (typeof query.id !== "string") {
throw new Error("`frame({ id })` expects a browsing context id string");
}
matches = (node) => node.context === query.id;
description = `id "${query.id}"`;
} else {
const { url: url2 } = query;
if (typeof url2 !== "string" && !(url2 instanceof RegExp)) {
throw new Error("`frame({ url })` expects a string or a RegExp");
}
matches = typeof url2 === "string" ? (node) => node.url === url2 : (node) => {
url2.lastIndex = 0;
return url2.test(node.url);
};
description = `url ${typeof url2 === "string" ? `"${url2}"` : String(url2)}`;
}
const found = await caller.waitUntil(
async () => await findInTree(caller, await childNodes(caller), matches) || false,
{
timeout: caller.options.waitforTimeout,
interval: caller.options.waitforInterval,
timeoutMsg: `Could not find a frame with ${description}`
}
).catch(() => void 0);
if (!found) {
throw new Error(`Could not find a frame with ${description}`);
}
return found;
}
async function frameFromElement(parent, element) {
await element.waitForExist({
timeoutMsg: `Can't find a frame with selector ${element.selector} because it doesn't exist`
});
const result = await parent.execute(
(iframe) => iframe.contentWindow,
element
);
const contextId = result && typeof result === "object" ? result.context : void 0;
if (!contextId) {
throw new Error("The element is not a frame with a browsing context");
}
return getBrowsingContext(parent.browser, contextId, {
isFrame: true,
parent,
url: ""
});
}
async function existingElement(caller, selector) {
try {
const element = await caller.$(selector);
return await element.isExisting() ? element : void 0;
} catch (err) {
if (err instanceof StrictSelectorError) {
throw err;
}
return void 0;
}
}
async function selectorExists(caller, selector) {
try {
const element = await caller.$(selector);
return await element.isExisting();
} catch {
return false;
}
}
async function frameFromTree(caller, target) {
const children = await childNodes(caller);
return findInTree(caller, children, (node) => node.context === target || node.url === target || node.url.includes(target), (node) => node.context === target ? 0 : node.url === target ? 1 : 2);
}
async function frameByPredicate(caller, predicate) {
const browser = caller.browser;
const functionDeclaration = createBidiFunctionDeclaration(predicate);
const match = await caller.waitUntil(async () => {
const children = await childNodes(caller);
return await findInTree(caller, children, async (node) => {
const result = await browser.scriptCallFunction({
functionDeclaration,
awaitPromise: true,
arguments: [
LocalValue.getArgument({ context: node.context, url: node.url })
],
target: { context: node.context }
}).catch(() => void 0);
return Boolean(result && result.type === "success" && result.result.type === "boolean" && result.result.value);
}) || false;
}, {
timeout: caller.options.waitforTimeout,
interval: caller.options.waitforInterval,
timeoutMsg: "No frame matched the given function"
}).catch(() => void 0);
if (!match) {
throw new Error("No frame matched the given function");
}
return match;
}
async function findInTree(parent, nodes, matches, rank) {
let best;
await walk(parent, nodes);
return best?.context;
async function walk(owner, level) {
for (const node of level) {
const context = getBrowsingContext(owner.browser, node.context, {
isFrame: true,
parent: owner,
url: node.url
});
if (await matches(node)) {
const score = rank ? rank(node) : 0;
if (!best || score < best.rank) {
best = { context, rank: score };
}
if (!rank) {
return true;
}
}
if (node.children?.length && await walk(context, node.children)) {
return true;
}
}
return false;
}
}
async function childNodes(parent) {
const { contexts } = await parent.browser.browsingContextGetTree({
root: parent.contextId
});
return contexts[0]?.children ?? [];
}
// src/commands/browser/navigateInContext.ts
import { setWdioKind as setWdioKind2 } from "@wdio/utils";
// src/session/networkManager.ts
function getNetworkManager(browser) {
return SessionManager.getSessionManager(browser, NetworkManager);
}
var UNKNOWN_NAVIGATION_ID = "UNKNOWN_NAVIGATION_ID";
var SUPPORTED_NAVIGATION_PROTOCOLS = ["http", "https", "data", "file"];
var NetworkManager = class _NetworkManager extends SessionManager {
#browser;
#initialize;
#requests = /* @__PURE__ */ new Map();
#lastNetworkId;
#navigationStartedListener = this.#navigationStarted.bind(this);
#responseCompletedListener = this.#responseCompleted.bind(this);
#beforeRequestSentListener = this.#beforeRequestSent.bind(this);
#fetchErrorListener = this.#fetchError.bind(this);
constructor(browser) {
super(browser, _NetworkManager.name);
this.#browser = browser;
if (!this.isEnabled()) {
this.#initialize = Promise.resolve(true);
return;
}
this.#initialize = this.#browser.sessionSubscribe({
events: [
"browsingContext.navigationStarted",
"browsingContext.fragmentNavigated",
"network.responseCompleted",
"network.beforeRequestSent",
"network.fetchError"
]
}).then(() => true, () => false);
this.#browser.on("browsingContext.navigationStarted", this.#navigationStartedListener);
this.#browser.on("browsingContext.fragmentNavigated", this.#navigationStartedListener);
this.#browser.on("network.responseCompleted", this.#responseCompletedListener);
this.#browser.on("network.beforeRequestSent", this.#beforeRequestSentListener);
this.#browser.on("network.fetchError", this.#fetchErrorListener);
}
removeListeners() {
super.removeListeners();
this.#browser.off("browsingContext.navigationStarted", this.#navigationStartedListener);
this.#browser.off("browsingContext.fragmentNavigated", this.#navigationStartedListener);
this.#browser.off("network.responseCompleted", this.#responseCompletedListener);
this.#browser.off("network.beforeRequestSent", this.#beforeRequestSentListener);
this.#browser.off("network.fetchError", this.#fetchErrorListener);
}
async initialize() {
return this.#initialize;
}
#beforeRequestSent(log29) {
if (log29.navigation) {
return;
}
const request = this.#findRootRequest(log29.navigation);
if (!request) {
return;
}
const { request: id, headers, cookies, url: url2 } = log29.request;
request.children?.push({
id,
url: url2,
headers: headerListToObject(headers),
cookies: cookies.map((cookie) => ({
name: cookie.name,
value: cookie.value.type === "string" ? cookie.value.value : atob(cookie.value.value),
domain: cookie.domain,
path: cookie.path,
size: cookie.size,
httpOnly: cookie.httpOnly,
secure: cookie.secure,
sameSite: cookie.sameSite,
expiry: cookie.expiry
})),
timestamp: log29.timestamp
});
}
#navigationStarted(log29) {
if (
/**
* we need a navigation id to identify the request
*/
!log29.navigation || /**
* ignore urls that users wouldn't navigate to
*/
!SUPPORTED_NAVIGATION_PROTOCOLS.some((protocol) => log29.url.startsWith(protocol))
) {
if (log29.navigation === null && log29.url === "") {
this.#lastNetworkId = UNKNOWN_NAVIGATION_ID;
return this.#requests.set(UNKNOWN_NAVIGATION_ID, {
url: "",
headers: {},
timestamp: log29.timestamp,
redirectChain: [],
children: []
});
}
return;
}
this.#lastNetworkId = log29.navigation;
this.#requests.set(log29.navigation, {
url: log29.url,
headers: {},
timestamp: log29.timestamp,
navigation: log29.navigation,
redirectChain: [],
children: []
});
}
#fetchError(log29) {
const response = this.#findRootRequest(log29.navigation);
if (!response) {
return;
}
const request = response.children?.find((child) => child.id === log29.request.request);
if (!request) {
return;
}
request.error = log29.errorText;
}
#findRootRequest(navigationId) {
const response = this.#requests.get(navigationId || UNKNOWN_NAVIGATION_ID);
if (response) {
return response;
}
const firstRequest = this.#requests.values().next().value;
return this.#lastNetworkId ? this.#requests.get(this.#lastNetworkId) || firstRequest : firstRequest;
}
#responseCompleted(log29) {
const response = this.#findRootRequest(log29.navigation);
if (!response) {
return;
}
if (!response.navigation && response.url === "") {
response.url = log29.request.url;
response.navigation = log29.navigation;
}
if (log29.navigation === response.navigation) {
if (response.url !== log29.response.url) {
response.redirectChain?.push(response.url);
}
response.url = log29.response.url;
const { headers: requestHeaders } = log29.request;
const { fromCache, headers: responseHeaders, mimeType, status } = log29.response;
response.headers = headerListToObject(requestHeaders), response.response = {
fromCache,
headers: headerListToObject(responseHeaders),
mimeType,
status
};
return;
}
const request = response.children?.find((child) => child.id === log29.request.request);
if (!request) {
return;
}
request.response = {
fromCache: log29.response.fromCache,
headers: headerListToObject(log29.response.headers),
mimeType: log29.response.mimeType,
status: log29.response.status
};
response.children?.push(request);
}
getRequestResponseData(navigationId) {
return this.#requests.get(navigationId);
}
/**
* Returns the number of requests that are currently pending.
* @param context browsing context id
* @returns the number of requests that are currently pending
*/
getPendingRequests(navigationId) {
const request = this.#requests.get(navigationId);
if (!request) {
throw new Error(`Couldn't find request for navigation with id ${navigationId}`);
}
const subRequests = request.children || [];
return subRequests.filter((child) => (
/**
* either the request has no response yet
*/
!child.response && /**
* and there was no request error
*/
!child.error
));
}
};
function headerListToObject(headers) {
return headers.reduce((acc, { name, value }) => {
acc[name] = value.value;
return acc;
}, {});
}
// src/commands/browser/navigateInContext.ts
var DEFAULT_NETWORK_IDLE_TIMEOUT = 5e3;
var DEFAULT_WAIT_STATE = "complete";
async function navigateInContext(browser, context, path9, options = {}) {
let resetPreloadScript;
if (options.onBeforeLoad) {
if (typeof options.onBeforeLoad !== "function") {
throw new Error(`Option "onBeforeLoad" must be a function, but received: ${typeof options.onBeforeLoad}`);
}
const target = contextIdValue(context);
const current = contextIdValue(await getContextManager(browser).getCurrentContext());
const fn = options.onBeforeLoad;
if (target === current) {
resetPreloadScript = await browser.addInitScript(fn);
} else {
const register = addInitScript;
resetPreloadScript = await register.call(setWdioKind2({
contextId: target,
browser,
isFrame: false
}, "browsing-context"), fn);
}
}
if (options.auth) {
options.headers = {
...options.headers || {},
Authorization: `Basic ${btoa(`${options.auth.user}:${options.auth.pass}`)}`
};
}
let navigationError;
let request = void 0;
try {
let mock2;
if (options.headers) {
mock2 = await browser.mock(path9);
mock2.requestOnce({ headers: options.headers });
}
const classicPageLoadStrategy = browser.capabilities.pageLoadStrategy === "none" ? "none" : browser.capabilities.pageLoadStrategy === "normal" ? "complete" : browser.capabilities.pageLoadStrategy === "eager" ? "interactive" : void 0;
const wait = options.wait === "networkIdle" ? "complete" : options.wait || classicPageLoadStrategy || DEFAULT_WAIT_STATE;
const navigation = await browser.browsingContextNavigate({
context,
url: path9,
wait
}).catch(async (err) => {
const current = contextIdValue(await getContextManager(browser).getCurrentContext());
if (contextIdValue(context) === current && (err.message.includes("navigation canceled by concurrent navigation") || err.message.includes("failed with error: unknown error") || err.message.includes("no such frame"))) {
return browser.navigateTo(validateUrl(path9));
}
throw err;
});
if (mock2) {
await mock2.restore();
}
const navigationId = navigation?.navigation;
if (!navigationId) {
return void 0;
}
const network = getNetworkManager(browser);
if (options.wait === "networkIdle") {
const timeout = options.timeout || DEFAULT_NETWORK_IDLE_TIMEOUT;
await browser.waitUntil(async () => {
return network.getPendingRequests(navigationId).length === 0;
}, {
timeout,
timeoutMsg: () => {
const pendingRequests = network.getPendingRequests(navigationId);
return `Navigation to '${path9}' timed out after ${timeout}ms with ${pendingRequests.length} (${pendingRequests.map((r) => r.url).join(", ")}) pending requests`;
}
});
}
request = await browser.waitUntil(
() => network.getRequestResponseData(navigationId),
{
interval: 1,
timeoutMsg: `Navigation to '${path9}' timed out as no request payload was received`
}
);
} catch (err) {
navigationError = err;
} finally {
if (resetPreloadScript) {
try {
await resetPreloadScript.remove();
} catch (cleanupError) {
if (!navigationError) {
navigationError = cleanupError;
}
}
}
}
if (navigationError) {
throw navigationError;
}
return request;
}
// src/commands/browsingContext/navigate.ts
async function navigate(path9, options = {}) {
if (typeof path9 !== "string") {
throw new Error('Parameter for "navigate" command needs to be type of string');
}
const browser = this.browser;
if (typeof browser.options.baseUrl === "string" && browser.options.baseUrl) {
path9 = new URL(path9, browser.options.baseUrl).href;
}
const request = await navigateInContext(browser, this.contextId, path9, options);
this.url = path9;
if (request) {
this.request = request;
}
return this;
}
// src/commands/browsingContext/refresh.ts
async function refresh() {
if (this.isFrame) {
return reloadFrame(this);
}
await this.browser.browsingContextReload({
context: this.contextId,
wait: "complete"
});
}
async function reloadFrame(frame2) {
const token = await frame2.execute(() => {
const marker = Math.random().toString(36).slice(2);
window.__wdioReload = marker;
const channel = new MessageChannel();
channel.port1.onmessage = () => location.reload();
channel.port2.postMessage(null);
return marker;
});
const capabilities = frame2.browser.capabilities;
await frame2.waitUntil(async () => {
const state = await frame2.execute(() => ({
marker: window.__wdioReload,
ready: document.readyState
})).catch(() => void 0);
return Boolean(state && state.marker !== token && state.ready === "complete");
}, {
timeout: capabilities.timeouts?.pageLoad ?? 3e5,
interval: 50,
timeoutMsg: "The frame did not finish reloading"
});
}
// src/commands/browsingContext/closeWindow.ts
async function closeWindow() {
assertTopLevel(this, "closeWindow");
await this.browser.browsingContextClose({ context: this.contextId });
}
// src/commands/browsingContext/activate.ts
async function activate() {
assertTopLevel(this, "activate");
await this.browser.browsingContextActivate({ context: this.contextId });
}
// src/commands/browsingContext/back.ts
async function back2() {
assertTopLevel(this, "back");
await traverseTopLevelHistory(this.browser, -1, this.contextId);
}
// src/commands/browsingContext/forward.ts
async function forward2() {
assertTopLevel(this, "forward");
await traverseTopLevelHistory(this.browser, 1, this.contextId);
}
// src/commands/browsingContext/getTitle.ts
async function getTitle() {
return this.execute(() => document.title);
}
// src/commands/browsingContext/getUrl.ts
async function getUrl() {
const url2 = await this.execute(() => document.URL);
if (typeof url2 === "string") {
this.url = url2;
}
return url2;
}
// src/session/dialog.ts
function getDialogManager(browser) {
return SessionManager.getSessionManager(browser, DialogManager);
}
var DialogManager = class _DialogManager extends SessionManager {
#browser;
#initialize;
#autoHandleDialog = true;
#prompts = /* @__PURE__ */ new Map();
#handleUserPromptListener = this.#handleUserPrompt.bind(this);
constructor(browser) {
super(browser, _DialogManager.name);
this.#browser = browser;
if (!this.isEnabled()) {
this.#initialize = Promise.resolve(true);
return;
}
this.#initialize = this.#browser.sessionSubscribe({
events: ["browsingContext.userPromptOpened"]
}).then(() => true, () => false);
this.#browser.on("_dialogListenerRegistered", () => this.#switchListenerFlag(false));
this.#browser.on("_dialogListenerRemoved", () => this.#switchListenerFlag(true));
this.#browser.on("browsingContext.userPromptOpened", this.#handleUserPromptListener);
}
removeListeners() {
super.removeListeners();
this.#browser.off("browsingContext.userPromptOpened", this.#handleUserPromptListener);
this.#browser.removeAllListeners("_dialogListenerRegistered");
this.#browser.removeAllListeners("_dialogListenerRemoved");
}
async initialize() {
return this.#initialize;
}
promptMessage(context) {
return this.#prompts.get(context);
}
clearPrompt(context) {
this.#prompts.delete(context);
}
/**
* capture shadow root elements propagated through console.debug
*/
async #handleUserPrompt(log29) {
this.#prompts.set(log29.context, log29.message);
if (this.#autoHandleDialog) {
this.#prompts.delete(log29.context);
try {
return await this.#browser.browsingContextHandleUserPrompt({
accept: false,
context: log29.context
});
} catch (err) {
if (err instanceof Error && (err.message.includes("no such alert") || err.message.includes("no such frame"))) {
return;
}
throw err;
}
}
const dialog = new Dialog(log29, this.#browser);
this.#browser.emit("dialog", dialog);
}
/**
* Is called when a new dialog listener is registered with the `dialog` name.
* In these cases we set a flag to the `#listener` map to indicate that we
* are listening to dialog events for this page in this context.
*/
#switchListenerFlag(value) {
this.#autoHandleDialog = value;
}
};
var Dialog = class {
#browser;
#context;
#message;
#defaultValue;
#type;
constructor(event, browser) {
this.#message = event.message;
this.#defaultValue = event.defaultValue;
this.#type = event.type;
this.#context = event.context;
this.#browser = browser;
}
message() {
return this.#message;
}
defaultValue() {
return this.#defaultValue;
}
type() {
return this.#type;
}
/**
* Returns when the dialog has been accepted.
*
* @alias dialog.accept
* @param {string=} promptText A text to enter into prompt. Does not cause any effects if the dialog's type is not prompt.
* @returns {Promise<void>}
*/
async accept(userText) {
await this.#handle({ accept: true, userText });
}
async dismiss() {
await this.#handle({ accept: false });
}
/**
* Answer the dialog in the context that opened it, which is not always
* the session's current context (a held tab, for example). A dialog whose
* context closed, or that another handler answered first, is done.
*/
async #handle(params) {
const browser = this.#browser;
try {
await browser.browsingContextHandleUserPrompt({
context: this.#context,
...params
});
} catch (err) {
if (err instanceof Error && (err.message.includes("no such alert") || err.message.includes("no such frame"))) {
return;
}
throw err;
} finally {
getDialogManager(browser).clearPrompt(this.#context);
}
}
};
// src/commands/browsingContext/acceptAlert.ts
async function acceptAlert(userText) {
await this.browser.browsingContextHandleUserPrompt({
context: this.contextId,
accept: true,
...userText !== void 0 ? { userText } : {}
});
getDialogManager(this.browser).clearPrompt(this.contextId);
}
// src/commands/browsingContext/dismissAlert.ts
async function dismissAlert() {
await this.browser.browsingContextHandleUserPrompt({
context: this.contextId,
accept: false
});
getDialogManager(this.browser).clearPrompt(this.contextId);
}
// src/commands/browsingContext/getAlertText.ts
async function getAlertText() {
const stored = getDialogManager(this.browser).promptMessage(this.contextId);
if (stored !== void 0) {
return stored;
}
return this.browser.getAlertText();
}
// src/browsingContext.ts
var SESSION_FLAGS = [
"isBidi",
"isMobile",
"isIOS",
"isAndroid",
"isFirefox",
"isChrome",
"isSafari"
];
function onBrowser(command, fn) {
return async function(...args) {
assertTopLevel(this, command);
return fn.apply(this.browser, args);
};
}
function topLevel(command, fn) {
return async function(...args) {
assertTopLevel(this, command);
return fn.apply(this, args);
};
}
function rejectCommand(name) {
return function() {
return Promise.reject(new Error(
`\`${name}\` is only available on the browser, not on a browsing context. Use \`browser.${name}(name, fn, { attachToBrowsingContext: true })\` to change the commands of every browsing context.`
));
};
}
var RESERVED_PROPERTIES = /* @__PURE__ */ new Set([
"contextId",
"browser",
"isFrame",
"url",
"parent",
"request",
"capabilities",
"strategies",
"sessionId",
"options",
"commandList",
"addCommand",
"overwriteCommand",
"on",
"off",
"once",
"emit",
"removeListener",
"removeAllListeners",
...SESSION_FLAGS
]);
var FORBIDDEN_COMMAND_NAMES = /* @__PURE__ */ new Map([
["then", "it would make every browsing context a thenable, and awaiting a command that returns a context would call it"],
["catch", "it would make every browsing context look like a promise"],
["finally", "it would make every browsing context look like a promise"],
["__propertiesObject__", "WebdriverIO uses it internally"],
["__elementOverrides__", "WebdriverIO uses it internally"],
["constructor", "it is an object internal"],
["__proto__", "it is an object internal"]
]);
function assertCommandName(command, name) {
const reason = FORBIDDEN_COMMAND_NAMES.get(name);
if (reason) {
throw new Error(`${command}: a browsing context command cannot be named "${name}": ${reason}.`);
}
}
var registries = /* @__PURE__ */ new WeakMap();
function getRegistry(browser) {
let registry = registries.get(browser);
if (!registry) {
const contexts = /* @__PURE__ */ new Set();
registry = {
commands: /* @__PURE__ */ new Map(),
overrides: /* @__PURE__ */ new Map(),
contexts,
finalizer: new FinalizationRegistry((ref2) => contexts.delete(ref2))
};
registries.set(browser, registry);
}
return registry;
}
function composeOverrides(base, overrides2 = []) {
return overrides2.reduce((previous, override) => {
return function(...args) {
const context = this;
function originalCommand(...originalArgs) {
return previous.apply(this || context, originalArgs);
}
return override.call(context, originalCommand, ...args);
};
}, base);
}
function resolveCommand(registry, name) {
const base = registry.commands.get(name) || commandProperties()[name]?.value;
if (typeof base !== "function") {
return void 0;
}
return composeOverrides(base, registry.overrides.get(name));
}
function liveContexts(registry) {
const contexts = [];
for (const ref2 of registry.contexts) {
const context = ref2.deref();
if (context) {
contexts.push(context);
} else {
registry.contexts.delete(ref2);
}
}
return contexts;
}
function applyToLiveContexts(registry, name) {
const command = resolveCommand(registry, name);
if (!command) {
return;
}
for (const context of liveContexts(registry)) {
Object.defineProperty(context, name, {
value: wrapCommand2(name, command),
configurable: true,
writable: true
});
}
}
function addBrowsingContextCommand(browser, name, fn) {
if (typeof fn !== "function") {
throw new Error(`addCommand: the browsing context command "${name}" must be a function`);
}
assertCommandName("addCommand", name);
if (RESERVED_PROPERTIES.has(name) || name in commandProperties()) {
throw new Error(
`addCommand: "${name}" is already a property of every browsing context. Use \`overwriteCommand(name, fn, { attachToBrowsingContext: true })\` to change a built-in command.`
);
}
const registry = getRegistry(browser);
registry.commands.set(name, fn);
applyToLiveContexts(registry, name);
}
function overwriteBrowsingContextCommand(browser, name, fn) {
if (typeof fn !== "function") {
throw new Error(`overwriteCommand: the browsing context command "${name}" must be overwritten with a function`);
}
assertCommandName("overwriteCommand", name);
const registry = getRegistry(browser);
if (!registry.commands.has(name) && typeof commandProperties()[name]?.value !== "function") {
throw new Error(`overwriteCommand: no browsing context command to be overwritten: ${name}`);
}
registry.overrides.set(name, [...registry.overrides.get(name) || [], fn]);
applyToLiveContexts(registry, name);
}
function enableBrowsingContextCommands(browser) {
const addCommand = browser.addCommand;
const overwriteCommand = browser.overwriteCommand;
browser.addCommand = function(name, fn, options) {
const resolved = resolveCustomCommandOptions("addCommand", options);
if (resolved.attachToBrowsingContext) {
return addBrowsingContextCommand(browser, name, fn);
}
return addCommand.call(this || browser, name, fn, resolved);
};
browser.overwriteCommand = function(name, fn, options) {
const resolved = resolveCustomCommandOptions("overwriteCommand", options);
if (resolved.attachToBrowsingContext) {
return overwriteBrowsingContextCommand(browser, name, fn);
}
return overwriteCommand.call(this || browser, name, fn, resolved);
};
}
function commandProperties() {
const commands = {
$,
$$,
custom$,
custom$$,
react$,
react$$,
execute,
action,
actions,
keys,
scroll,
saveScreenshot: saveScreenshot2,
savePDF: savePDF2,
getCookies,
setCookies,
deleteCookies,
setViewport,
addInitScript,
mock,
mockClearAll: topLevel("mockClearAll", mockClearAll),
mockRestoreAll: topLevel("mockRestoreAll", mockRestoreAll),
waitUntil,
pause,
frame,
navigate,
refresh,
closeWindow,
activate,
back: back2,
forward: forward2,
getTitle,
getUrl,
acceptAlert,
dismissAlert,
getAlertText,
emulate: topLevel("emulate", emulate),
restore: onBrowser("restore", restore)
};
const properties = {
strategies: { value: null, writable: true }
};
for (const [name, value] of Object.entries(commands)) {
properties[name] = { value, configurable: true };
}
return properties;
}
function getBrowsingContext(browser, contextId, init) {
const registry = getRegistry(browser);
const monad = webdriverMonad2({
...browser.options,
capabilities: browser.capabilities
}, (client) => {
client.contextId = contextId;
client.browser = browser;
client.isFrame = init.isFrame;
client.url = init.url;
if (init.parent) {
client.parent = init.parent;
}
if (init.request) {
client.request = init.request;
}
client.capabilities = browser.capabilities;
client.strategies = browser.strategies;
const target = client;
const source = browser;
for (const flag of SESSION_FLAGS) {
if (flag in browser) {
target[flag] = source[flag];
}
}
client.on = browser.on.bind(browser);
client.off = browser.off.bind(browser);
client.once = browser.once.bind(browser);
client.emit = browser.emit.bind(browser);
client.removeListener = browser.removeListener.bind(browser);
client.removeAllListeners = browser.removeAllListeners.bind(browser);
return setWdioKind3(client, "browsing-context");
}, contextProperties(registry));
const context = monad(browser.sessionId, wrapCommand2);
context.addCommand = rejectCommand("addCommand");
context.overwriteCommand = rejectCommand("overwriteCommand");
const ref2 = new WeakRef(context);
registry.contexts.add(ref2);
registry.finalizer.register(context, ref2);
return context;
}
function contextProperties(registry) {
const properties = commandProperties();
for (const [name, fn] of registry.commands) {
properties[name] = { value: fn, configurable: true };
}
for (const [name, overrides2] of registry.overrides) {
const base = properties[name]?.value;
if (typeof base === "function") {
properties[name] = { value: composeOverrides(base, overrides2), configurable: true };
}
}
return properties;
}
// src/commands/browser/newWindow.ts
var WAIT_FOR_NEW_HANDLE_TIMEOUT = 3e3;
async function newWindow(url2, options = {}) {
if (typeof url2 !== "string") {
throw new Error("number or type of arguments don't agree with newWindow command");
}
const legacyOptions = options;
if (legacyOptions.windowName || legacyOptions.windowFeatures || legacyOptions.windowFeature) {
throw new Error(
"The `windowName` and `windowFeatures` options were removed from `newWindow` in WebdriverIO v10. Only `{ type: 'tab' | 'window' }` is supported."
);
}
const { type = "window" } = options;
if (!["tab", "window"].includes(type)) {
throw new Error(`Invalid type '${type}' provided to newWindow command. Use either 'tab' or 'window'`);
}
if (this.isMobile) {
throw new Error("newWindow command is not supported on mobile platforms");
}
const tabsBefore = await this.getWindowHandles();
if (this.isBidi) {
const reference = options.referenceContext;
if (reference && typeof reference !== "string" && reference.isFrame) {
throw new Error("`referenceContext` must be a top-level browsing context");
}
const referenceContext = typeof reference === "string" ? reference : reference?.contextId;
const { context } = await this.browsingContextCreate({
type,
...referenceContext ? { referenceContext } : {}
});
await this.browsingContextNavigate({ context, url: url2, wait: historyReadiness(this.capabilities.pageLoadStrategy) });
return getBrowsingContext(this, context, { isFrame: false, url: url2 });
}
await this.execute(newWindowHelper, url2);
let tabsAfter = await this.getWindowHandles();
const now = Date.now();
while (Date.now() - now < WAIT_FOR_NEW_HANDLE_TIMEOUT) {
tabsAfter = await this.getWindowHandles();
if (tabsAfter.length > tabsBefore.length) {
break;
}
await sleep(100);
}
const newTab = tabsAfter.pop();
if (!newTab) {
throw new Error("No window handle was found to switch to");
}
await this.switchToWindow(newTab);
return { handle: newTab, type };
}
// src/commands/browser/browsingContexts.ts
async function browsingContexts() {
if (!this.isBidi) {
throw new Error("`browsingContexts` is only available in a WebDriver BiDi session");
}
const { contexts } = await this.browsingContextGetTree({});
return contexts.map((info) => getBrowsingContext(this, contextIdValue(info.context), {
isFrame: false,
url: info.url
}));
}
// src/commands/browser/reloadSession.ts
import logger16 from "@wdio/logger";
// src/session/polyfill.ts
import logger15 from "@wdio/logger";
import { polyfillFn as polyfillFn2 } from "./scripts/polyfill.js";
function getPolyfillManager(browser) {
return SessionManager.getSessionManager(browser, PolyfillManager);
}
var log15 = logger15("webdriverio:PolyfillManager");
var PolyfillManager = class _PolyfillManager extends SessionManager {
#initialize;
#browser;
#scriptsRegisteredInContexts = /* @__PURE__ */ new Set();
#registerScriptsListener = this.#registerScripts.bind(this);
constructor(browser) {
super(browser, _PolyfillManager.name);
this.#browser = browser;
if (!this.isEnabled()) {
this.#initialize = Promise.resolve(true);
return;
}
this.#browser.on("browsingContext.contextCreated", this.#registerScriptsListener);
this.#initialize = Promise.all([
this.#browser.browsingContextGetTree({}).then(({ contexts }) => {
return Promise.all(contexts.map((context) => this.#registerScripts(context)));
}),
this.#browser.sessionSubscribe({
events: ["browsingContext.contextCreated"]
})
]).then(() => true, () => false);
}
removeListeners() {
super.removeListeners();
this.#browser.off("browsingContext.contextCreated", this.#registerScriptsListener);
}
#registerScripts(context) {
if (this.#scriptsRegisteredInContexts.has(context.context)) {
return;
}
const functionDeclaration = createFunctionDeclarationFromString(polyfillFn2);
log15.info(`Adding polyfill script to context with id ${context.context}`);
this.#scriptsRegisteredInContexts.add(context.context);
return Promise.all([
!context.parent ? this.#browser.scriptAddPreloadScript({
functionDeclaration,
contexts: [context.context]
}).catch(() => {
}) : Promise.resolve(),
this.#browser.scriptCallFunction({
functionDeclaration,
target: context,
awaitPromise: false
}).catch(() => {
})
]);
}
async initialize() {
return this.#initialize;
}
};
// src/session/index.ts
function registerSessionManager(instance) {
const initializationPromises = [
getContextManager(instance).initialize()
];
if (typeof instance.capabilities.webSocketUrl === "string") {
initializationPromises.push(
getPolyfillManager(instance).initialize(),
getShadowRootManager(instance).initialize(),
getNetworkManager(instance).initialize(),
getDialogManager(instance).initialize()
);
}
return Promise.all(initializationPromises);
}
// src/commands/browser/reloadSession.ts
var log16 = logger16("webdriverio");
async function reloadSession(newCapabilities) {
const oldSessionId = this.sessionId;
const shutdownDriver = Boolean(newCapabilities?.browserName);
try {
await this.deleteSession({ shutdownDriver });
} catch (err) {
log16.warn(`Suppressing error closing the session: ${err.stack}`);
}
if (this.puppeteer?.connected) {
this.puppeteer.disconnect();
log16.debug("Disconnected puppeteer session");
}
const ProtocolDriver = (await import(
/* @vite-ignore */
this.options.automationProtocol
)).default;
await ProtocolDriver.reloadSession(this, newCapabilities);
await registerSessionManager(this);
const options = this.options;
if (Array.isArray(options.onReload) && options.onReload.length) {
await Promise.all(options.onReload.map((hook) => hook(oldSessionId, this.sessionId)));
}
return this.sessionId;
}
// src/commands/browser/saveRecordingScreen.ts
async function saveRecordingScreen2(filepath) {
return environment.value.saveRecordingScreen.call(this, filepath);
}
// src/commands/browser/setTimeout.ts
async function setTimeout2(timeouts) {
if (typeof timeouts !== "object") {
throw new Error('Parameter for "setTimeout" command needs to be an object');
}
if ("page load" in timeouts) {
throw new Error("The `page load` timeout key was removed in WebdriverIO v10. Use `{ pageLoad: ... }`.");
}
const timeoutKeys = ["implicit", "pageLoad", "script"];
const knownTimeoutValues = Object.entries(timeouts).filter(([key]) => timeoutKeys.includes(key)).map(([, timeout]) => timeout);
const timeoutValues = knownTimeoutValues.length > 0 ? knownTimeoutValues : Object.values(timeouts);
const definedTimeoutValues = timeoutValues.filter((timeout) => timeout !== void 0);
if (definedTimeoutValues.some((timeout) => typeof timeout !== "number" || timeout < 0 || timeout > Number.MAX_SAFE_INTEGER) || timeoutValues.length > 0 && definedTimeoutValues.length === 0) {
throw new Error("Specified timeout values are not valid integer (see https://webdriver.io/docs/api/browser/setTimeout for documentation).");
}
const implicit = timeouts.implicit;
const pageLoad = timeouts.pageLoad;
const script = timeouts.script;
const setTimeouts = this.setTimeouts.bind(this);
return setTimeouts(implicit, pageLoad, script);
}
// src/commands/browser/setWindowSize.ts
import { getBrowserObject as getBrowserObject15 } from "@wdio/utils";
var minWindowSize2 = 0;
var maxWindowSize2 = Number.MAX_SAFE_INTEGER;
async function setWindowSize(width, height) {
if (typeof width !== "number" || typeof height !== "number") {
throw new Error("setWindowSize expects width and height of type number");
}
if (width < minWindowSize2 || width > maxWindowSize2 || height < minWindowSize2 || height > maxWindowSize2) {
throw new Error("setWindowSize expects width and height to be a number in the 0 to 2^31 \u2212 1 range");
}
const browser = getBrowserObject15(this);
await browser.setWindowRect(null, null, width, height);
}
// src/commands/browser/switchWindow.ts
async function switchWindow(matcher) {
if (this.isBidi) {
throw new Error(
"`switchWindow` was removed for WebDriver BiDi sessions in WebdriverIO v10. Hold the browsing context returned by `browser.url()` or `browser.newWindow()`, or find one with `browser.browsingContexts()`."
);
}
if (typeof matcher !== "string" && !(matcher instanceof RegExp)) {
throw new Error('Unsupported parameter for switchWindow, required is "string" or a RegExp');
}
const contextManager = getContextManager(this);
const tabs = await this.getWindowHandles();
if (typeof matcher === "string" && tabs.includes(matcher)) {
if (matcher === contextManager.getCurrentWindowHandle()) {
return matcher;
}
await this.switchToWindow(matcher);
contextManager.setCurrentContext(matcher);
return matcher;
}
const matchesTarget = (target) => {
if (typeof matcher === "string") {
return target.includes(matcher);
}
return matcher.test(target);
};
for (const tab of tabs) {
await this.switchToWindow(tab);
contextManager.setCurrentContext(tab);
const url2 = await this.getUrl();
if (matchesTarget(url2)) {
return tab;
}
const title = await this.getTitle();
if (matchesTarget(title)) {
return tab;
}
const windowName = await this.execute(
/* istanbul ignore next */
() => window.name
);
if (windowName && matchesTarget(windowName)) {
return tab;
}
}
throw new Error(`No window found with title, url, name or window handle matching "${matcher}"`);
}
// src/commands/browser/switchFrame.ts
import { getWdioKind as getWdioKind3 } from "@wdio/utils";
async function switchFrame(context) {
if (this.isBidi) {
throw new Error(
"`switchFrame` was removed for WebDriver BiDi sessions in WebdriverIO v10. Call `frame()` on the browsing context returned by `browser.url()` or `browser.newWindow()`."
);
}
function isPossiblyUnresolvedElement(input) {
return getWdioKind3(input) === "element";
}
if (typeof context === "function") {
throw new Error("Cannot use a function to fetch a context in WebDriver Classic");
}
if (typeof context === "string") {
throw new Error("Cannot use a string to fetch a context in WebDriver Classic");
}
if (isPossiblyUnresolvedElement(context)) {
const element = await context.getElement();
await element.waitForExist({
timeoutMsg: `Can't switch to frame with selector ${element.selector} because it doesn't exist`
});
return switchToFrame(this, element);
}
return switchToFrame(this, context);
}
function switchToFrame(browser, frame2) {
const classicBrowser = browser;
toggleDisableDeprecationWarning();
return classicBrowser.switchToFrame(frame2).finally(toggleDisableDeprecationWarning);
}
function toggleDisableDeprecationWarning() {
if (typeof process !== "undefined" && process.env) {
process.env.DISABLE_WEBDRIVERIO_DEPRECATION_WARNINGS = process.env.DISABLE_WEBDRIVERIO_DEPRECATION_WARNINGS ? void 0 : "true";
}
}
// src/commands/browser/throttleCPU.ts
async function throttleCPU(factor) {
if (typeof factor !== "number") {
throw new Error('Invalid factor for "throttleCPU". Expected it to be a number (int)');
}
const failedConnectionMessage = "No Puppeteer connection could be established which is required to use this command";
await this.getPuppeteer();
if (!this.puppeteer) {
throw new Error(failedConnectionMessage);
}
const pages = await this.puppeteer.pages();
if (!pages.length) {
throw new Error(failedConnectionMessage);
}
const client = await pages[0].target().createCDPSession();
await client.send("Emulation.setCPUThrottlingRate", { rate: factor });
}
// src/commands/browser/throttleNetwork.ts
import { getBrowserObject as getBrowserObject16 } from "@wdio/utils";
import logger17 from "@wdio/logger";
var log17 = logger17("webdriverio:throttleNetwork");
var NETWORK_PRESETS = {
"offline": {
offline: true,
downloadThroughput: 0,
uploadThroughput: 0,
latency: 1
},
"GPRS": {
offline: false,
downloadThroughput: 50 * 1024 / 8,
uploadThroughput: 20 * 1024 / 8,
latency: 500
},
"Regular2G": {
offline: false,
downloadThroughput: 250 * 1024 / 8,
uploadThroughput: 50 * 1024 / 8,
latency: 300
},
"Good2G": {
offline: false,
downloadThroughput: 450 * 1024 / 8,
uploadThroughput: 150 * 1024 / 8,
latency: 150
},
"Regular3G": {
offline: false,
downloadThroughput: 750 * 1024 / 8,
uploadThroughput: 250 * 1024 / 8,
latency: 100
},
"Good3G": {
offline: false,
downloadThroughput: 1.5 * 1024 * 1024 / 8,
uploadThroughput: 750 * 1024 / 8,
latency: 40
},
"Regular4G": {
offline: false,
downloadThroughput: 4 * 1024 * 1024 / 8,
uploadThroughput: 3 * 1024 * 1024 / 8,
latency: 20
},
"DSL": {
offline: false,
downloadThroughput: 2 * 1024 * 1024 / 8,
uploadThroughput: 1 * 1024 * 1024 / 8,
latency: 5
},
"WiFi": {
offline: false,
downloadThroughput: 30 * 1024 * 1024 / 8,
uploadThroughput: 15 * 1024 * 1024 / 8,
latency: 2
},
"online": {
offline: false,
latency: 0,
downloadThroughput: -1,
uploadThroughput: -1
}
};
var NETWORK_PRESET_TYPES = Object.keys(NETWORK_PRESETS);
async function throttleNetwork(params) {
if (
/**
* check string parameter
*/
(typeof params !== "string" || !NETWORK_PRESET_TYPES.includes(params)) && /**
* check object parameter
*/
typeof params !== "object"
) {
throw new Error(`Invalid parameter for "throttleNetwork". Expected it to be typeof object or one of the following values: ${NETWORK_PRESET_TYPES.join(", ")} but found "${params}"`);
}
if (this.isSauce) {
const browser = getBrowserObject16(this);
await browser.sauceThrottleNetwork(params);
return;
}
const failedConnectionMessage = "No Puppeteer connection could be established which is required to use this command";
await this.getPuppeteer();
if (!this.puppeteer) {
throw new Error(failedConnectionMessage);
}
const pages = await this.puppeteer.pages();
if (!pages.length) {
throw new Error(failedConnectionMessage);
}
const conditions = typeof params === "string" ? NETWORK_PRESETS[params] : params;
const client = await pages[0].target().createCDPSession();
await client.send("Network.emulateNetworkConditions", conditions);
const serviceWorkers = this.puppeteer.targets().filter((target) => target.type() === "service_worker");
for (const serviceWorker of serviceWorkers) {
try {
const serviceWorkerClient = await serviceWorker.createCDPSession();
await serviceWorkerClient.send("Network.enable");
await serviceWorkerClient.send("Network.emulateNetworkConditions", conditions);
} catch {
log17.debug("Skipped network throttling for a service worker that terminated during attach");
}
}
return;
}
// src/commands/browser/uninstallExtension.ts
async function uninstallExtension(extensionId) {
if (!this.isBidi) {
throw new Error("uninstallExtension requires a WebDriver BiDi session (webExtension.uninstall)");
}
if (typeof extensionId !== "string" || extensionId.length === 0) {
throw new Error("uninstallExtension expects the extension id returned by installExtension");
}
try {
await this.webExtensionUninstall({ extension: extensionId });
} catch (err) {
throw annotateWebExtensionError(err, "webExtension.uninstall");
}
}
// src/utils/classicContext.ts
var BIDI_ONLY_COMMANDS = {
frame: "Use `browser.switchFrame()` to run commands in a frame.",
navigate: "Use `browser.url()` to navigate.",
activate: "Use `browser.switchWindow()` to bring a window to the front."
};
function classicContext(browser, url2) {
let currentUrl = url2;
return new Proxy(browser, {
get(target, prop) {
if (prop === "url") {
return currentUrl;
}
if (prop === "getUrl") {
return async () => {
currentUrl = await target.getUrl();
return currentUrl;
};
}
if (prop === "isFrame") {
return false;
}
if (prop === "parent" || prop === "request" || prop === "contextId") {
return void 0;
}
if (prop === "browser") {
return target;
}
if (typeof prop === "string" && prop in BIDI_ONLY_COMMANDS) {
return () => Promise.reject(new Error(
`\`${prop}()\` needs a WebDriver BiDi session, but this session uses WebDriver Classic (for example Appium or Safari), where \`browser.url()\` returns the browser itself. ${BIDI_ONLY_COMMANDS[prop]}`
));
}
const value = Reflect.get(target, prop);
return typeof value === "function" ? value.bind(target) : value;
}
});
}
// src/commands/browser/url.ts
async function url(path9, options = {}) {
if (typeof path9 !== "string") {
throw new Error('Parameter for "url" command needs to be type of string');
}
if (typeof this.options.baseUrl === "string" && this.options.baseUrl) {
path9 = new URL(path9, this.options.baseUrl).href;
}
if (this.isBidi && path9.startsWith("http")) {
const context = await getContextManager(this).getCurrentContext();
const request = await navigateInContext(this, context, path9, options);
return getBrowsingContext(this, contextIdValue(context), {
isFrame: false,
url: path9,
...request ? { request } : {}
});
}
if (Object.keys(options).length > 0) {
throw new Error("Setting url options is only supported when automating browser using WebDriver Bidi protocol");
}
await this.navigateTo(validateUrl(path9));
if (this.isBidi) {
const context = await getContextManager(this).getCurrentContext();
return getBrowsingContext(this, contextIdValue(context), { isFrame: false, url: path9 });
}
return classicContext(this, path9);
}
// src/commands/mobile/swipe.ts
import logger18 from "@wdio/logger";
// src/types.ts
var MobileScrollDirection = /* @__PURE__ */ ((MobileScrollDirection2) => {
MobileScrollDirection2["Down"] = "down";
MobileScrollDirection2["Up"] = "up";
MobileScrollDirection2["Left"] = "left";
MobileScrollDirection2["Right"] = "right";
return MobileScrollDirection2;
})(MobileScrollDirection || {});
// src/commands/mobile/swipe.ts
var log18 = logger18("webdriverio");
var SWIPE_DEFAULTS = {
DIRECTION: "up" /* Up */,
DURATION: 1500,
PERCENT: 0.95
};
async function swipe(options) {
const browser = this;
if (!browser.isNativeContext) {
throw new Error("The swipe command is only available for mobile platforms in the NATIVE context.");
}
let { scrollableElement, from, to } = options || {};
if (scrollableElement && (from || to)) {
log18.warn("`scrollableElement` is provided, so `from` and `to` will be ignored.");
}
if (!from || !to) {
scrollableElement = scrollableElement || await getScrollableElement(browser);
({ from, to } = await calculateFromTo({
browser,
direction: options?.direction || SWIPE_DEFAULTS.DIRECTION,
percentage: options?.percent,
scrollableElement
}));
}
return w3cSwipe({ browser, duration: options?.duration || SWIPE_DEFAULTS.DURATION, from, to });
}
async function calculateFromTo({
browser,
direction,
percentage,
scrollableElement
}) {
let swipePercentage = SWIPE_DEFAULTS.PERCENT;
if (percentage !== void 0) {
if (isNaN(percentage)) {
log18.warn("The percentage to swipe should be a number.");
} else if (percentage < 0 || percentage > 1) {
log18.warn("The percentage to swipe should be a number between 0 and 1.");
} else {
swipePercentage = percentage;
}
}
const { x, y, width, height } = await browser.getElementRect(await scrollableElement?.elementId);
const verticalOffset = height - height * swipePercentage;
const horizontalOffset = width - width * swipePercentage;
const scrollRectangles = {
top: { x: Math.round(x + width / 2), y: Math.round(y + verticalOffset / 2) },
right: { x: Math.round(x + width - horizontalOffset / 2), y: Math.round(y + height / 2) },
bottom: { x: Math.round(x + width / 2), y: Math.round(y + height - verticalOffset / 2) },
left: { x: Math.round(x + horizontalOffset / 2), y: Math.round(y + height / 2) }
};
let from;
let to;
switch (direction) {
case "down" /* Down */:
from = scrollRectangles.top;
to = scrollRectangles.bottom;
break;
case "left" /* Left */:
from = scrollRectangles.right;
to = scrollRectangles.left;
break;
case "right" /* Right */:
from = scrollRectangles.left;
to = scrollRectangles.right;
break;
case "up" /* Up */:
from = scrollRectangles.bottom;
to = scrollRectangles.top;
break;
default:
throw new Error(`Unknown direction: ${direction}`);
}
return { from, to };
}
async function getScrollableElement(browser) {
const defaultAndroidSelector = "//android.widget.ScrollView";
const defaultIosSelector = '-ios predicate string:type == "XCUIElementTypeApplication"';
const selector = browser.isIOS ? (
// For iOS, we need to find the application element, if we can't find it, we should throw an error
defaultIosSelector
) : (
// There is always a scrollview for Android or, if this fails we should throw an error
defaultAndroidSelector
);
const scrollableElements = await browser.$$(
selector
);
if (scrollableElements.length > 0) {
return scrollableElements[0];
}
throw new Error(
`Default scrollable element '${browser.isIOS ? defaultIosSelector : defaultAndroidSelector}' was not found. Our advice is to provide a scrollable element like this:
await browser.swipe({ scrollableElement: $('#scrollable') });
`
);
}
async function w3cSwipe({ browser, duration, from, to }) {
await browser.action("pointer", {
parameters: { pointerType: browser.isMobile ? "touch" : "mouse" }
}).move(from.x, from.y).down().pause(10).move({ duration, x: to.x, y: to.y }).up().perform();
return browser.pause(500);
}
// src/commands/mobile/tap.ts
import logger19 from "@wdio/logger";
import { getBrowserObject as getBrowserObject17 } from "@wdio/utils";
var log19 = logger19("webdriver");
async function tap(options) {
const isElement2 = this.selector !== void 0;
const element = isElement2 ? this : null;
const browser = isElement2 ? getBrowserObject17(this) : this;
if (!browser.isMobile) {
throw new Error("The tap command is only available for mobile platforms.");
}
validateTapOptions(options);
if (element) {
return await elementTap(browser, element, options);
}
if (!options || options.x === void 0 || options.y === void 0) {
throw new Error("The tap command requires x and y coordinates to be set for screen taps.");
}
return await screenTap(browser, options);
}
function validateTapOptions(options) {
if (options) {
if (typeof options !== "object" || Array.isArray(options)) {
throw new TypeError("Options must be an object.");
}
const { x, y, ...otherArgs } = options;
if (x === void 0 !== (y === void 0)) {
throw new TypeError(`If ${x !== void 0 ? "x" : "y"} is set, then ${x !== void 0 ? "y" : "x"} must also be set.`);
}
if (x !== void 0 && y !== void 0 && Object.keys(otherArgs).length > 0) {
throw new TypeError(`If x and y are provided, no other arguments are allowed. Found: ${Object.keys(otherArgs).join(", ")}`);
}
const invalidCoordinates = [];
if (x !== void 0 && x < 0) {
invalidCoordinates.push("x");
}
if (y !== void 0 && y < 0) {
invalidCoordinates.push("y");
}
if (invalidCoordinates.length > 0) {
throw new TypeError(`The ${invalidCoordinates.join(" and ")} value${invalidCoordinates.length > 1 ? "s" : ""} must be positive.`);
}
}
}
async function elementTap(browser, element, options) {
if (browser.isNativeContext) {
return await nativeTap(element, browser, options);
}
if (options) {
log19.warn("The options object is not supported in Web environments and will be ignored.");
}
return await webTap(element);
}
async function webTap(element) {
return element.click();
}
async function executeNativeTap(browser, options) {
return await browser.execute(
`mobile: ${browser.isIOS ? "tap" : "clickGesture"}`,
{ ...browser.isIOS ? { x: 0, y: 0 } : {}, ...options }
);
}
async function nativeTap(element, browser, options = {}) {
try {
if (!element.elementId) {
throw new Error("no such element");
}
return await executeNativeTap(browser, { elementId: element.elementId });
} catch (error) {
let err = error;
if (typeof error === "string") {
err = new Error(error);
}
if (!err.message.includes("no such element")) {
throw err;
}
const scrollIntoViewOptions = Object.fromEntries(
Object.entries({
direction: options?.direction,
maxScrolls: options?.maxScrolls,
scrollableElement: options?.scrollableElement
}).filter(([_, value]) => value !== void 0)
);
try {
await element.scrollIntoView(scrollIntoViewOptions);
return await executeNativeTap(browser, { elementId: element.elementId });
} catch (scrollError) {
let err2 = scrollError;
if (typeof scrollError === "string") {
err2 = new Error(scrollError);
}
if (err2.message.includes("Element not found within scroll limit of")) {
throw new Error(`Element not found within the automatic 'tap' scroll limit of ${scrollIntoViewOptions?.maxScrolls || "10"} scrolls by scrolling "${scrollIntoViewOptions?.direction || "down"}". The 'tap' methods will automatically scroll if it can't find the element. It might be that 'direction|maxScrolls|scrollableElement' are not correct. You can change change them like this:
await elem.tap({
direction: 'left' // possible options are: 'up|down|left|right'
maxScrolls: 15,
scrollableElement: $('#scrollable'),
});
`);
} else if (err2.message.includes("Default scrollable element")) {
const match = err2.message.match(/Default scrollable element '(.*?)' was not found/);
const scrollableElement = match?.[1] || "unknown-scrollable-element";
throw new Error(`The 'tap' method tried to automatically scroll to the element but couldn't find the default scrollable element. '${scrollableElement}' If needed you can provide a custom scrollable element, together with the 'direction' and the 'maxScrolls' like this:
await elem.tap({
scrollableElement: $('#scrollable'),
});
`);
}
throw err2;
}
}
}
async function screenTap(browser, options) {
const { x, y } = options;
if (browser.isNativeContext) {
return await executeNativeTap(browser, options);
}
return await browser.action(
"pointer",
{
parameters: { pointerType: "touch" }
}
).move({ x, y }).down({ button: 0 }).pause(10).up({ button: 0 }).perform();
}
// src/commands/mobile/getContext.ts
import logger20 from "@wdio/logger";
var log20 = logger20("webdriver");
async function getContext(options) {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `getContext` command is only available for mobile platforms.");
}
const currentAppiumContext = await browser.getAppiumContext();
if (!options || !options?.returnDetailedContext || currentAppiumContext === "NATIVE_APP") {
return currentAppiumContext;
}
delete options.returnDetailedContext;
return getDetailedContext(browser, currentAppiumContext, options);
}
async function getDetailedContext(browser, currentAppiumContext, options) {
const detailedContexts = await browser.getContexts({
...options,
// Defaults
returnDetailedContexts: true,
// We want to get back the detailed context information
isAndroidWebviewVisible: true,
// We only want to get back the visible webviews
filterByCurrentAndroidApp: true,
// We only want to get back the webviews that are attached to the current app
returnAndroidDescriptionData: false
// We don't want to get back the Android Webview description data
});
const parsedContexts = detailedContexts.filter((context) => context.id === currentAppiumContext);
if (parsedContexts.length > 1) {
log20.warn(`We found more than 1 detailed context for the current context '${currentAppiumContext}'. We will return the first context.`);
return parsedContexts[0];
} else if (parsedContexts.length === 0) {
log20.warn(`We did not get back any detailed context for the current context '${currentAppiumContext}'. We will return the current context as a string.`);
return currentAppiumContext;
}
return parsedContexts[0];
}
// src/commands/mobile/getContexts.ts
import logger21 from "@wdio/logger";
var log21 = logger21("webdriver");
async function getContexts(options) {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `getContexts` command is only available for mobile platforms.");
}
if (!options || !options.returnDetailedContexts) {
log21.info("The standard Appium `contexts` method is used. If you want to get more detailed data, you can set `returnDetailedContexts` to `true`.");
return browser.getAppiumContexts();
}
const defaultOptions = {
androidWebviewConnectionRetryTime: 500,
androidWebviewConnectTimeout: 5e3,
filterByCurrentAndroidApp: false,
isAndroidWebviewVisible: true,
returnAndroidDescriptionData: false
};
return getCurrentContexts({ browser, ...{ ...defaultOptions, ...options } });
}
var CHROME_PACKAGE_NAME = "com.android.chrome";
async function parsedAndroidContexts({
contexts,
filterByCurrentAndroidApp,
isAttachedAndVisible,
packageName
}) {
const currentWebviewName = `WEBVIEW_${packageName}`;
let parsedContexts = contexts;
if (filterByCurrentAndroidApp) {
parsedContexts = contexts.filter((context) => context.webviewName === currentWebviewName);
}
const result = [{ id: "NATIVE_APP" }];
if (!parsedContexts || parsedContexts.length < 1) {
return result;
}
parsedContexts.forEach(
(context) => context.pages?.filter((page) => {
if (packageName === CHROME_PACKAGE_NAME) {
return true;
}
if (page.type === "page" && page.description) {
let descriptionObj;
try {
descriptionObj = JSON.parse(page.description);
} catch (e) {
return false;
}
return isAttachedAndVisible ? descriptionObj.attached === true && descriptionObj.visible === true : true;
}
return !isAttachedAndVisible;
}).forEach((page) => {
const {
attached = false,
empty = false,
height = 0,
never_attached: neverAttached = false,
screenX = 0,
screenY = 0,
visible = false,
width = 0
} = JSON.parse(page.description || "{}");
const pageData = {
androidWebviewData: {
attached,
empty,
height,
neverAttached,
screenX,
screenY,
visible,
width
},
id: context.webviewName,
title: page.title,
url: page.url,
packageName: context.info["Android-Package"],
webviewPageId: page.id
};
result.push(pageData);
})
);
return result;
}
async function getCurrentContexts({
browser,
androidWebviewConnectionRetryTime,
androidWebviewConnectTimeout,
filterByCurrentAndroidApp,
isAndroidWebviewVisible,
returnAndroidDescriptionData,
waitForWebviewMs
}) {
const contexts = await (waitForWebviewMs !== void 0 ? browser.execute("mobile: getContexts", { waitForWebviewMs }) : browser.execute("mobile: getContexts"));
if (browser.isIOS) {
return contexts;
}
const packageName = await browser.getCurrentPackage();
const startTime = Date.now();
const retryInterval = androidWebviewConnectionRetryTime;
let isPackageNameMissing = false;
while (Date.now() - startTime < androidWebviewConnectTimeout) {
const parsedContexts = await parsedAndroidContexts({
contexts,
filterByCurrentAndroidApp,
isAttachedAndVisible: isAndroidWebviewVisible,
packageName
});
const matchingContexts = parsedContexts.filter((context) => context.packageName === packageName);
isPackageNameMissing = matchingContexts.length === 0;
const hasAndroidWebviewData = matchingContexts.some((context) => Boolean(context.androidWebviewData));
const isAndroidWebviewDataMissing = matchingContexts.length > 0 && !hasAndroidWebviewData;
const hasNonEmptyAndroidWebviewData = matchingContexts.some((context) => context.androidWebviewData && !context.androidWebviewData.empty);
const isAndroidWebviewDataEmpty = matchingContexts.length > 0 && hasAndroidWebviewData && !hasNonEmptyAndroidWebviewData;
if (packageName === CHROME_PACKAGE_NAME) {
return parsedContexts;
}
if (!isPackageNameMissing && !isAndroidWebviewDataMissing && !isAndroidWebviewDataEmpty) {
if (!returnAndroidDescriptionData) {
parsedContexts.forEach((context) => {
if ("androidWebviewData" in context) {
delete context.androidWebviewData;
}
});
}
return parsedContexts;
}
await new Promise((resolve) => setTimeout(resolve, retryInterval));
}
throw new Error(
`The packageName '${packageName}' ${isPackageNameMissing ? "could not be found!" : "matches, but no webview with pages was loaded in this response: " + JSON.stringify(contexts) + "'"}`
);
}
// src/commands/mobile/switchContext.ts
import logger22 from "@wdio/logger";
var log22 = logger22("webdriver");
async function switchContext(options) {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `switchContext` command is only available for mobile platforms.");
}
if (!options) {
throw new Error("You need to provide at least a context name to switch to. See https://webdriver.io/docs/api/mobile/switchContext for more information.");
}
if (typeof options === "string") {
log22.info("The standard Appium `context`-method is used. If you want to switch to a webview with a specific title or url, please provide an object with the `title` or `url` property. See https://webdriver.io/docs/api/mobile/switchContext for more information.");
return browser.switchAppiumContext(options);
}
if (!options.title && !options.url) {
throw new Error("You need to provide at least a `title` or `url` property to use full potential of the `switchContext` command. See https://webdriver.io/docs/api/mobile/switchContext for more information.");
}
return switchToContext({ browser, options });
}
async function switchToContext({ browser, options }) {
const getContextsOptions = {
returnDetailedContexts: true,
filterByCurrentAndroidApp: false,
isAndroidWebviewVisible: false,
returnAndroidDescriptionData: true,
...options?.androidWebviewConnectionRetryTime && { androidWebviewConnectionRetryTime: options.androidWebviewConnectionRetryTime },
...options?.androidWebviewConnectTimeout && { androidWebviewConnectTimeout: options.androidWebviewConnectTimeout }
};
const contexts = await browser.getContexts(getContextsOptions);
let identifier;
if (options.appIdentifier) {
identifier = options.appIdentifier;
} else {
identifier = browser.isIOS ? (await browser.execute("mobile: activeAppInfo"))?.bundleId : await browser.getCurrentPackage();
}
const { matchingContext, reasons } = findMatchingContext({ browser, contexts, identifier, ...options?.title && { title: options.title }, ...options?.url && { url: options.url } });
if (!matchingContext) {
throw new Error(reasons.join("\n"));
}
log22.info("WebdriverIO found a matching context:", JSON.stringify(matchingContext, null, 2));
if (!browser.isIOS) {
const webviewName = `WEBVIEW_${identifier}`;
await browser.switchAppiumContext(webviewName);
}
const switchFunction = browser.isIOS ? browser.switchAppiumContext.bind(browser) : browser.switchToWindow.bind(browser);
const matchingContextId = browser.isIOS ? matchingContext.id : matchingContext.webviewPageId;
return switchFunction(matchingContextId);
}
function findMatchingContext({
browser: { isIOS },
contexts,
identifier,
title,
url: url2
}) {
const reasons = [];
reasons.push(`We parsed a total of ${contexts.length} Webviews but did not find a matching context. The reasons are:`);
const matchingContext = contexts.find((context, index) => {
reasons.push(`- Webview ${index + 1}: '${context.id}'`);
if (context.id === "NATIVE_APP") {
reasons.push(" - Skipped context because it is NATIVE_APP");
return false;
}
const idMatch = isIOS ? context.bundleId === identifier : context.packageName === identifier;
const titleMatches = title ? title instanceof RegExp ? title.test(context.title || "") : context.title?.includes(title) : true;
const urlMatches = url2 ? url2 instanceof RegExp ? url2.test(context.url || "") : context.url?.includes(url2) : true;
const additionalAndroidChecks = isIOS ? true : context.androidWebviewData?.attached && context.androidWebviewData?.visible;
if (!idMatch) {
reasons.push(` - App ${isIOS ? "bundleId" : "packageName"} '${identifier}' did not match: '${context.id}'`);
}
if (!titleMatches) {
reasons.push(` - Title '${title}' did not match: '${context.title}'`);
}
if (!urlMatches) {
reasons.push(` - URL '${url2}' did not match: '${context.url}'`);
}
if (!additionalAndroidChecks) {
reasons.push(" - Additional Android checks failed");
}
return idMatch && titleMatches && urlMatches && additionalAndroidChecks;
});
return { matchingContext, reasons };
}
// src/commands/mobile/relaunchActiveApp.ts
async function relaunchActiveApp() {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `relaunchActiveApp` command is only available for mobile platforms.");
}
if (browser.isIOS) {
const { bundleId, processArguments: { args, env } } = await browser.execute("mobile: activeAppInfo");
const iOSLaunchOptions = {
bundleId,
...args.length > 0 && { arguments: args },
...Object.keys(env).length > 0 && { environment: env }
};
await browser.execute("mobile: terminateApp", { bundleId });
return browser.execute("mobile:launchApp", iOSLaunchOptions);
}
const packageName = await browser.getCurrentPackage();
await browser.execute("mobile: terminateApp", { appId: packageName });
return browser.execute("mobile: activateApp", { appId: packageName });
}
// src/commands/mobile/deepLink.ts
async function deepLink(link, appIdentifier, waitForLaunch) {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `deepLink` command is only available for mobile platforms.");
}
if (!isDeepLinkUrl(link)) {
throw new Error(`The provided link is not a valid deep link URL.${browser.isIOS ? " If your url is a `universal deep link` then use the `url` command instead." : ""}`);
}
if (!appIdentifier) {
const mobileOS = browser.isIOS ? "iOS" : "Android";
const identifierValue = browser.isIOS ? "bundleId" : "package";
throw new Error(`When using a deep link URL for ${mobileOS}, you need to provide the \`${identifierValue}\` of the app that the deep link should open.`);
}
const args = {
url: link,
[browser.isIOS ? "bundleId" : "package"]: appIdentifier
};
if (!browser.isIOS && waitForLaunch !== void 0) {
args.waitForLaunch = waitForLaunch;
}
return browser.execute("mobile:deepLink", args);
}
function isDeepLinkUrl(link) {
const deepLinkRegex = /^(?!https?:\/\/)[a-zA-Z][\w+\-.]*:\/\//;
return deepLinkRegex.test(link);
}
// src/commands/mobile/lock.ts
async function lock(seconds) {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `lock` command is only available for mobile platforms.");
}
return executeMobile(browser, "mobile: lock", { seconds });
}
// src/commands/mobile/touchId.ts
async function touchId(match, type = "touchId") {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `touchId` command is only available for mobile platforms.");
}
if (!browser.isIOS) {
throw new Error("The `touchId` command is only available for iOS. For Android, use `fingerPrint` instead.");
}
return executeMobile(browser, "mobile: sendBiometricMatch", { match, type });
}
// src/commands/mobile/toggleEnrollTouchId.ts
async function toggleEnrollTouchId(enabled) {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `toggleEnrollTouchId` command is only available for mobile platforms.");
}
if (!browser.isIOS) {
throw new Error("The `toggleEnrollTouchId` command is only available for iOS.");
}
return executeMobile(browser, "mobile: enrollBiometric", { enabled });
}
// src/commands/mobile/launchApp.ts
async function launchApp(options) {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `launchApp` command is only available for mobile platforms.");
}
let mobileCmd;
let mobileArgs;
if (browser.isIOS) {
mobileCmd = "mobile: launchApp";
const bundleId = options?.bundleId ?? (await executeMobile(browser, "mobile: activeAppInfo")).bundleId;
mobileArgs = { bundleId };
if (options?.arguments !== void 0) {
mobileArgs.arguments = options.arguments;
}
if (options?.environment !== void 0) {
mobileArgs.environment = options.environment;
}
} else {
mobileCmd = "mobile: activateApp";
const appId = options?.appId ?? await browser.getCurrentPackage();
mobileArgs = { appId };
}
return executeMobile(browser, mobileCmd, mobileArgs);
}
// src/commands/mobile/background.ts
async function background(seconds) {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `background` command is only available for mobile platforms.");
}
return executeMobile(browser, "mobile: backgroundApp", { seconds });
}
// src/commands/mobile/getStrings.ts
async function getStrings(language, stringFile) {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `getStrings` command is only available for mobile platforms.");
}
return executeMobile(browser, "mobile: getAppStrings", { language, stringFile });
}
// src/commands/mobile/setClipboard.ts
async function setClipboard(content, contentType, label) {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `setClipboard` command is only available for mobile platforms.");
}
return executeMobile(browser, "mobile: setClipboard", { content, contentType, label });
}
// src/commands/mobile/getClipboard.ts
async function getClipboard(contentType) {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `getClipboard` command is only available for mobile platforms.");
}
return executeMobile(browser, "mobile: getClipboard", { contentType });
}
// src/commands/mobile/toggleAirplaneMode.ts
async function toggleAirplaneMode(enabled) {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `toggleAirplaneMode` command is only available for mobile platforms.");
}
if (!browser.isAndroid) {
throw new Error("The `toggleAirplaneMode` command is only available for Android.");
}
return executeMobile(browser, "mobile: setConnectivity", { airplaneMode: enabled });
}
// src/commands/mobile/toggleData.ts
async function toggleData(enabled) {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `toggleData` command is only available for mobile platforms.");
}
if (!browser.isAndroid) {
throw new Error("The `toggleData` command is only available for Android.");
}
return executeMobile(browser, "mobile: setConnectivity", { data: enabled });
}
// src/commands/mobile/toggleWiFi.ts
async function toggleWiFi(enabled) {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `toggleWiFi` command is only available for mobile platforms.");
}
if (!browser.isAndroid) {
throw new Error("The `toggleWiFi` command is only available for Android.");
}
return executeMobile(browser, "mobile: setConnectivity", { wifi: enabled });
}
// src/commands/mobile/unlock.ts
async function unlock(options) {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `unlock` command is only available for mobile platforms.");
}
const args = browser.isAndroid && options ? { ...options } : {};
return executeMobile(browser, "mobile: unlock", args);
}
// src/commands/mobile/isLocked.ts
async function isLocked() {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `isLocked` command is only available for mobile platforms.");
}
return executeMobile(browser, "mobile: isLocked", {});
}
// src/commands/mobile/shake.ts
async function shake() {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `shake` command is only available for mobile platforms.");
}
if (!browser.isIOS) {
throw new Error("The `shake` command is only available for iOS.");
}
return executeMobile(browser, "mobile: shake", {});
}
// src/commands/mobile/closeApp.ts
async function closeApp(options) {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `closeApp` command is only available for mobile platforms.");
}
let terminateArgs;
if (browser.isIOS) {
const bundleId = options?.bundleId ?? (await executeMobile(browser, "mobile: activeAppInfo")).bundleId;
terminateArgs = { bundleId };
} else {
const appId = options?.appId ?? await browser.getCurrentPackage();
terminateArgs = { appId };
}
return executeMobile(browser, "mobile: terminateApp", terminateArgs);
}
// src/commands/mobile/getCurrentActivity.ts
async function getCurrentActivity() {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `getCurrentActivity` command is only available for mobile platforms.");
}
if (!browser.isAndroid) {
throw new Error("The `getCurrentActivity` command is only available for Android.");
}
return executeMobile(browser, "mobile: getCurrentActivity", {});
}
// src/commands/mobile/getCurrentPackage.ts
async function getCurrentPackage() {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `getCurrentPackage` command is only available for mobile platforms.");
}
if (!browser.isAndroid) {
throw new Error("The `getCurrentPackage` command is only available for Android.");
}
return executeMobile(browser, "mobile: getCurrentPackage", {});
}
// src/commands/mobile/getPerformanceData.ts
async function getPerformanceData(packageName, dataType, dataReadTimeout) {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `getPerformanceData` command is only available for mobile platforms.");
}
if (!browser.isAndroid) {
throw new Error("The `getPerformanceData` command is only available for Android.");
}
return executeMobile(browser, "mobile: getPerformanceData", { packageName, dataType, dataReadTimeout });
}
// src/commands/mobile/getPerformanceDataTypes.ts
async function getPerformanceDataTypes() {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `getPerformanceDataTypes` command is only available for mobile platforms.");
}
if (!browser.isAndroid) {
throw new Error("The `getPerformanceDataTypes` command is only available for Android.");
}
return executeMobile(browser, "mobile: getPerformanceDataTypes", {});
}
// src/commands/mobile/getSystemBars.ts
async function getSystemBars() {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `getSystemBars` command is only available for mobile platforms.");
}
if (!browser.isAndroid) {
throw new Error("The `getSystemBars` command is only available for Android.");
}
return executeMobile(browser, "mobile: getSystemBars", {});
}
// src/commands/mobile/getDisplayDensity.ts
async function getDisplayDensity() {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `getDisplayDensity` command is only available for mobile platforms.");
}
if (!browser.isAndroid) {
throw new Error("The `getDisplayDensity` command is only available for Android.");
}
return executeMobile(browser, "mobile: getDisplayDensity", {});
}
// src/commands/mobile/pressKeyCode.ts
async function pressKeyCode(keycode, metastate, flags) {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `pressKeyCode` command is only available for mobile platforms.");
}
if (!browser.isAndroid) {
throw new Error("The `pressKeyCode` command is only available for Android.");
}
return executeMobile(browser, "mobile: pressKey", { keycode, metastate, flags });
}
// src/commands/mobile/longPressKeyCode.ts
async function longPressKeyCode(keycode, metastate, flags) {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `longPressKeyCode` command is only available for mobile platforms.");
}
if (!browser.isAndroid) {
throw new Error("The `longPressKeyCode` command is only available for Android.");
}
return executeMobile(browser, "mobile: pressKey", { keycode, metastate, flags, isLongPress: true });
}
// src/commands/mobile/sendKeyEvent.ts
async function sendKeyEvent(keycode, metastate) {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `sendKeyEvent` command is only available for mobile platforms.");
}
if (!browser.isAndroid) {
throw new Error("The `sendKeyEvent` command is only available for Android.");
}
const args = { keycode: parseInt(keycode, 10) };
if (metastate !== void 0) {
args.metastate = parseInt(metastate, 10);
}
return executeMobile(browser, "mobile: pressKey", args);
}
// src/commands/mobile/startActivity.ts
async function startActivity(options) {
const browser = this;
if (typeof options !== "object" || options === null) {
throw new Error(
"`startActivity` only accepts an options object in WebdriverIO v10. Use `browser.startActivity({ appPackage, appActivity })`."
);
}
const legacyOptions = options;
if ("appWaitPackage" in legacyOptions || "appWaitActivity" in legacyOptions || "optionalIntentArguments" in legacyOptions) {
throw new Error(
"The `appWaitPackage`, `appWaitActivity`, and `optionalIntentArguments` options were removed from `startActivity` in WebdriverIO v10. They only applied to the removed Appium HTTP endpoint and are not accepted by `mobile: startActivity`."
);
}
if (!browser.isMobile) {
throw new Error("The `startActivity` command is only available for mobile platforms.");
}
if (!browser.isAndroid) {
throw new Error("The `startActivity` command is only available for Android.");
}
const mobileArgs = {
component: `${options.appPackage}/${options.appActivity}`
};
if (options.intentAction !== void 0) {
mobileArgs.action = options.intentAction;
}
if (options.intentCategory !== void 0) {
mobileArgs.categories = options.intentCategory;
}
if (options.intentFlags !== void 0) {
mobileArgs.flags = options.intentFlags;
}
if (options.dontStopAppOnReset !== void 0) {
mobileArgs.stop = options.dontStopAppOnReset !== "true";
}
return executeMobile(browser, "mobile: startActivity", mobileArgs);
}
// src/commands/mobile/openNotifications.ts
async function openNotifications() {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `openNotifications` command is only available for mobile platforms.");
}
if (!browser.isAndroid) {
throw new Error("The `openNotifications` command is only available for Android.");
}
return executeMobile(browser, "mobile: openNotifications", {});
}
// src/commands/mobile/toggleLocationServices.ts
async function toggleLocationServices() {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `toggleLocationServices` command is only available for mobile platforms.");
}
if (!browser.isAndroid) {
throw new Error("The `toggleLocationServices` command is only available for Android.");
}
return executeMobile(browser, "mobile: toggleGps", {});
}
// src/commands/mobile/toggleNetworkSpeed.ts
async function toggleNetworkSpeed(netspeed) {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `toggleNetworkSpeed` command is only available for mobile platforms.");
}
if (!browser.isAndroid) {
throw new Error("The `toggleNetworkSpeed` command is only available for Android.");
}
return executeMobile(browser, "mobile: networkSpeed", { netspeed });
}
// src/commands/mobile/gsmCall.ts
async function gsmCall(phoneNumber, action2) {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `gsmCall` command is only available for mobile platforms.");
}
if (!browser.isAndroid) {
throw new Error("The `gsmCall` command is only available for Android.");
}
return executeMobile(browser, "mobile: gsmCall", { phoneNumber, action: action2 });
}
// src/commands/mobile/gsmSignal.ts
async function gsmSignal(signalStrength) {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `gsmSignal` command is only available for mobile platforms.");
}
if (!browser.isAndroid) {
throw new Error("The `gsmSignal` command is only available for Android.");
}
return executeMobile(browser, "mobile: gsmSignal", { signalStrength });
}
// src/commands/mobile/gsmVoice.ts
async function gsmVoice(state) {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `gsmVoice` command is only available for mobile platforms.");
}
if (!browser.isAndroid) {
throw new Error("The `gsmVoice` command is only available for Android.");
}
return executeMobile(browser, "mobile: gsmVoice", { state });
}
// src/commands/mobile/sendSms.ts
async function sendSms(phoneNumber, message) {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `sendSms` command is only available for mobile platforms.");
}
if (!browser.isAndroid) {
throw new Error("The `sendSms` command is only available for Android.");
}
return executeMobile(browser, "mobile: sendSms", { phoneNumber, message });
}
// src/commands/mobile/fingerPrint.ts
async function fingerPrint(fingerprintId) {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `fingerPrint` command is only available for mobile platforms.");
}
if (!browser.isAndroid) {
throw new Error("The `fingerPrint` command is only available for Android. For iOS, use `touchId` instead.");
}
return executeMobile(browser, "mobile: fingerprint", { fingerprintId });
}
// src/commands/mobile/powerCapacity.ts
async function powerCapacity(percent) {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `powerCapacity` command is only available for mobile platforms.");
}
if (!browser.isAndroid) {
throw new Error("The `powerCapacity` command is only available for Android.");
}
return executeMobile(browser, "mobile: powerCapacity", { percent });
}
// src/commands/mobile/powerAC.ts
async function powerAC(state) {
const browser = this;
if (!browser.isMobile) {
throw new Error("The `powerAC` command is only available for mobile platforms.");
}
if (!browser.isAndroid) {
throw new Error("The `powerAC` command is only available for Android.");
}
return executeMobile(browser, "mobile: powerAC", { state });
}
// src/utils/mobileDialog.ts
var SPRINGBOARD_BUNDLE_ID = "com.apple.springboard";
function toXPathStringLiteral(value) {
if (!value.includes("'")) {
return `'${value}'`;
}
if (!value.includes('"')) {
return `"${value}"`;
}
const concatSeparator = `, "'", `;
return `concat(${value.split("'").map((part) => `'${part}'`).join(concatSeparator)})`;
}
function androidButtonSelector(buttonText) {
return `//android.widget.Button[@text=${toXPathStringLiteral(buttonText)}]`;
}
function assertMobileNativeDialogCommand(browser, commandName) {
if (!browser.isMobile) {
throw new Error(`The \`${commandName}\` command is only available for mobile platforms.`);
}
if (!browser.isIOS && !browser.isAndroid) {
throw new Error(`The \`${commandName}\` command is only available for iOS and Android.`);
}
if (!browser.isNativeContext) {
throw new Error(`The \`${commandName}\` command is only available for mobile platforms in the NATIVE context.`);
}
}
function isMissingDialogError(err) {
if (!(err instanceof Error)) {
return false;
}
const message = err.message.toLowerCase();
const errorCode = ("error" in err && typeof err.error === "string" ? err.error : err.name).toLowerCase();
return message.includes("no such element") || message.includes("no such alert") || message.includes("unable to find") && message.includes("element") || message.includes("could not be located on the page") || errorCode === "no such element" || errorCode === "no such alert";
}
async function interactWithDialog(browser, action2, buttonText, isIOS) {
if (buttonText) {
const selector = isIOS ? `~${buttonText}` : androidButtonSelector(buttonText);
await browser.$(selector, { strict: false }).click();
return;
}
if (action2 === "accept") {
await browser.acceptAlert();
return;
}
await browser.dismissAlert();
}
async function handleMobileDialog(browser, action2, buttonText) {
if (browser.isIOS) {
const { bundleId } = await browser.execute("mobile: activeAppInfo");
try {
await browser.execute("mobile: activateApp", { bundleId: SPRINGBOARD_BUNDLE_ID });
await interactWithDialog(browser, action2, buttonText, true);
} catch (err) {
if (isMissingDialogError(err)) {
return;
}
throw err;
} finally {
if (bundleId) {
await browser.execute("mobile: activateApp", { bundleId });
}
}
return;
}
try {
await interactWithDialog(browser, action2, buttonText, false);
} catch (err) {
if (isMissingDialogError(err)) {
return;
}
throw err;
}
}
// src/commands/mobile/acceptDialog.ts
async function acceptDialog(buttonText) {
const browser = this;
assertMobileNativeDialogCommand(browser, "acceptDialog");
return handleMobileDialog(browser, "accept", buttonText);
}
// src/commands/mobile/dismissDialog.ts
async function dismissDialog(buttonText) {
const browser = this;
assertMobileNativeDialogCommand(browser, "dismissDialog");
return handleMobileDialog(browser, "dismiss", buttonText);
}
// src/commands/element.ts
var element_exports = {};
__export(element_exports, {
$: () => $2,
$$: () => $$2,
addValue: () => addValue,
clearValue: () => clearValue,
click: () => click,
custom$: () => custom$2,
custom$$: () => custom$$2,
doubleClick: () => doubleClick,
dragAndDrop: () => dragAndDrop,
execute: () => execute2,
getAttribute: () => getAttribute,
getCSSProperty: () => getCSSProperty,
getComputedLabel: () => getComputedLabel,
getComputedRole: () => getComputedRole,
getElement: () => getElement2,
getHTML: () => getHTML,
getLocation: () => getLocation,
getProperty: () => getProperty,
getSize: () => getSize,
getTagName: () => getTagName,
getText: () => getText,
getValue: () => getValue,
isClickable: () => isClickable,
isDisplayed: () => isDisplayed,
isEnabled: () => isEnabled,
isEqual: () => isEqual,
isExisting: () => isExisting,
isFocused: () => isFocused,
isSelected: () => isSelected,
isStable: () => isStable,
longPress: () => longPress,
moveTo: () => moveTo,
nextElement: () => nextElement,
parentElement: () => parentElement,
pinch: () => pinch,
previousElement: () => previousElement,
react$: () => react$2,
react$$: () => react$$2,
saveScreenshot: () => saveScreenshot3,
scrollIntoView: () => scrollIntoView,
selectByAttribute: () => selectByAttribute,
selectByIndex: () => selectByIndex,
selectByVisibleText: () => selectByVisibleText,
setFiles: () => setFiles,
setValue: () => setValue,
shadow$: () => shadow$,
shadow$$: () => shadow$$,
tap: () => tap,
waitForClickable: () => waitForClickable,
waitForDisplayed: () => waitForDisplayed,
waitForEnabled: () => waitForEnabled,
waitForExist: () => waitForExist,
waitForStable: () => waitForStable,
waitUntil: () => waitUntil2,
zoom: () => zoom
});
// src/commands/element/$$.ts
var $$2 = $$;
// src/commands/element/$.ts
var $2 = $;
// src/commands/element/addValue.ts
import { CommandRuntimeOptions } from "webdriver";
var VALID_TYPES = ["string", "number"];
function addValue(value, options) {
if (!VALID_TYPES.includes(typeof value)) {
throw new Error(
'The setValue/addValue command only take string or number values. If you like to use special characters, use the "keys" command.'
);
}
if (options) {
return this.elementSendKeys(this.elementId, value.toString(), new CommandRuntimeOptions(options));
}
return this.elementSendKeys(this.elementId, value.toString());
}
// src/commands/element/clearValue.ts
function clearValue() {
return this.elementClear(this.elementId);
}
// src/commands/element/click.ts
import logger23 from "@wdio/logger";
import { getBrowserObject as getBrowserObject18 } from "@wdio/utils";
var log23 = logger23("webdriver");
function click(options) {
if (typeof options !== "undefined") {
if (typeof options !== "object" || Array.isArray(options)) {
throw new TypeError("Options must be an object");
}
return actionClick(this, options);
}
return elementClick(this);
}
async function workaround(element) {
await element.scrollIntoView({ block: "center", inline: "center" });
}
async function elementClick(element) {
try {
return await element.elementClick(element.elementId);
} catch (error) {
let err = error;
if (typeof error === "string") {
err = new Error(error);
}
if (!err.message.includes("element click intercepted")) {
throw err;
}
await workaround(element);
return element.elementClick(element.elementId);
}
}
async function actionClick(element, options) {
const defaultOptions = {
button: 0,
x: 0,
y: 0,
skipRelease: false,
duration: 0
};
const { button, x, y, skipRelease, duration } = { ...defaultOptions, ...options };
if (typeof x !== "number" || typeof y !== "number" || !Number.isInteger(x) || !Number.isInteger(y)) {
throw new TypeError("Coordinates must be integers");
}
if (!buttonValue.includes(button)) {
throw new Error("Button type not supported.");
}
const browser = getBrowserObject18(element);
if (x || y) {
const { width, height } = await browser.getElementRect(element.elementId);
if (x && x < -Math.floor(width / 2) || x && x > Math.floor(width / 2)) {
log23.warn("x would cause a out of bounds error as it goes outside of element");
}
if (y && y < -Math.floor(height / 2) || y && y > Math.floor(height / 2)) {
log23.warn("y would cause a out of bounds error as it goes outside of element");
}
}
const clickNested = async () => {
await browser.action("pointer", {
parameters: { pointerType: browser.isMobile ? "touch" : "mouse" }
}).move({ origin: element, x, y }).down({ button }).pause(duration).up({ button }).perform(skipRelease);
};
try {
return await clickNested();
} catch {
await workaround(element);
return clickNested();
}
}
// src/commands/element/custom$$.ts
import { ELEMENT_KEY as ELEMENT_KEY13 } from "webdriver";
import { getBrowserObject as getBrowserObject19 } from "@wdio/utils";
function custom$$2(strategyName, ...strategyArguments) {
return ElementArray.fromAsyncCallback(async () => {
const browserObject = getBrowserObject19(this);
const strategy = browserObject.strategies.get(strategyName);
if (!strategy) {
throw new Error("No strategy found for " + strategyName);
}
if (!this.elementId) {
throw new Error(`Can't call custom$ on element with selector "${this.selector}" because element wasn't found`);
}
const strategyRef = { strategy, strategyName, strategyArguments: [...strategyArguments, this] };
let res = await browserObject.execute(strategy, ...strategyArguments, this);
if (!Array.isArray(res)) {
res = [res];
}
res = res.filter((el) => !!el && typeof el[ELEMENT_KEY13] === "string");
return res.length ? await getElements.call(this, strategyRef, res) : [];
}, {
selector: strategyName,
foundWith: "custom$$",
parent: this,
props: strategyArguments
});
}
// src/commands/element/custom$.ts
import { ELEMENT_KEY as ELEMENT_KEY14 } from "webdriver";
import { getBrowserObject as getBrowserObject20 } from "@wdio/utils";
async function custom$2(strategyName, ...strategyArguments) {
const browserObject = getBrowserObject20(this);
const strategy = browserObject.strategies.get(strategyName);
if (!strategy) {
throw Error("No strategy found for " + strategyName);
}
if (!this.elementId) {
throw Error(`Can't call custom$ on element with selector "${this.selector}" because element wasn't found`);
}
const strategyRef = { strategy, strategyName, strategyArguments: [...strategyArguments, this] };
let res = await browserObject.execute(strategy, ...strategyArguments, this);
if (Array.isArray(res)) {
res = res[0];
}
if (res && typeof res[ELEMENT_KEY14] === "string") {
return await getElement.call(this, strategyRef, res, { strict: false });
}
return await getElement.call(this, strategyRef, new Error("no such element"), { strict: false });
}
// src/commands/element/doubleClick.ts
import { getBrowserObject as getBrowserObject21 } from "@wdio/utils";
async function doubleClick() {
const browser = getBrowserObject21(this);
return browser.action("pointer", { parameters: { pointerType: "mouse" } }).move({ origin: this }).down().up().pause(10).down().up().perform();
}
// src/commands/element/dragAndDrop.ts
import { getBrowserObject as getBrowserObject22, isLoadedElement } from "@wdio/utils";
async function dragAndDrop(target, options = {}) {
const moveToCoordinates = target;
const moveToElement = await target;
const isMovingToElement = isLoadedElement(moveToElement);
if (
/**
* no target was specified
*/
!moveToElement || /**
* target is not from type element
*/
!isMovingToElement && /**
* and is also not an object with x and y number parameters
*/
(typeof moveToCoordinates.x !== "number" || typeof moveToCoordinates.y !== "number")
) {
throw new Error('command dragAndDrop requires an WebdriverIO Element or and object with "x" and "y" variables as first parameter');
}
const ACTION_BUTTON = 0;
const browser = getBrowserObject22(this);
const defaultOptions = { duration: browser.isMobile ? 250 : 10 };
const { duration } = { ...defaultOptions, ...options };
const origin = this;
const targetOrigin = isMovingToElement ? moveToElement : "pointer";
const targetX = isMovingToElement ? 0 : moveToCoordinates.x;
const targetY = isMovingToElement ? 0 : moveToCoordinates.y;
return browser.action("pointer", {
parameters: { pointerType: browser.isMobile ? "touch" : "mouse" }
}).move({ duration: 0, origin, x: 0, y: 0 }).down({ button: ACTION_BUTTON }).pause(10).move({ duration, origin: targetOrigin, x: targetX, y: targetY }).up({ button: ACTION_BUTTON }).perform();
}
// src/commands/element/execute.ts
async function execute2(script, ...args) {
const scope = this;
await scope.waitForExist();
return execute.call(scope, script, scope, ...args);
}
// src/commands/element/getAttribute.ts
function getAttribute(attributeName) {
return this.getElementAttribute(this.elementId, attributeName);
}
// src/commands/element/getCSSProperty.ts
import cssShorthandProps from "css-shorthand-properties";
import { getBrowserObject as getBrowserObject23 } from "@wdio/utils";
async function getCSSProperty(cssProperty, pseudoElement) {
const getCSSProperty2 = cssShorthandProps.isShorthand(cssProperty) ? getShorthandPropertyCSSValue : getPropertyCSSValue;
const cssValue2 = await getCSSProperty2.call(
this,
{
cssProperty,
pseudoElement
}
);
return parseCSS(cssValue2, cssProperty);
}
async function getShorthandPropertyCSSValue(options) {
const { pseudoElement, cssProperty } = options;
const properties = getShorthandProperties(cssProperty);
if (pseudoElement) {
const cssValues2 = await Promise.all(
properties.map((prop) => getPseudoElementCSSValue(
this,
{
pseudoElement,
cssProperty: prop
}
))
);
return mergeEqualSymmetricalValue(cssValues2);
}
const cssValues = await Promise.all(
properties.map((prop) => this.getElementCSSValue(this.elementId, prop))
);
return mergeEqualSymmetricalValue(cssValues);
}
async function getPropertyCSSValue(options) {
const { pseudoElement, cssProperty } = options;
if (pseudoElement) {
return await getPseudoElementCSSValue(
this,
{
pseudoElement,
cssProperty
}
);
}
return await this.getElementCSSValue(this.elementId, cssProperty);
}
function getShorthandProperties(cssProperty) {
return cssShorthandProps.expand(cssProperty);
}
function mergeEqualSymmetricalValue(cssValues) {
let newCssValues = [...cssValues];
while (newCssValues.length % 2 === 0) {
const mergedValues = [
newCssValues.slice(0, newCssValues.length / 2).join(" "),
newCssValues.slice(newCssValues.length / 2).join(" ")
];
const hasEqualProperties = mergedValues.every((v) => v === mergedValues[0]);
if (!hasEqualProperties) {
break;
}
newCssValues = newCssValues.slice(0, newCssValues.length / 2);
}
return newCssValues.join(" ");
}
async function getPseudoElementCSSValue(elem, options) {
const { cssProperty, pseudoElement } = options;
const read = (elem2, pseudoElement2, cssProperty2) => {
if (typeof elem2.isConnected === "boolean" && !elem2.isConnected) {
throw new Error("stale element reference: element is not attached to the page document");
}
return window.getComputedStyle(elem2, pseudoElement2)[cssProperty2];
};
const cssValue2 = await foreignContextId(elem) ? await elem.execute(read, pseudoElement, cssProperty) : await getBrowserObject23(elem).execute(read, elem, pseudoElement, cssProperty);
return cssValue2;
}
// src/commands/element/getComputedRole.ts
function getComputedRole() {
return this.getElementComputedRole(this.elementId);
}
// src/commands/element/getComputedLabel.ts
function getComputedLabel() {
return this.getElementComputedLabel(this.elementId);
}
// src/commands/element/getElement.ts
async function getElement2() {
return this;
}
// src/commands/element/getHTML.ts
import { ELEMENT_KEY as ELEMENT_KEY15 } from "webdriver";
import { prettify as prettifyFn } from "htmlfy";
import { getBrowserObject as getBrowserObject24 } from "@wdio/utils";
import getHTMLScript from "./scripts/getHTML.js";
import getHTMLShadowScript from "./scripts/getHTMLShadow.js";
var SHADOW_ID_ATTR_NAME = "data-wdio-shadow-id";
var SHADOW_ID_ATTR = `[${SHADOW_ID_ATTR_NAME}]`;
async function getHTML(options = {}) {
const browser = getBrowserObject24(this);
if (typeof options === "boolean") {
throw new Error(
`Passing a boolean to \`getHTML\` was removed in WebdriverIO v10. Use \`element.getHTML({ includeSelectorTag: ${options} })\`.`
);
}
if (typeof options !== "object" || options === null) {
throw new Error("The `getHTML` options parameter must be an object");
}
const { includeSelectorTag, pierceShadowRoot, removeCommentNodes, prettify, excludeElements } = Object.assign({
includeSelectorTag: true,
pierceShadowRoot: true,
removeCommentNodes: true,
prettify: true,
excludeElements: []
}, options);
const basicGetHTML = (elementId, includeSelectorTag2) => {
return browser.execute(getHTMLScript, {
[ELEMENT_KEY15]: elementId
}, includeSelectorTag2, excludeElements);
};
if (pierceShadowRoot && this.isBidi) {
if (globalThis.wdio) {
return globalThis.wdio.executeWithScope(
"getHTML",
this.elementId,
{ includeSelectorTag, pierceShadowRoot, removeCommentNodes, prettify, excludeElements }
);
}
const { load: load2 } = await import("cheerio");
const shadowRootManager = getShadowRootManager(browser);
const context = await contextIdOf(this);
const shadowRootElementPairs = await shadowRootManager.getShadowElementPairsByContextId(context, this.elementId);
const elementsWithShadowRootAndIdVerified = (await Promise.all(
shadowRootElementPairs.map(([elemId, elem]) => executeInContext(browser, context, (elem2) => elem2.tagName, { [ELEMENT_KEY15]: elemId }).then(
() => [elemId, elem],
() => void 0
))
)).filter(Boolean).map(([elemId, shadowId]) => [
elemId,
{ [ELEMENT_KEY15]: elemId },
shadowId ? { [ELEMENT_KEY15]: shadowId } : void 0
]);
const { html, shadowElementHTML } = await this.execute(
getHTMLShadowScript,
includeSelectorTag,
elementsWithShadowRootAndIdVerified
);
const $3 = load2(html);
populateHTML($3, shadowElementHTML.map(({ id, ...props }) => ({
...props,
id,
mode: shadowRootManager.getShadowRootModeById(context, id) || "open"
})));
return sanitizeHTML($3, { removeCommentNodes, prettify, excludeElements });
}
const returnHTML = await basicGetHTML(this.elementId, includeSelectorTag);
return sanitizeHTML(returnHTML, { removeCommentNodes, prettify });
}
function populateHTML($3, shadowElementHTML) {
const shadowElements = $3(SHADOW_ID_ATTR);
if (shadowElements.length === 0) {
return;
}
for (const elem of shadowElements) {
const id = elem.attribs[SHADOW_ID_ATTR_NAME];
const shadowReference = shadowElementHTML.find(({ id: shadowRootId }) => id === shadowRootId);
if (!shadowReference) {
continue;
}
$3(`[${SHADOW_ID_ATTR_NAME}="${id}"]`).append([
`<template shadowrootmode="${shadowReference.mode}">`,
shadowReference.styles && shadowReference.styles.length > 0 ? ` <style>${shadowReference.styles.join("\n")}</style>` : "",
` ${shadowReference.html}`,
"</template>"
].join("\n"));
delete elem.attribs[SHADOW_ID_ATTR_NAME];
}
populateHTML($3, shadowElementHTML);
}
function removeCommentNodesDeep($3, node) {
$3(node).contents().each((_, child) => {
if (child.type === "comment") {
$3(child).remove();
return;
}
removeCommentNodesDeep($3, child);
});
}
function sanitizeHTML($3, options = {}) {
const isCheerioObject = $3 && typeof $3 !== "string";
if (isCheerioObject) {
for (const elemToRemove of options.excludeElements || []) {
$3(elemToRemove).remove();
}
if (options.removeCommentNodes) {
removeCommentNodesDeep($3, $3.root()[0]);
}
}
let returnHTML = isCheerioObject ? $3("body").html() : $3;
if (!isCheerioObject && options.removeCommentNodes && returnHTML) {
returnHTML = returnHTML.replace(/<!--[\s\S]*?-->/g, "");
}
return options.prettify ? prettifyFn(returnHTML) : returnHTML;
}
// src/commands/element/getLocation.ts
async function getLocation(prop) {
const { x, y } = await getElementRect(this);
const location2 = { x, y };
if (prop === "x" || prop === "y") {
return location2[prop];
}
return location2;
}
// src/commands/element/getProperty.ts
function getProperty(property) {
return this.getElementProperty(this.elementId, property);
}
// src/commands/element/getSize.ts
async function getSize(prop) {
const rect = await getElementRect(this);
if (prop && typeof rect[prop] === "number") {
return rect[prop];
}
return {
width: rect.width,
height: rect.height
};
}
// src/commands/element/getTagName.ts
function getTagName() {
return this.getElementTagName(this.elementId);
}
// src/commands/element/getText.ts
function getText() {
return this.getElementText(this.elementId);
}
// src/commands/element/getValue.ts
function getValue() {
const value = this.getElementProperty(this.elementId, "value");
return value.then((res) => typeof res === "string" ? res : "");
}
// src/commands/element/isClickable.ts
import { ELEMENT_KEY as ELEMENT_KEY16 } from "webdriver";
import { getBrowserObject as getBrowserObject25 } from "@wdio/utils";
import isElementClickableScript from "./scripts/isElementClickable.js";
async function isClickable() {
if (!await this.isDisplayed()) {
return false;
}
if (this.isMobile && this.isNativeContext) {
throw new Error("Method not supported in mobile native environment. It is unlikely that you need to use this command.");
}
if (await foreignContextId(this)) {
return this.execute(isElementClickableScript);
}
const browser = getBrowserObject25(this);
return browser.execute(isElementClickableScript, {
[ELEMENT_KEY16]: this.elementId
});
}
// src/commands/element/isDisplayed.ts
import { getBrowserObject as getBrowserObject26 } from "@wdio/utils";
import isElementDisplayedLegacyScript from "./scripts/isElementDisplayed.js";
import isElementInViewportScript from "./scripts/isElementInViewport.js";
async function isDisplayed(commandParams = DEFAULT_PARAMS) {
const browser = getBrowserObject26(this);
if (!await hasElementId(this)) {
return false;
}
if (browser.isMobile && (browser.isNativeContext || browser.isWindowsApp || browser.isMacApp)) {
if (commandParams?.withinViewport) {
throw new Error(
"Cannot determine element visibility within viewport for native mobile apps as it is not feasible to determine full vertical and horizontal application bounds. In most cases a basic visibility check should suffice."
);
}
return await this.isElementDisplayed(this.elementId);
}
let hadToFallback = false;
const [isDisplayed2, displayProperty] = await Promise.all([
browser.execute(function checkVisibility(elem, params) {
if (typeof elem.checkVisibility === "function") {
return elem.checkVisibility(params);
}
return null;
}, this, {
...DEFAULT_PARAMS,
...commandParams
}).then((result) => {
if (result === null) {
hadToFallback = true;
return browser.execute(isElementDisplayedLegacyScript, this);
}
return result;
}),
browser.execute(function(elem) {
try {
const style = window.getComputedStyle(elem);
return { value: style?.display ?? "" };
} catch {
if (typeof elem.isConnected === "boolean" && !elem.isConnected) {
throw new Error("stale element reference: element is not attached to the page document");
}
return { value: "" };
}
}, this)
]);
const hasDisplayContentsCSSProperty = displayProperty.value === "contents";
const shouldRecheckContentVisibility = !hadToFallback && hasDisplayContentsCSSProperty;
const finalResponse = shouldRecheckContentVisibility ? await browser.execute(isElementDisplayedLegacyScript, this).catch(() => false) : isDisplayed2;
if (finalResponse && commandParams?.withinViewport) {
return browser.execute(isElementInViewportScript, this);
}
return finalResponse;
}
var DEFAULT_PARAMS = {
withinViewport: false,
contentVisibilityAuto: true,
opacityProperty: true,
visibilityProperty: true
};
// src/commands/element/isEnabled.ts
function isEnabled() {
return this.isElementEnabled(this.elementId);
}
// src/commands/element/isEqual.ts
import { getBrowserObject as getBrowserObject27 } from "@wdio/utils";
async function isEqual(el) {
const browser = getBrowserObject27(this);
if (browser.isMobile) {
const context = await browser.getContext().catch(() => void 0);
const contextId = typeof context === "string" ? context : context?.id;
if (contextId && contextId.toLowerCase().includes("native")) {
return this.elementId === el.elementId;
}
}
let result;
try {
result = await browser.execute(
/* istanbul ignore next */
function(el1, el2) {
return el1 === el2;
},
this,
el
);
} catch {
result = false;
}
return result;
}
// src/commands/element/isExisting.ts
async function isExisting() {
if (!this.selector) {
return this.getElementTagName(this.elementId).then(
() => true,
() => false
);
}
const elements = await findAllAgain(this);
return elements.length > (this.index ?? 0);
}
// src/commands/element/isFocused.ts
import { getBrowserObject as getBrowserObject28 } from "@wdio/utils";
import isFocusedScript from "./scripts/isFocused.js";
async function isFocused() {
const browser = await getBrowserObject28(this);
return browser.execute(isFocusedScript, this);
}
// src/commands/element/isSelected.ts
function isSelected() {
return this.isElementSelected(this.elementId);
}
// src/commands/element/isStable.ts
import { getBrowserObject as getBrowserObject29 } from "@wdio/utils";
import isElementStable from "./scripts/isElementStable.js";
async function isStable() {
const browser = getBrowserObject29(this);
if (browser.isMobile && browser.isNativeContext) {
throw new Error("The `isStable` command is only available for desktop and mobile browsers.");
}
return await browser.execute(isElementStable, this);
}
// src/commands/element/moveTo.ts
import logger24 from "@wdio/logger";
import { getBrowserObject as getBrowserObject30 } from "@wdio/utils";
var log24 = logger24("webdriver");
async function moveTo({ xOffset, yOffset } = {}) {
const browser = getBrowserObject30(this);
if (xOffset || yOffset) {
const { width, height } = await this.getElementRect(this.elementId);
if (xOffset && xOffset < -Math.floor(width / 2) || xOffset && xOffset > Math.floor(width / 2)) {
log24.warn("xOffset would cause a out of bounds error as it goes outside of element");
}
if (yOffset && yOffset < -Math.floor(height / 2) || yOffset && yOffset > Math.floor(height / 2)) {
log24.warn("yOffset would cause a out of bounds error as it goes outside of element");
}
}
const held = await foreignContext(this);
if (held) {
await held.execute((elem) => {
elem.scrollIntoView({ block: "nearest", inline: "nearest" });
}, this);
}
const moveToNested = async () => {
await browser.action("pointer", { parameters: { pointerType: "mouse" } }).move({ origin: this, x: xOffset || 0, y: yOffset || 0 }).perform();
};
try {
await moveToNested();
} catch {
await this.scrollIntoView({ block: "center", inline: "center" });
await moveToNested();
}
}
// src/commands/element/nextElement.ts
function nextElement() {
return this.$(
/* istanbul ignore next */
function nextElement2() {
return this.nextElementSibling;
}
);
}
// src/commands/element/parentElement.ts
function parentElement() {
return this.$(
/* istanbul ignore next */
function parentElement2() {
return this.parentElement;
}
);
}
// src/commands/element/previousElement.ts
function previousElement() {
return this.$(
/* istanbul ignore next */
function previousElement2() {
return this.previousElementSibling;
}
);
}
// src/commands/element/react$$.ts
import { getBrowserObject as getBrowserObject31 } from "@wdio/utils";
import { waitToLoadReact as waitToLoadReact3, react$$ as react$$Script2 } from "./scripts/react.js";
function react$$2(selector, { props = {}, state = {} } = {}) {
return ElementArray.fromAsyncCallback(async () => {
const browser = await getBrowserObject31(this);
await this.execute(reactScript);
await this.execute(waitToLoadReact3);
const res = await browser.execute(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
react$$Script2,
selector,
props,
state,
this
);
return getElements.call(this, selector, res, { isReactElement: true, reactOptions: { props, state } });
}, {
selector,
foundWith: "react$$",
parent: this,
/**
* the arguments after the selector, so that
* `parent[foundWith](selector, ...props)` runs the same query again
*/
props: [{ props, state }]
});
}
// src/commands/element/react$.ts
import { getBrowserObject as getBrowserObject32 } from "@wdio/utils";
import { waitToLoadReact as waitToLoadReact4, react$ as react$Script2 } from "./scripts/react.js";
async function react$2(selector, { props = {}, state = {} } = {}) {
const browser = await getBrowserObject32(this);
await this.execute(reactScript);
await this.execute(waitToLoadReact4);
const res = await browser.execute(
react$Script2,
selector,
props,
state,
this
);
return getElement.call(this, selector, res, { isReactElement: true, strict: false, reactOptions: { props, state } });
}
// src/commands/element/saveScreenshot.ts
async function saveScreenshot3(filepath) {
return environment.value.saveElementScreenshot.call(this, filepath);
}
// src/commands/element/scrollIntoView.ts
import logger25 from "@wdio/logger";
import { getBrowserObject as getBrowserObject33 } from "@wdio/utils";
var log25 = logger25("webdriverio");
var VERIFY_TOLERANCE_PX = 1;
async function scrollIntoView(options = { block: "start", inline: "nearest" }) {
const browser = getBrowserObject33(this);
if (browser.isMobile) {
if (await browser.isNativeContext) {
return nativeMobileScrollIntoView({
browser,
element: this,
options: options || {}
});
}
return scrollIntoViewWeb.call(this, options);
}
if (options === true) {
options = { block: "start", inline: "nearest" };
}
if (options === false) {
options = { block: "end", inline: "nearest" };
}
try {
const measure = async () => browser.execute((elem) => {
const { left, top, width, height } = elem.getBoundingClientRect();
let isPainted = false;
try {
const cx = left + width / 2;
const cy = top + height / 2;
const deepElementsFromPoint = (root) => {
const hits = root.elementsFromPoint(cx, cy);
const topHit = hits[0];
return topHit?.shadowRoot ? deepElementsFromPoint(topHit.shadowRoot) : hits;
};
isPainted = deepElementsFromPoint(document).includes(elem);
} catch {
}
return {
elemRect: {
x: left + window.scrollX,
y: top + window.scrollY,
width,
height
},
viewport: {
width: window.innerWidth,
height: window.innerHeight
},
scroll: {
x: window.scrollX,
y: window.scrollY
},
isPainted
};
}, this);
const getVisibility = ({ elemRect, viewport, scroll: { x: windowScrollX, y: windowScrollY } }) => {
const TOL = VERIFY_TOLERANCE_PX;
const isVisibleY = elemRect.y >= windowScrollY - TOL && elemRect.y + elemRect.height <= windowScrollY + viewport.height + TOL;
const isVisibleX = elemRect.x >= windowScrollX - TOL && elemRect.x + elemRect.width <= windowScrollX + viewport.width + TOL;
const spansViewportY = elemRect.y <= windowScrollY + TOL && elemRect.y + elemRect.height >= windowScrollY + viewport.height - TOL;
const spansViewportX = elemRect.x <= windowScrollX + TOL && elemRect.x + elemRect.width >= windowScrollX + viewport.width - TOL;
return {
y: isVisibleY || spansViewportY,
x: isVisibleX || spansViewportX
};
};
const computeDelta = (measurement) => {
const { elemRect, viewport, scroll: { x: windowScrollX, y: windowScrollY } } = measurement;
const targetByOption = {
start: { y: elemRect.y, x: elemRect.x },
center: {
y: elemRect.y - (viewport.height - elemRect.height) / 2,
x: elemRect.x - (viewport.width - elemRect.width) / 2
},
end: {
y: elemRect.y - (viewport.height - elemRect.height),
x: elemRect.x - (viewport.width - elemRect.width)
}
};
let [deltaX, deltaY] = [targetByOption.start.x, targetByOption.start.y];
if (options && typeof options === "object") {
const { block, inline } = options;
const isVisible = getVisibility(measurement);
if (block === "nearest") {
if (isVisible.y) {
deltaY = windowScrollY;
} else {
const nearestYDistance = Math.min(...Object.values(targetByOption).map((delta) => Math.abs(delta.y - windowScrollY)));
deltaY = Object.values(targetByOption).find((delta) => Math.abs(delta.y - windowScrollY) === nearestYDistance).y;
}
} else if (block) {
deltaY = targetByOption[block].y;
}
if (inline === "nearest") {
if (isVisible.x) {
deltaX = windowScrollX;
} else {
const nearestXDistance = Math.min(...Object.values(targetByOption).map((delta) => Math.abs(delta.x - windowScrollX)));
deltaX = Object.values(targetByOption).find((delta) => Math.abs(delta.x - windowScrollX) === nearestXDistance).x;
}
} else if (inline) {
deltaX = targetByOption[inline].x;
}
}
return {
deltaX: Math.round(deltaX - windowScrollX),
deltaY: Math.round(deltaY - windowScrollY)
};
};
const initialMeasurement = await measure();
const initialDelta = computeDelta(initialMeasurement);
if (initialMeasurement.isPainted && initialDelta.deltaX === 0 && initialDelta.deltaY === 0) {
return;
}
const wheelScroll = (deltaX, deltaY) => browser.action("wheel").scroll({ duration: 0, x: 0, y: 0, deltaX, deltaY, origin: this }).perform();
if (initialMeasurement.isPainted) {
await wheelScroll(initialDelta.deltaX, initialDelta.deltaY);
} else {
await wheelScroll(0, 0);
const remainingDelta = computeDelta(await measure());
if (remainingDelta.deltaX !== 0 || remainingDelta.deltaY !== 0) {
await wheelScroll(remainingDelta.deltaX, remainingDelta.deltaY);
}
}
await browser.execute(async (elem) => {
try {
let last = elem.getBoundingClientRect();
let stableFrames = 0;
let totalFrames = 0;
const maxFrames = 60;
await new Promise((resolve) => {
const check = () => {
totalFrames++;
const current = elem.getBoundingClientRect();
if (current.top === last.top && current.left === last.left) {
stableFrames++;
} else {
stableFrames = 0;
last = current;
}
if (stableFrames >= 2 || totalFrames >= maxFrames) {
return resolve();
}
requestAnimationFrame(check);
};
requestAnimationFrame(check);
});
} catch {
}
}, this);
const finalMeasurement = await measure();
if (!finalMeasurement.isPainted) {
log25.warn(
"scrollIntoView via the WebDriver Actions API did not bring the element into view! Re-attempting using `Element.scrollIntoView` via Web API."
);
await scrollIntoViewWeb.call(this, options);
}
} catch (err) {
log25.warn(
`Failed to execute "scrollIntoView" using WebDriver Actions API: ${err.message}!
Re-attempting using \`Element.scrollIntoView\` via Web API.`
);
await scrollIntoViewWeb.call(this, options);
}
}
async function mobileScrollUntilVisible({
browser,
direction,
duration,
element,
maxScrolls,
percent,
scrollableElement
}) {
let isVisible = false;
let hasScrolled = false;
let scrolls = 0;
while (!isVisible && scrolls < maxScrolls) {
try {
isVisible = await element.isDisplayed();
} catch (err) {
if (err instanceof StrictSelectorError) {
throw err;
}
isVisible = false;
}
if (isVisible) {
break;
}
await browser.swipe({
direction,
...duration ? { duration } : {},
...percent ? { percent } : {},
...scrollableElement ? { scrollableElement } : {}
});
hasScrolled = true;
scrolls++;
}
return { hasScrolled, isVisible };
}
async function nativeMobileScrollIntoView({
browser,
element,
options
}) {
const defaultOptions = {
maxScrolls: 10,
direction: "up" /* Up */
};
const mobileOptions = {
...defaultOptions,
...options || {}
};
const { hasScrolled, isVisible } = await mobileScrollUntilVisible({
browser,
element,
maxScrolls: mobileOptions.maxScrolls,
direction: mobileOptions.direction,
...mobileOptions?.duration ? { duration: mobileOptions.duration } : {},
...mobileOptions?.percent ? { percent: mobileOptions.percent } : {},
...mobileOptions?.scrollableElement ? { scrollableElement: mobileOptions.scrollableElement } : {}
});
if (hasScrolled && isVisible) {
return browser.pause(1e3);
} else if (isVisible) {
return;
}
throw new Error(`Element not found within scroll limit of ${mobileOptions.maxScrolls} scrolls by scrolling "${mobileOptions.direction}". Are you sure the element is within the scrollable element or the direction is correct? You can change the scrollable element or direction like this:
await elem.scrollIntoView({
direction: 'left' // possible options are: 'up|down|left|right'
scrollableElement: $('#scrollable'),
});
`);
}
function scrollIntoViewWeb(options = { block: "start", inline: "nearest" }) {
const browser = getBrowserObject33(this);
return browser.execute(
(elem, options2) => elem.scrollIntoView(options2),
this,
options
);
}
// src/commands/element/selectByAttribute.ts
import { ELEMENT_KEY as ELEMENT_KEY17 } from "webdriver";
async function selectByAttribute(attribute, value) {
value = typeof value === "number" ? value.toString() : value;
const normalized = `[normalize-space(@${attribute.trim()}) = "${value.trim()}"]`;
let optionElement;
await this.waitUntil(async () => {
optionElement = await this.findElementFromElement(
this.elementId,
"xpath",
`./option${normalized}|./optgroup/option${normalized}`
);
return ELEMENT_KEY17 in optionElement;
}, {
timeoutMsg: `Option with attribute "${attribute}=${value}" not found.`
});
return this.elementClick(getElementFromResponse(optionElement));
}
// src/commands/element/selectByIndex.ts
async function selectByIndex(index) {
if (index < 0) {
throw new Error("Index needs to be 0 or any other positive number");
}
const fetchOptionElements = async () => {
return this.findElementsFromElement(this.elementId, "css selector", "option");
};
let optionElements = [];
await this.waitUntil(async () => {
optionElements = await fetchOptionElements();
return optionElements.length > 0;
}, {
timeoutMsg: "Select element doesn't contain any option element"
});
await this.waitUntil(async () => {
optionElements = await fetchOptionElements();
return typeof optionElements[index] !== "undefined";
}, {
timeoutMsg: `Option with index "${index}" not found. Select element only contains ${optionElements.length} option elements`
});
return this.elementClick(getElementFromResponse(optionElements[index]));
}
// src/commands/element/selectByVisibleText.ts
import { getBrowserObject as getBrowserObject34 } from "@wdio/utils";
async function selectByVisibleText(text) {
text = typeof text === "number" ? text.toString() : text;
const normalized = text.trim().replace(/\s+/, " ");
const formatted = /"/.test(normalized) ? 'concat("' + normalized.split('"').join(`", '"', "`) + '")' : `"${normalized}"`;
const dotFormat = `[. = ${formatted}]`;
const spaceFormat = `[normalize-space(text()) = ${formatted}]`;
const selections = [
`./option${dotFormat}`,
`./option${spaceFormat}`,
`./optgroup/option${dotFormat}`,
`./optgroup/option${spaceFormat}`
];
const optionElement = await this.$(selections.join("|"), { strict: false });
if (!await optionElement.isExisting()) {
const option = await getBrowserObject34(this).execute(function(select, wanted) {
const normalize = (value) => value.replace(/\s+/g, " ").trim();
return Array.from(select.options || []).find((o) => normalize(o.text) === wanted || normalize(o.label) === wanted) || null;
}, this, normalized);
const optionId = option && typeof option === "object" ? option.elementId ?? getElementFromResponse(option) : void 0;
if (optionId) {
return this.elementClick(optionId);
}
}
await optionElement.waitForExist({
timeoutMsg: `Option with text "${text}" not found.`
});
return this.elementClick(getElementFromResponse(optionElement));
}
// src/commands/element/setFiles.ts
import path8 from "node:path";
import { getBrowserObject as getBrowserObject35 } from "@wdio/utils";
async function setFiles(files) {
const browser = getBrowserObject35(this);
if (!browser.isBidi) {
throw new Error(
"setFiles requires a WebDriver BiDi session. On a classic session, use setValue with a path the local browser can already see."
);
}
const list = Array.isArray(files) ? files : [files];
if (list.length === 0) {
throw new Error("setFiles requires at least one file path.");
}
const resolved = list.map((file, index) => {
if (typeof file !== "string") {
const where = Array.isArray(files) ? ` at index ${index}` : "";
const received = file === null ? "null" : typeof file;
throw new Error(`setFiles expected a file path string${where}, received ${received}.`);
}
return path8.resolve(file);
});
const context = await contextIdOf(this);
await browser.inputSetFiles({
context,
element: { sharedId: this.elementId },
files: resolved
});
}
// src/commands/element/setValue.ts
import { getBrowserObject as getBrowserObject36 } from "@wdio/utils";
var SET_DIRECTLY = ["range", "date", "datetime-local", "month", "week", "time", "color"];
async function setValue(value, options) {
if ((typeof value === "string" || typeof value === "number") && await setsDirectly(this)) {
const browser = getBrowserObject36(this);
const done = await browser.execute(function setInputValue(elem, newValue) {
if (elem.disabled || elem.readOnly) {
return false;
}
const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value").set;
setter.call(elem, newValue);
elem.dispatchEvent(new Event("input", { bubbles: true }));
elem.dispatchEvent(new Event("change", { bubbles: true }));
return true;
}, this, value.toString());
if (done) {
return;
}
}
await this.clearValue();
await clearStubbornValue(this);
return this.addValue(value, options);
}
async function clearStubbornValue(elem) {
const browser = getBrowserObject36(elem);
if (browser.isMobile && (browser.isNativeContext || browser.isWindowsApp || browser.isMacApp)) {
return;
}
const remaining = await elem.getElementProperty(elem.elementId, "value").catch(() => void 0);
if (typeof remaining !== "string" || remaining === "") {
return;
}
await browser.execute((el) => el.focus(), elem);
await browser.keys([Key.Ctrl, "a"]);
await browser.keys(Key.Backspace);
}
async function setsDirectly(elem) {
const browser = getBrowserObject36(elem);
if (browser.isMobile && (browser.isNativeContext || browser.isWindowsApp || browser.isMacApp)) {
return false;
}
const type = await elem.getElementProperty(elem.elementId, "type").catch(() => void 0);
return typeof type === "string" && SET_DIRECTLY.includes(type);
}
// src/commands/element/shadow$$.ts
import logger26 from "@wdio/logger";
import { getBrowserObject as getBrowserObject37 } from "@wdio/utils";
import { SHADOW_ELEMENT_KEY as SHADOW_ELEMENT_KEY3 } from "webdriver";
import { shadowFnFactory } from "./scripts/shadowFnFactory.js";
// src/utils/findStrategy.ts
import { roleElements } from "aria-query";
import { knownRoles, ROLE_SYNONYMS } from "@wdio/utils";
var DEFAULT_STRATEGY = "css selector";
var DIRECT_SELECTOR_REGEXP = /^(id|css selector|xpath|link text|partial link text|name|tag name|class name|-android uiautomator|-android datamatcher|-android viewmatcher|-android viewtag|-ios uiautomation|-ios predicate string|-ios class chain|accessibility id):(.+)/;
var XPATH_SELECTORS_START = [
"/",
"(",
"../",
"./",
"*/"
];
var NAME_MOBILE_SELECTORS_START = [
"uia",
"xcuielementtype",
"android.widget",
"cyi",
"android.view"
];
var XPATH_SELECTOR_REGEXP = [
// HTML tag
/^([a-z0-9|-]*)/,
// optional . or # + class or id
/(?:(\.|#)(-?[_a-zA-Z]+[_a-zA-Z0-9-]*))?/,
// optional [attribute-name="attribute-selector"]
/(?:\[(-?[_a-zA-Z]+[_a-zA-Z0-9-]*)(?:=(?:"|')([a-zA-Z0-9\-_. ]+)(?:"|'))?\])?/,
// optional case insensitive
/(\.)?/,
// *=query or =query
/(\*)?=(.+)$/
];
var IMAGEPATH_MOBILE_SELECTORS_ENDSWITH = [
".jpg",
".jpeg",
".gif",
".png",
".bmp",
".svg"
];
var defineStrategy = function(selector) {
if (typeof selector === "object") {
if (JSON.stringify(selector).indexOf("test.espresso.matcher.ViewMatchers") < 0) {
return "-android datamatcher";
}
return "-android viewmatcher";
}
const stringSelector = selector;
if (DIRECT_SELECTOR_REGEXP.test(stringSelector)) {
return "directly";
}
if (IMAGEPATH_MOBILE_SELECTORS_ENDSWITH.some((path9) => {
const selector2 = stringSelector.toLowerCase();
return selector2.endsWith(path9) && selector2 !== path9;
})) {
return "-image";
}
if (XPATH_SELECTORS_START.some((option) => stringSelector.startsWith(option))) {
return "xpath";
}
if (stringSelector.startsWith("=")) {
return "link text";
}
if (stringSelector.startsWith("*=")) {
return "partial link text";
}
if (stringSelector.startsWith("id=")) {
return "id";
}
if (stringSelector.startsWith(DEEP_SELECTOR)) {
return "shadow";
}
if (stringSelector.startsWith(ARIA_SELECTOR)) {
return "aria";
}
if (stringSelector.startsWith(ROLE_SELECTOR)) {
return "role selector";
}
if (stringSelector.startsWith("android=")) {
return "-android uiautomator";
}
if (stringSelector.startsWith("ios=")) {
return "-ios uiautomation";
}
if (stringSelector.startsWith("~")) {
return "accessibility id";
}
if (NAME_MOBILE_SELECTORS_START.some((option) => stringSelector.toLowerCase().startsWith(option))) {
return "class name";
}
if (stringSelector.search(/<[0-9a-zA-Z-]+( \/)*>/g) >= 0) {
return "tag name";
}
if (stringSelector.search(/^\[name=(?:"(.[^"]*)"|'(.[^']*)')]$/) >= 0) {
return "name";
}
if (selector === ".." || selector === ".") {
return "xpath";
}
if (stringSelector.match(new RegExp(XPATH_SELECTOR_REGEXP.map((rx) => rx.source).join("")))) {
return "xpath extended";
}
if (/^\[role=[A-Za-z]+]$/.test(stringSelector)) {
return "role";
}
};
function escapeXPathString(value) {
if (!value.includes('"')) {
return `"${value}"`;
}
if (!value.includes("'")) {
return `'${value}'`;
}
const parts = [];
for (const segment of value.split('"')) {
if (segment.length > 0) {
parts.push(`"${segment}"`);
}
parts.push(`'"'`);
}
parts.pop();
return `concat(${parts.join(", ")})`;
}
function getAriaXPathSelector(label) {
const escaped = escapeXPathString(label);
const conditions = [
// aria label is recevied by other element with aria-labelledBy
// https://www.w3.org/TR/accname-1.1/#step2B
`.//*[@aria-labelledby][id(@aria-labelledby)[normalize-space(text()) = ${escaped}]]`,
// aria label is recevied by other element with aria-labelledBy
// https://www.w3.org/TR/accname-1.1/#step2B
`.//*[@aria-describedby][id(@aria-describedby)[normalize-space(text()) = ${escaped}]]`,
// element has direct aria label
// https://www.w3.org/TR/accname-1.1/#step2C
`.//*[@aria-label = ${escaped}]`,
// input and textarea with a label
// https://www.w3.org/TR/accname-1.1/#step2D
`.//input[@id][@id = (//label[normalize-space() = ${escaped}]/@for)]`,
`.//textarea[@id][@id = (//label[normalize-space() = ${escaped}]/@for)]`,
// input and textarea with a label as parent
// https://www.w3.org/TR/accname-1.1/#step2D
`.//input[ancestor::label[normalize-space(text()) = ${escaped}]]`,
`.//textarea[ancestor::label[normalize-space(text()) = ${escaped}]]`,
// aria label is received by a placeholder
// https://www.w3.org/TR/accname-1.1/#step2D
`.//input[@placeholder=${escaped}]`,
`.//textarea[@placeholder=${escaped}]`,
// aria label is received by a aria-placeholder
// https://www.w3.org/TR/accname-1.1/#step2D
`.//input[@aria-placeholder=${escaped}]`,
`.//textarea[@aria-placeholder=${escaped}]`,
// aria label is received by a title
// https://www.w3.org/TR/accname-1.1/#step2D
`.//*[not(self::label)][@title=${escaped}]`,
// images with an alt tag
// https://www.w3.org/TR/accname-1.1/#step2D
`.//img[@alt=${escaped}]`,
// aria label is received from element text content
// https://www.w3.org/TR/accname-1.1/#step2G
`.//*[not(self::label)][normalize-space(text()) = ${escaped}]`
];
return conditions.join(" | ");
}
var ROLE_SELECTOR_REGEXP = /^role\/([a-zA-Z]+)(?:\[name=(?:"((?:[^"\\]|\\.)*)"|'((?:[^'\\]|\\.)*)')\])?$/;
function editDistance(a, b) {
const row = Array.from({ length: b.length + 1 }, (_, i) => i);
for (let i = 1; i <= a.length; i++) {
let previous = row[0];
row[0] = i;
for (let j = 1; j <= b.length; j++) {
const current = row[j];
row[j] = Math.min(row[j] + 1, row[j - 1] + 1, previous + (a[i - 1] === b[j - 1] ? 0 : 1));
previous = current;
}
}
return row[b.length];
}
function parseRoleSelector(selector) {
const match = selector.match(ROLE_SELECTOR_REGEXP);
if (!match) {
throw new Error(
`InvalidSelectorMatch. Strategy 'role' has failed to match '${selector}'. Expected \`role/<role>\` or \`role/<role>[name="<accessible name>"]\`.`
);
}
const role = match[1];
const roles = knownRoles();
if (!roles.includes(role)) {
const closest = roles.reduce((best, candidate) => editDistance(role, candidate) < editDistance(role, best) ? candidate : best);
throw new Error(
`InvalidSelectorMatch. Strategy 'role' has failed to match '${selector}': "${role}" is not an ARIA role. Did you mean "${closest}"?`
);
}
const canonicalRole = ROLE_SYNONYMS[role] || role;
const rawName = match[2] ?? match[3];
return rawName === void 0 ? { role: canonicalRole } : { role: canonicalRole, name: rawName.replace(/\\(.)/g, "$1") };
}
var findStrategy = function(selector, isMobile, isBidi2) {
const stringSelector = selector;
let using = DEFAULT_STRATEGY;
let value = selector;
switch (defineStrategy(selector)) {
// user has specified locator strategy directly
case "directly": {
const match = stringSelector.match(DIRECT_SELECTOR_REGEXP);
if (!match) {
throw new Error("InvalidSelectorStrategy");
}
using = match[1];
value = match[2];
break;
}
case "xpath": {
using = "xpath";
break;
}
case "id": {
using = "id";
value = stringSelector.slice(3);
break;
}
case "link text": {
using = "link text";
value = stringSelector.slice(1);
break;
}
case "partial link text": {
using = "partial link text";
value = stringSelector.slice(2);
break;
}
case "shadow":
using = "shadow";
value = stringSelector.slice(DEEP_SELECTOR.length);
break;
case "role selector": {
using = "role";
value = JSON.stringify(parseRoleSelector(stringSelector));
break;
}
case "aria": {
const label = stringSelector.slice(ARIA_SELECTOR.length);
if (isBidi2) {
using = "aria";
value = label;
} else {
using = "xpath";
value = getAriaXPathSelector(label);
}
break;
}
case "-android uiautomator": {
using = "-android uiautomator";
value = stringSelector.slice(8);
break;
}
case "-android datamatcher": {
using = "-android datamatcher";
value = JSON.stringify(value);
break;
}
case "-android viewmatcher": {
using = "-android viewmatcher";
value = JSON.stringify(value);
break;
}
case "-ios uiautomation": {
using = "-ios uiautomation";
value = stringSelector.slice(4);
break;
}
case "accessibility id": {
using = "accessibility id";
value = stringSelector.slice(1);
break;
}
case "class name": {
using = "class name";
break;
}
case "tag name": {
using = "tag name";
value = stringSelector.replace(/<|>|\/|\s/g, "");
break;
}
case "name": {
if (isMobile) {
const match = stringSelector.match(/^\[name=(?:"(.[^"]*)"|'(.[^']*)')]$/);
if (!match) {
throw new Error(`InvalidSelectorMatch. Strategy 'name' has failed to match '${stringSelector}'`);
}
using = "name";
value = match[1] || match[2];
}
break;
}
case "xpath extended": {
using = "xpath";
const match = stringSelector.match(new RegExp(XPATH_SELECTOR_REGEXP.map((rx) => rx.source).join("")));
if (!match) {
throw new Error(`InvalidSelectorMatch: Strategy 'xpath extended' has failed to match '${stringSelector}'`);
}
const PREFIX_NAME = { ".": "class", "#": "id" };
const conditions = [];
const [
tag,
prefix,
name,
attrName,
attrValue,
insensitive,
partial,
query
] = match.slice(1);
if (prefix) {
if (prefix === ".") {
conditions.push(`contains(concat(" ",@${PREFIX_NAME[prefix]}," "), " ${name} ")`);
} else {
conditions.push(`contains(@${PREFIX_NAME[prefix]}, "${name}")`);
}
}
if (attrName) {
conditions.push(
attrValue ? `contains(@${attrName}, "${attrValue}")` : `@${attrName}`
);
}
const partialNot = ` and not(${`.//${tag || "*"}${conditions.length ? `[${conditions.join(" and ")}]` : ""}`})`;
if (insensitive) {
conditions.push(
partial ? `contains(translate(., "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz"), "${query.toLowerCase()}")${partialNot}` : `normalize-space(translate(text(), "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz")) = "${query.toLowerCase()}"`
);
} else {
conditions.push(partial ? `contains(., "${query}")${partialNot}` : `normalize-space(text()) = "${query}"`);
}
const getValue2 = () => `.//${tag || "*"}[${conditions.join(" and ")}]`;
value = getValue2();
if (!partial) {
conditions.pop();
conditions.push(
`not(${value})`,
`normalize-space() = "${insensitive ? query.toLowerCase() : query}"`
);
value = value + " | " + getValue2();
}
break;
}
case "-image": {
using = "-image";
value = environment.value.readFileSync(stringSelector, { encoding: "base64" });
break;
}
case "role": {
const match = stringSelector.match(/^\[role=(.+)\]/);
if (!match) {
throw new Error(`InvalidSelectorMatch. Strategy 'role' has failed to match '${stringSelector}'`);
}
using = "css selector";
value = createRoleBaseXpathSelector(match[1]);
break;
}
}
return { using, value };
};
var createRoleBaseXpathSelector = (role) => {
const locatorArr = [];
roleElements.get(role)?.forEach((value) => {
let locator;
let tagAttribute, tagAttributevalue;
const tagname = value.name;
if (value.attributes instanceof Array) {
value.attributes.forEach((val) => {
tagAttribute = val.name;
tagAttributevalue = val.value;
});
}
if (!tagAttribute) {
locator = tagname;
} else if (!tagAttributevalue) {
locator = `${tagname}[${tagAttribute}]`;
} else {
locator = `${tagname}[${tagAttribute}="${tagAttributevalue}"]`;
}
locatorArr.push(locator);
});
let xpathLocator = `[role="${role}"]`;
locatorArr.forEach((loc) => {
xpathLocator += "," + loc;
});
return xpathLocator;
};
// src/commands/element/shadow$$.ts
var log26 = logger26("webdriverio");
function shadow$$(selector) {
return ElementArray.fromAsyncCallback(async () => {
const browser = getBrowserObject37(this);
if (await foreignContext(this)) {
return [...await this.$$(shadowFnFactory(selector, true))];
}
try {
const shadowRoot = await browser.getElementShadowRoot(this.elementId);
const { using, value } = findStrategy(selector, this.isMobile);
const res = await browser.findElementsFromShadowRoot(shadowRoot[SHADOW_ELEMENT_KEY3], using, value);
return await getElements.call(this, selector, res, { isShadowElement: true });
} catch (err) {
log26.warn(
`Failed to fetch element within shadow DOM using WebDriver command: ${err.message}!
Falling back to JavaScript shim.`
);
const fallback = await this.$$(shadowFnFactory(selector, true));
return [...fallback];
}
}, {
selector,
foundWith: "shadow$$",
parent: this,
props: []
});
}
// src/commands/element/shadow$.ts
import logger27 from "@wdio/logger";
import { SHADOW_ELEMENT_KEY as SHADOW_ELEMENT_KEY4 } from "webdriver";
import { shadowFnFactory as shadowFnFactory2 } from "./scripts/shadowFnFactory.js";
import { getBrowserObject as getBrowserObject38 } from "@wdio/utils";
var log27 = logger27("webdriverio");
async function shadow$(selector) {
const browser = getBrowserObject38(this);
if (await foreignContext(this)) {
return this.$(shadowFnFactory2(selector), { strict: false });
}
try {
const shadowRoot = await browser.getElementShadowRoot(this.elementId);
const { using, value } = findStrategy(selector, this.isMobile);
const res = await browser.findElementFromShadowRoot(shadowRoot[SHADOW_ELEMENT_KEY4], using, value);
return getElement.call(this, selector, res, { isShadowElement: true, strict: false });
} catch (err) {
log27.warn(
`Failed to fetch element within shadow DOM using WebDriver command: ${err.message}!
Falling back to JavaScript shim.`
);
return this.$(shadowFnFactory2(selector), { strict: false });
}
}
// src/commands/element/waitForClickable.ts
import { getBrowserObject as getBrowserObject39 } from "@wdio/utils";
async function waitForClickable({
timeout = this.options.waitforTimeout,
interval = this.options.waitforInterval,
reverse = false,
timeoutMsg = `element ("${this.selector}") still ${reverse ? "" : "not "}clickable after ${timeout}ms`
} = {}) {
const browser = getBrowserObject39(this);
if (browser.isMobile && browser.isNativeContext && !browser.capabilities?.browserName) {
throw new Error("The `waitForClickable` command is only available for desktop and mobile browsers.");
}
return this.waitUntil(
async () => reverse !== await this.isClickable(),
{ timeout, timeoutMsg, interval }
);
}
// src/commands/element/waitForDisplayed.ts
function waitForDisplayed({
timeout = this.options.waitforTimeout,
interval = this.options.waitforInterval,
reverse = false,
withinViewport = false,
contentVisibilityAuto = true,
opacityProperty = true,
visibilityProperty = true,
timeoutMsg = `element ("${this.selector}") still ${reverse ? "" : "not "}displayed${withinViewport ? " within viewport" : ""} after ${timeout}ms`
} = {}) {
return this.waitUntil(
async () => reverse !== await this.isDisplayed({ withinViewport, contentVisibilityAuto, opacityProperty, visibilityProperty }),
{ timeout, interval, timeoutMsg }
);
}
// src/commands/element/waitForEnabled.ts
async function waitForEnabled({
timeout = this.options.waitforTimeout,
interval = this.options.waitforInterval,
reverse = false,
timeoutMsg = `element ("${this.selector}") still ${reverse ? "" : "not "}enabled after ${timeout}ms`
} = {}) {
if (!this.elementId && !reverse) {
await this.waitForExist({ timeout, interval, timeoutMsg });
}
return this.waitUntil(
async () => reverse !== await this.isEnabled(),
{ timeout, interval, timeoutMsg }
);
}
// src/commands/element/waitForExist.ts
import { ELEMENT_KEY as ELEMENT_KEY18 } from "webdriver";
async function waitForExist({
timeout = this.options.waitforTimeout,
interval = this.options.waitforInterval,
reverse = false,
timeoutMsg = `element ("${this.selector}") still ${reverse ? "" : "not "}existing after ${timeout}ms`
} = {}) {
const isExisting2 = await this.waitUntil(
async () => reverse !== await this.isExisting(),
{ timeout, interval, timeoutMsg }
);
if (!reverse && isExisting2 && canFindAgain(this)) {
let isCurrentIdValid = false;
if (this.elementId) {
try {
await this.getElementTagName(this.elementId);
isCurrentIdValid = true;
} catch {
}
}
if (!isCurrentIdValid) {
const element = await findElementAgain(this, { wait: true });
this.elementId = element?.elementId;
this[ELEMENT_KEY18] = this.elementId;
}
delete this.error;
}
return isExisting2;
}
// src/commands/element/waitForStable.ts
import { getBrowserObject as getBrowserObject40 } from "@wdio/utils";
async function waitForStable({
timeout = this.options.waitforTimeout,
interval = this.options.waitforInterval,
reverse = false,
timeoutMsg = `element ("${this.selector}") still ${reverse ? "" : "not "}stable after ${timeout}ms`
} = {}) {
let errorMsg;
const browser = getBrowserObject40(this);
if (browser.isMobile && browser.isNativeContext) {
throw new Error("The `waitForStable` command is only available for desktop and mobile browsers.");
}
await this.waitUntil(
async () => {
try {
return reverse !== await this.isStable();
} catch (error) {
if (error instanceof Error) {
errorMsg = error.message;
} else if (typeof error === "string") {
errorMsg = error;
} else {
errorMsg = "The waitForStable command got an unknown error";
}
return !reverse;
}
},
{ timeout, interval, timeoutMsg }
);
if (errorMsg) {
throw Error(errorMsg);
}
}
// src/commands/element/waitUntil.ts
var waitUntil2 = waitUntil;
// src/commands/mobile/longPress.ts
import { getBrowserObject as getBrowserObject41 } from "@wdio/utils";
function longPress(options) {
const browser = getBrowserObject41(this);
if (!browser.isMobile) {
throw new Error("The longPress command is only available for mobile platforms.");
}
if (typeof options !== "undefined" && (typeof options !== "object" || Array.isArray(options))) {
throw new TypeError("Options must be an object");
}
const defaultOptions = {
duration: 1500,
x: 0,
y: 0
};
const { duration, x, y } = { ...defaultOptions, ...options };
if (!browser.isNativeContext && browser.isIOS) {
return browser.execute(
(el, duration2) => {
const touchStart = new TouchEvent("touchstart", {
touches: [new Touch({ identifier: 0, target: el, clientX: 0, clientY: 0 })],
bubbles: true,
cancelable: true
});
el.dispatchEvent(touchStart);
setTimeout(() => {
const touchEnd = new TouchEvent("touchend", {
changedTouches: [new Touch({ identifier: 0, target: el, clientX: 0, clientY: 0 })],
bubbles: true,
cancelable: true
});
el.dispatchEvent(touchEnd);
}, duration2);
},
this,
duration
);
}
return this.click({ duration, x, y });
}
// src/commands/mobile/pinch.ts
import { getBrowserObject as getBrowserObject42 } from "@wdio/utils";
async function pinch(options = {}) {
const browser = getBrowserObject42(this);
if (!browser.isMobile) {
throw new Error("The pinch command is only available for mobile platforms.");
}
const { duration, scale } = validatePinchAndZoomOptions({ browser, gesture: "pinch", options });
const gestureConfig = browser.isIOS ? {
elementId: await this.elementId,
scale,
velocity: -Math.abs(duration)
// Velocity is always negative for iOS pinch
} : {
elementId: await this.elementId,
percent: scale,
speed: calculateAndroidPinchAndZoomSpeed({ browser, duration, scale })
};
return browser.execute(browser.isIOS ? "mobile: pinch" : "mobile: pinchCloseGesture", gestureConfig);
}
// src/commands/mobile/zoom.ts
import { getBrowserObject as getBrowserObject43 } from "@wdio/utils";
async function zoom(options = {}) {
const browser = getBrowserObject43(this);
if (!browser.isMobile) {
throw new Error("The zoom command is only available for mobile platforms.");
}
const { duration, scale } = validatePinchAndZoomOptions({ browser, gesture: "zoom", options });
const gestureConfig = browser.isIOS ? {
elementId: await this.elementId,
scale,
velocity: duration
} : {
elementId: await this.elementId,
percent: scale,
speed: calculateAndroidPinchAndZoomSpeed({ browser, duration, scale })
};
return browser.execute(browser.isIOS ? "mobile: pinch" : "mobile: pinchOpenGesture", gestureConfig);
}
// src/utils/thirdParty/querySelectorShadowDom.ts
function querySelectorAllDeep(findMany, s, r) {
function normalizeSelector2(sel) {
function saveUnmatched() {
if (unmatched) {
if (tokens.length > 0 && /^[~+>]$/.test(tokens[tokens.length - 1])) {
tokens.push(" ");
}
tokens.push(unmatched);
}
}
const tokens = [], state = [0], not_escaped_pattern = /(?:[^\\]|(?:^|[^\\])(?:\\\\)+)$/, whitespace_pattern = /^\s+$/, state_patterns = [
/\s+|\/\*|["'>~+[(]/g,
// general
/\s+|\/\*|["'[\]()]/g,
// [..] set
/\s+|\/\*|["'[\]()]/g,
// (..) set
null,
// string literal (placeholder)
/\*\//g
// comment
];
let match, unmatched, regex, next_match_idx = 0, prev_match_idx;
sel = sel.trim();
while (true) {
unmatched = "";
regex = state_patterns[state[state.length - 1]];
regex.lastIndex = next_match_idx;
match = regex.exec(sel);
if (match) {
prev_match_idx = next_match_idx;
next_match_idx = regex.lastIndex;
if (prev_match_idx < next_match_idx - match[0].length) {
unmatched = sel.substring(
prev_match_idx,
next_match_idx - match[0].length
);
}
if (state[state.length - 1] < 3) {
saveUnmatched();
if (match[0] === "[") {
state.push(1);
} else if (match[0] === "(") {
state.push(2);
} else if (/^["']$/.test(match[0])) {
state.push(3);
state_patterns[3] = new RegExp(match[0], "g");
} else if (match[0] === "/*") {
state.push(4);
} else if (/^[\])]$/.test(match[0]) && state.length > 0) {
state.pop();
} else if (/^(?:\s+|[~+>])$/.test(match[0])) {
if (tokens.length > 0 && !whitespace_pattern.test(tokens[tokens.length - 1]) && state[state.length - 1] === 0) {
tokens.push(" ");
}
if (state[state.length - 1] === 1 && tokens.length === 5 && tokens[2].charAt(tokens[2].length - 1) === "=") {
tokens[4] = " " + tokens[4];
}
if (whitespace_pattern.test(match[0])) {
continue;
}
}
tokens.push(match[0]);
} else {
tokens[tokens.length - 1] += unmatched;
if (not_escaped_pattern.test(tokens[tokens.length - 1])) {
if (state[state.length - 1] === 4) {
if (tokens.length < 2 || whitespace_pattern.test(tokens[tokens.length - 2])) {
tokens.pop();
} else {
tokens[tokens.length - 1] = " ";
}
match[0] = "";
}
state.pop();
}
tokens[tokens.length - 1] += match[0];
}
} else {
unmatched = sel.substr(next_match_idx);
saveUnmatched();
break;
}
}
return tokens.join("").trim();
}
function _querySelectorDeep(selector, root, allElements = null) {
selector = normalizeSelector2(selector);
const lightElement = root.querySelector(selector);
if (document.head.createShadowRoot || document.head.attachShadow) {
if (!findMany && lightElement) {
return lightElement;
}
const selectionsToMake = splitByCharacterUnlessQuoted(selector, ",");
return selectionsToMake.reduce((acc, minimalSelector) => {
if (!findMany && acc) {
return acc;
}
const splitSelector = splitByCharacterUnlessQuoted(minimalSelector.replace(/^\s+/g, "").replace(/\s*([>+~]+)\s*/g, "$1"), " ").filter((entry) => !!entry).map((entry) => splitByCharacterUnlessQuoted(entry, ">"));
const possibleElementsIndex = splitSelector.length - 1;
const lastSplitPart = splitSelector[possibleElementsIndex][splitSelector[possibleElementsIndex].length - 1];
const possibleElements = collectAllElementsDeep(lastSplitPart, root, allElements);
const findElements2 = findMatchingElement(splitSelector, possibleElementsIndex, root);
if (findMany) {
acc = acc.concat(possibleElements.filter(findElements2));
return acc;
}
acc = possibleElements.find(findElements2);
return acc || null;
}, findMany ? [] : null);
}
return !findMany ? lightElement : root.querySelectorAll(selector);
}
function findMatchingElement(splitSelector, possibleElementsIndex, root) {
return (element) => {
let position = possibleElementsIndex;
let parent = element;
let foundElement = false;
while (parent && !isDocumentNode(parent)) {
let foundMatch = true;
if (splitSelector[position].length === 1) {
foundMatch = parent.matches(splitSelector[position]);
} else {
const reversedParts = [].concat(splitSelector[position]).reverse();
let newParent = parent;
for (const part of reversedParts) {
if (!newParent || !newParent.matches(part)) {
foundMatch = false;
break;
}
newParent = findParentOrHost(newParent, root);
}
}
if (foundMatch && position === 0) {
foundElement = true;
break;
}
if (foundMatch) {
position--;
}
parent = findParentOrHost(parent, root);
}
return foundElement;
};
}
function splitByCharacterUnlessQuoted(selector, character) {
return selector.match(/\\?.|^$/g).reduce((p, c) => {
if (c === '"' && !p.sQuote) {
p.quote ^= 1;
p.a[p.a.length - 1] += c;
} else if (c === "'" && !p.quote) {
p.sQuote ^= 1;
p.a[p.a.length - 1] += c;
} else if (!p.quote && !p.sQuote && c === character) {
p.a.push("");
} else {
p.a[p.a.length - 1] += c;
}
return p;
}, { a: [""] }).a;
}
function isDocumentNode(node) {
return node.nodeType === Node.DOCUMENT_FRAGMENT_NODE || node.nodeType === Node.DOCUMENT_NODE;
}
function findParentOrHost(element, root) {
const parentNode = element.parentNode;
return parentNode && parentNode.host && parentNode.nodeType === 11 ? parentNode.host : parentNode === root ? null : parentNode;
}
function collectAllElementsDeep(selector = null, root, cachedElements = null) {
let allElements = [];
if (cachedElements) {
allElements = cachedElements;
} else {
const findAllElements = function(nodes) {
for (let i = 0; i < nodes.length; i++) {
const el = nodes[i];
allElements.push(el);
if (el.shadowRoot) {
findAllElements(el.shadowRoot.querySelectorAll("*"));
}
}
};
const shadowRoot = root.shadowRoot;
if (shadowRoot) {
findAllElements(shadowRoot.querySelectorAll("*"));
}
findAllElements(root.querySelectorAll("*"));
}
return selector ? allElements.filter((el) => el.matches(selector)) : allElements;
}
return _querySelectorDeep(s, r || document);
}
// src/utils/index.ts
import findByRole, { ACCESSIBILITY_API_MISSING } from "./scripts/findByRole.js";
var log28 = logger28("webdriverio");
var INVALID_SELECTOR_ERROR = "selector needs to be typeof `string` or `function`";
var IGNORED_COMMAND_FILE_EXPORTS = ["SESSION_MOCKS", "CDP_SESSIONS"];
var scopes = {
browser: browser_exports,
element: element_exports
};
var applyScopePrototype = (prototype, scope) => {
Object.entries(scopes[scope]).filter(([exportName]) => !IGNORED_COMMAND_FILE_EXPORTS.includes(exportName)).forEach(([commandName, command]) => {
prototype[commandName] = { value: command };
});
};
var getPrototype = (scope) => {
const prototype = {
/**
* used to store the puppeteer instance in the browser scope
*/
puppeteer: { value: null, writable: true }
};
if (scope === "browser") {
prototype.isNativeContext = {
get: function() {
const context = getContextManager(this);
return context.isNativeContext;
}
};
prototype.mobileContext = {
get: function() {
const context = getContextManager(this);
return context.mobileContext;
}
};
}
applyScopePrototype(prototype, scope);
prototype.strategies = { value: /* @__PURE__ */ new Map() };
prototype[WDIO_KIND2] = { value: scope, configurable: true };
return prototype;
};
var getElementFromResponse = (res) => {
if (!res) {
return null;
}
if (res[ELEMENT_KEY19]) {
return res[ELEMENT_KEY19];
}
return null;
};
function sanitizeCSS(value) {
if (!value) {
return value;
}
return value.trim().replace(/'/g, "").replace(/"/g, "").toLowerCase();
}
function parseCSS(cssPropertyValue, cssProperty) {
const parsedValue = {
property: cssProperty,
value: cssPropertyValue.toLowerCase().trim(),
parsed: {}
};
if (parsedValue.value?.indexOf("rgb") === 0) {
parsedValue.value = parsedValue.value.replace(/\s/g, "");
const color = parsedValue.value;
parsedValue.parsed = rgb2hex(parsedValue.value);
parsedValue.parsed.type = "color";
const colorType = /[rgba]+/g.exec(color) || [];
parsedValue.parsed[colorType[0]] = color;
} else if (parsedValue.property === "font-family") {
const font = cssValue(cssPropertyValue);
const string = parsedValue.value;
const value = cssPropertyValue.split(/,/).map(sanitizeCSS);
parsedValue.value = sanitizeCSS(font[0].value || font[0].string);
parsedValue.parsed = { value, type: "font", string };
} else {
try {
const value = cssValue(cssPropertyValue);
if (value.length === 1) {
parsedValue.parsed = value[0];
}
if (parsedValue.parsed.type && parsedValue.parsed.type === "number" && parsedValue.parsed.unit === "") {
parsedValue.value = parsedValue.parsed.value;
}
} catch {
}
}
return parsedValue;
}
function checkUnicode(value) {
if (value === Key.Ctrl) {
return [value];
}
if (!Object.prototype.hasOwnProperty.call(UNICODE_CHARACTERS2, value)) {
return new GraphemeSplitter().splitGraphemes(value);
}
return [UNICODE_CHARACTERS2[value]];
}
function fetchElementByJSFunction(selector, scope, referenceId) {
if (!("elementId" in scope)) {
return scope.execute(selector, referenceId);
}
const script = (function(elem, id) {
return selector.call(elem, id);
}).toString().replace("selector", `(${selector.toString()})`);
const args = [scope];
if (referenceId) {
args.push(referenceId);
}
const browser = getBrowserObject44(scope);
if (browser.isBidi && heldBrowsingContext(scope)) {
return scope.execute(
`const result = (${script}).apply(null, arguments); return result && typeof result.length === "number" && typeof result !== "string" && !result.nodeType ? Array.from(result) : result`,
...args.slice(1)
);
}
return browser.executeScript(`return (${script}).apply(null, arguments)`, args);
}
function isElement(o) {
return typeof HTMLElement === "object" ? o instanceof HTMLElement : o && typeof o === "object" && o !== null && o.nodeType === 1 && typeof o.nodeName === "string";
}
function isStaleElementError(err) {
return (
// Chrome
err.message.includes("stale element reference") || // Firefox
err.message.includes("is no longer attached to the DOM") || // Safari
err.message.toLowerCase().includes("stale element found") || // Chrome through JS execution
err.message.includes("stale element not found in the current frame") || // BIDI
err.message.includes("belongs to different document") || err.message.includes("no such node - The node with the reference")
);
}
function transformClassicToBidiSelector(using, value) {
if (using === "role") {
const { role, name } = JSON.parse(value);
return {
type: "accessibility",
value: name === void 0 ? { role } : { role, name }
};
}
if (using === "css selector" || using === "tag name") {
return { type: "css", value };
}
if (using === "xpath") {
return { type: "xpath", value };
}
if (using === "link text") {
return { type: "innerText", value };
}
if (using === "partial link text") {
return { type: "innerText", value, matchType: "partial" };
}
if (using === "aria") {
return {
type: "accessibility",
value: {
name: value
}
};
}
throw new Error(`Can't transform classic selector ${using} to Bidi selector`);
}
function toClassicSelector(using, value) {
if (using === "aria") {
return { using: "xpath", value: getAriaXPathSelector(value) };
}
return { using, value };
}
function toAriaXPathLocator(value) {
return { type: "xpath", value: getAriaXPathSelector(value) };
}
function getScopedElementId(ctx) {
return ctx && "elementId" in ctx ? ctx.elementId : void 0;
}
async function findRoleElementsByScript(ctx, browser, value) {
const { role, name } = JSON.parse(value);
if (browser.isMobile && await browser.isNativeContext) {
throw new Error(
`The role selector "role/${role}" is not supported in a native app context. Use an accessibility id (\`~id\`) or a platform selector instead.`
);
}
const root = getScopedElementId(ctx) ? ctx : null;
const find = () => browser.execute(
findByRole,
role,
name ?? null,
-1,
root
);
let result = await find();
if (result === ACCESSIBILITY_API_MISSING) {
await browser.execute(accessibilityScript);
result = await find();
}
return (Array.isArray(result) ? result : []).filter((elem) => getElementFromResponse(elem));
}
async function findElementViaClassic(ctx, browser, using, value, shadowRoots = []) {
const classic = toClassicSelector(using, value);
const elementId = getScopedElementId(ctx);
let lightDom;
let lightDomError;
try {
lightDom = await (elementId ? ctx.findElementFromElement(elementId, classic.using, classic.value) : browser.findElement(classic.using, classic.value));
} catch (err) {
lightDomError = err;
}
if (getElementFromResponse(lightDom)) {
return lightDom;
}
for (const rootId of shadowRoots.filter((id) => id !== elementId)) {
try {
const result = await browser.findElementFromElement(rootId, classic.using, classic.value);
if (getElementFromResponse(result)) {
return result;
}
} catch {
}
}
if (lightDomError) {
throw lightDomError;
}
return lightDom;
}
async function findElementsViaClassic(ctx, browser, using, value, shadowRoots = []) {
const classic = toClassicSelector(using, value);
const elementId = getScopedElementId(ctx);
const collected = [];
let lightDomError;
try {
const lightDom = await (elementId ? ctx.findElementsFromElement(elementId, classic.using, classic.value) : browser.findElements(classic.using, classic.value));
if (Array.isArray(lightDom)) {
collected.push(...lightDom.filter((node) => getElementFromResponse(node)));
}
} catch (err) {
lightDomError = err;
}
for (const rootId of shadowRoots.filter((id) => id !== elementId)) {
try {
const result = await browser.findElementsFromElement(rootId, classic.using, classic.value);
if (Array.isArray(result)) {
collected.push(...result.filter((node) => getElementFromResponse(node)));
}
} catch {
}
}
const ids = /* @__PURE__ */ new Set();
const unique = collected.filter((node) => {
const id = node[ELEMENT_KEY19];
return Boolean(id) && !ids.has(id) && ids.add(id);
});
if (unique.length === 0 && lightDomError) {
throw lightDomError;
}
return unique;
}
async function findInFrameByScript(scope, browser, using, value, index) {
const held = heldBrowsingContext(scope);
if (!held || !held.isFrame && !await foreignContextId(scope)) {
return void 0;
}
const query = function(strategy, selector, at, root) {
const css = strategy === "id" ? `#${CSS.escape(selector)}` : strategy === "class name" ? `.${selector.split(/\s+/).map((part) => CSS.escape(part)).join(".")}` : strategy === "name" ? `[name="${selector.replace(/"/g, '\\"')}"]` : strategy === "tag name" ? selector : selector;
const matches = (base) => {
if (strategy === "xpath") {
const snapshot = document.evaluate(selector, base, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
const found2 = [];
for (let i = 0; i < snapshot.snapshotLength; i++) {
const node = snapshot.snapshotItem(i);
if (node) {
found2.push(node);
}
}
return found2;
}
const scope2 = "querySelectorAll" in base ? base : document;
if (strategy === "link text" || strategy === "partial link text") {
return Array.from(scope2.querySelectorAll("a")).filter((link) => strategy === "link text" ? link.textContent === selector : (link.textContent || "").includes(selector));
}
const found = Array.from(scope2.querySelectorAll(css));
for (const element of Array.from(scope2.querySelectorAll("*"))) {
if (element.shadowRoot) {
found.push(...matches(element.shadowRoot));
}
}
return found;
};
const nodes = matches(root && "querySelectorAll" in root ? root : document);
if (at === null) {
return nodes.length;
}
return nodes[at] || null;
};
const rootId = scope[ELEMENT_KEY19];
if (using === "role") {
const { role, name } = JSON.parse(value);
const roleParams = {
functionDeclaration: createBidiFunctionDeclaration(findByRole),
awaitPromise: true,
arguments: [
LocalValue.getArgument(role),
LocalValue.getArgument(name ?? null),
LocalValue.getArgument(index),
rootId ? { sharedId: rootId } : LocalValue.getArgument(null)
],
target: { context: held.contextId }
};
let found = parseScriptResult(roleParams, await browser.scriptCallFunction(roleParams));
if (found === ACCESSIBILITY_API_MISSING) {
await browser.scriptEvaluate({ expression: accessibilityScript, target: { context: held.contextId }, awaitPromise: false });
found = parseScriptResult(roleParams, await browser.scriptCallFunction(roleParams));
}
if (typeof found === "number") {
return found;
}
return found && typeof found === "object" && ELEMENT_KEY19 in found ? found : void 0;
}
const resolved = using === "aria" ? { using: "xpath", value: getAriaXPathSelector(value) } : { using, value };
const params = {
functionDeclaration: createBidiFunctionDeclaration(query),
awaitPromise: true,
arguments: [
LocalValue.getArgument(resolved.using),
LocalValue.getArgument(resolved.value),
LocalValue.getArgument(index),
rootId ? { sharedId: rootId } : LocalValue.getArgument(null)
],
target: { context: held.contextId }
};
const parsed = parseScriptResult(params, await browser.scriptCallFunction(params));
if (typeof parsed === "number") {
return parsed;
}
if (parsed && typeof parsed === "object" && ELEMENT_KEY19 in parsed) {
return parsed;
}
return void 0;
}
async function findAllInFrame(scope, browser, using, value) {
const count = await findInFrameByScript(scope, browser, using, value, null);
const total = typeof count === "number" ? count : 0;
const nodes = [];
for (let i = 0; i < total; i++) {
const node = await findInFrameByScript(scope, browser, using, value, i);
if (node && typeof node === "object") {
nodes.push(node);
}
}
return nodes;
}
async function findAriaElementViaXPathFallback(ctx, browser, using, value, context, startNodes, shadowRoots) {
const xpathLocator = toAriaXPathLocator(value);
if (startNodes && startNodes.length > 0) {
try {
const result = await browser.browsingContextLocateNodes({
locator: xpathLocator,
context,
startNodes
});
const nodes = returnUniqueNodes(
result.nodes.filter((node) => Boolean(node.sharedId)).map((node) => ({
[ELEMENT_KEY19]: node.sharedId,
locator: xpathLocator
}))
);
if (nodes[0]) {
return nodes[0];
}
} catch (err) {
log28.warn(`BiDi XPath fallback for "aria/${value}" failed due to ${err}, using Classic`);
}
}
return findElementViaClassic(ctx, browser, using, value, shadowRoots);
}
async function findAriaElementsViaXPathFallback(ctx, browser, using, value, context, startNodes, shadowRoots) {
const xpathLocator = toAriaXPathLocator(value);
if (startNodes && startNodes.length > 0) {
try {
const result = await browser.browsingContextLocateNodes({
locator: xpathLocator,
context,
startNodes
});
const nodes = returnUniqueNodes(
result.nodes.filter((node) => Boolean(node.sharedId)).map((node) => ({
[ELEMENT_KEY19]: node.sharedId,
locator: xpathLocator
}))
);
if (nodes.length > 0) {
return nodes;
}
} catch (err) {
log28.warn(`BiDi XPath fallback for "aria/${value}" failed due to ${err}, using Classic`);
}
}
return findElementsViaClassic(ctx, browser, using, value, shadowRoots);
}
function isFirefoxBidiRootSelectorBug(browser) {
if (!browser.isFirefox) {
return false;
}
const version = browser.capabilities?.browserVersion ?? "";
const major = parseInt(version.match(/\d+/)?.[0] ?? "0", 10);
return !isNaN(major) && major > 0 && major < 150;
}
function applyFirefoxRootSelectorWorkaround(using, value, isScopedContext, browser) {
if (using === "css selector" && (value === "html" || value === ":root") && !isScopedContext && isFirefoxBidiRootSelectorBug(browser)) {
return { using: "xpath", value: "/html" };
}
return { using, value };
}
async function findDeepElement(selector) {
const browser = getBrowserObject44(this);
const shadowRootManager = getShadowRootManager(browser);
const context = await contextIdOf(this);
let shadowRoots = await shadowRootManager.getShadowElementsByContextId(
context,
this.elementId
);
let { using, value } = findStrategy(selector, this.isMobile, this.isBidi);
if (using === "link text" || using === "partial link text") {
const held = await foreignContext(this);
if (held) {
const [first] = await findLinksInContext(held, using, value, this.elementId, 1);
return first;
}
}
if (heldBrowsingContext(this)?.isFrame) {
return findInFrameByScript(this, browser, using, value, 0);
}
if (using === "xpath" && (value.startsWith("./") || value.startsWith("..")) && this.elementId) {
return this.findElementFromElement(this.elementId, using, value);
}
const isScopedContext = shadowRoots.length > 0 || Boolean(this.elementId);
({ using, value } = applyFirefoxRootSelectorWorkaround(using, value, isScopedContext, browser));
const locator = transformClassicToBidiSelector(using, value);
const startNodes = this.elementId ? [
{ sharedId: this.elementId },
...shadowRoots.map((shadowRootNodeId) => ({ sharedId: shadowRootNodeId }))
] : shadowRoots.length > 0 ? shadowRoots.map((shadowRootNodeId) => ({ sharedId: shadowRootNodeId })) : void 0;
const locateNodes = () => browser.browsingContextLocateNodes({ locator, context, startNodes }).catch(async (err) => {
const message = String(err?.message);
const staleNodes = message.includes("no such node") ? shadowRoots.filter((root) => message.includes(root)) : [];
if (staleNodes.length === 0) {
throw err;
}
for (const node of staleNodes) {
shadowRootManager.forgetNode(context, node);
}
const scope = this.elementId;
shadowRoots = await shadowRootManager.getShadowElementsByContextId(context, scope);
return browser.browsingContextLocateNodes({
locator,
context,
startNodes: scope ? [{ sharedId: scope }, ...shadowRoots.map((sharedId) => ({ sharedId }))] : shadowRoots.length > 0 ? shadowRoots.map((sharedId) => ({ sharedId })) : void 0
});
});
const deepElementResult = await locateNodes().then(async (result) => {
let nodes = result.nodes.filter((node) => Boolean(node.sharedId)).map((node) => ({
[ELEMENT_KEY19]: node.sharedId,
locator
}));
nodes = returnUniqueNodes(nodes);
if (!this.elementId) {
if (shadowRoots.length > 0 && nodes.length > 0) {
const connected = await checkElementsConnected(
browser,
nodes.map((node) => node[ELEMENT_KEY19]),
context
);
nodes.forEach((node, i) => {
if (!connected[i]) {
shadowRootManager.deleteShadowRoot(node[ELEMENT_KEY19], context);
}
});
const firstConnected = nodes.find((_, i) => connected[i]);
if (firstConnected) {
return firstConnected;
}
log28.warn(`All ${nodes.length} BiDi results for "${value}" are detached, removed stale entries from shadow root tree`);
return void 0;
}
return nodes[0];
}
const containment = await checkElementsContainedIn(
browser,
this.elementId,
nodes.map((node) => node[ELEMENT_KEY19]),
context
);
const scopedNodes = nodes.filter((_, i) => containment[i]);
if (shadowRoots.length > 0) {
const connected = await checkElementsConnected(
browser,
scopedNodes.map((node) => node[ELEMENT_KEY19]),
context
);
scopedNodes.forEach((node, i) => {
if (!connected[i]) {
shadowRootManager.deleteShadowRoot(node[ELEMENT_KEY19], context);
}
});
const firstConnected = scopedNodes.find((_, i) => connected[i]);
if (firstConnected) {
return firstConnected;
}
if (scopedNodes.length > 0) {
log28.warn(`All ${scopedNodes.length} scoped BiDi results for "${value}" are detached, removed stale entries from shadow root tree`);
return void 0;
}
}
return scopedNodes[0];
}).then(async (found) => {
if (using === "aria" && !found) {
log28.info(`BiDi accessibility locator found no nodes for "aria/${value}", falling back to XPath`);
return findAriaElementViaXPathFallback(
this,
browser,
using,
value,
context,
startNodes,
shadowRoots
);
}
return found;
}, async (err) => {
log28.warn(`Failed to execute browser.browsingContextLocateNodes({ ... }) due to ${err}, falling back to regular WebDriver Classic command`);
if (using === "role") {
return (await findRoleElementsByScript(this, browser, value))[0];
}
if (using === "aria") {
return findAriaElementViaXPathFallback(
this,
browser,
using,
value,
context,
startNodes,
shadowRoots
);
}
if (heldBrowsingContext(this)?.isFrame || await foreignContextId(this)) {
return findInFrameByScript(this, browser, using, value, 0);
}
return findElementViaClassic(this, browser, using, value, shadowRoots);
});
return deepElementResult;
}
async function findDeepElements(selector) {
const browser = getBrowserObject44(this);
const shadowRootManager = getShadowRootManager(browser);
const context = await contextIdOf(this);
let shadowRoots = await shadowRootManager.getShadowElementsByContextId(
context,
this.elementId
);
let { using, value } = findStrategy(selector, this.isMobile, this.isBidi);
if (using === "link text" || using === "partial link text") {
const held = await foreignContext(this);
if (held) {
return findLinksInContext(held, using, value, this.elementId);
}
}
if (heldBrowsingContext(this)?.isFrame) {
return findAllInFrame(this, browser, using, value);
}
if (using === "xpath" && (value.startsWith("./") || value.startsWith("..")) && this.elementId) {
return this.findElementsFromElement(this.elementId, using, value);
}
const isScopedContext = shadowRoots.length > 0 || Boolean(this.elementId);
({ using, value } = applyFirefoxRootSelectorWorkaround(using, value, isScopedContext, browser));
const locator = transformClassicToBidiSelector(using, value);
const startNodes = this.elementId ? [
{ sharedId: this.elementId },
...shadowRoots.map((shadowRootNodeId) => ({ sharedId: shadowRootNodeId }))
] : shadowRoots.length > 0 ? shadowRoots.map((shadowRootNodeId) => ({ sharedId: shadowRootNodeId })) : void 0;
const locateNodes = () => browser.browsingContextLocateNodes({ locator, context, startNodes }).catch(async (err) => {
const message = String(err?.message);
const staleNodes = message.includes("no such node") ? shadowRoots.filter((root) => message.includes(root)) : [];
if (staleNodes.length === 0) {
throw err;
}
for (const node of staleNodes) {
shadowRootManager.forgetNode(context, node);
}
const scope = this.elementId;
shadowRoots = await shadowRootManager.getShadowElementsByContextId(context, scope);
return browser.browsingContextLocateNodes({
locator,
context,
startNodes: scope ? [{ sharedId: scope }, ...shadowRoots.map((sharedId) => ({ sharedId }))] : shadowRoots.length > 0 ? shadowRoots.map((sharedId) => ({ sharedId })) : void 0
});
});
const deepElementResult = await locateNodes().then(async (result) => {
let nodes = result.nodes.filter((node) => Boolean(node.sharedId)).map((node) => ({
[ELEMENT_KEY19]: node.sharedId,
locator
}));
nodes = returnUniqueNodes(nodes);
if (!this.elementId) {
if (shadowRoots.length > 0 && nodes.length > 0) {
const connected = await checkElementsConnected(
browser,
nodes.map((node) => node[ELEMENT_KEY19]),
context
);
nodes.forEach((node, i) => {
if (!connected[i]) {
shadowRootManager.deleteShadowRoot(node[ELEMENT_KEY19], context);
}
});
const validNodes = nodes.filter((_, i) => connected[i]);
if (validNodes.length > 0) {
return validNodes;
}
return [];
}
return nodes;
}
const containment = await checkElementsContainedIn(
browser,
this.elementId,
nodes.map((node) => node[ELEMENT_KEY19]),
context
);
const scopedNodes = nodes.filter((_, i) => containment[i]);
if (shadowRoots.length > 0) {
const connected = await checkElementsConnected(
browser,
scopedNodes.map((node) => node[ELEMENT_KEY19]),
context
);
scopedNodes.forEach((node, i) => {
if (!connected[i]) {
shadowRootManager.deleteShadowRoot(node[ELEMENT_KEY19], context);
}
});
const connectedScopedNodes = scopedNodes.filter((_, i) => connected[i]);
if (connectedScopedNodes.length > 0) {
return connectedScopedNodes;
}
if (scopedNodes.length > 0) {
return [];
}
}
return scopedNodes;
}).then(async (found) => {
if (using === "aria" && (!found || found.length === 0)) {
log28.info(`BiDi accessibility locator found no nodes for "aria/${value}", falling back to XPath`);
return findAriaElementsViaXPathFallback(
this,
browser,
using,
value,
context,
startNodes,
shadowRoots
);
}
return found;
}, async (err) => {
log28.warn(`Failed to execute browser.browsingContextLocateNodes({ ... }) due to ${err}, falling back to regular WebDriver Classic command`);
if (using === "role") {
return findRoleElementsByScript(this, browser, value);
}
if (using === "aria") {
return findAriaElementsViaXPathFallback(
this,
browser,
using,
value,
context,
startNodes,
shadowRoots
);
}
if (heldBrowsingContext(this)?.isFrame || await foreignContextId(this)) {
return findAllInFrame(this, browser, using, value);
}
return findElementsViaClassic(this, browser, using, value, shadowRoots);
});
return deepElementResult;
}
function returnUniqueNodes(nodes) {
const ids = /* @__PURE__ */ new Set();
return nodes.filter((node) => !ids.has(node[ELEMENT_KEY19]) && ids.add(node[ELEMENT_KEY19]));
}
function buildNotFoundError(selector) {
if (typeof selector === "string" && selector.startsWith(DEEP_SELECTOR)) {
return new Error(`shadow selector "${selector.slice(DEEP_SELECTOR.length)}" did not return an HTMLElement`);
}
if (selector && typeof selector === "object" && typeof selector.strategy === "function") {
const { strategyName } = selector;
return new Error(`Custom Strategy "${strategyName}" did not return an HTMLElement`);
}
if (typeof selector === "function") {
return new Error(`Function selector "${selector.toString()}" did not return an HTMLElement`);
}
if (isElement(selector)) {
return new Error("DOM Node couldn't be found anymore");
}
return new Error(`Couldn't find element with selector "${String(selector)}"`);
}
async function findElement(selector) {
const browserObject = getBrowserObject44(this);
const shadowRootManager = getShadowRootManager(browserObject);
if (this.isBidi && typeof selector === "string" && !selector.startsWith(DEEP_SELECTOR) && !shadowRootManager.isWithinFrame()) {
const notFoundError = new Error(`Couldn't find element with selector "${selector}"`);
const elem = await findDeepElement.call(this, selector);
return getElementFromResponse(elem) ? elem : notFoundError;
}
if (typeof selector === "string" && selector.startsWith(DEEP_SELECTOR)) {
const notFoundError = new Error(`shadow selector "${selector.slice(DEEP_SELECTOR.length)}" did not return an HTMLElement`);
let elem = await browserObject.execute(
querySelectorAllDeep,
false,
selector.slice(DEEP_SELECTOR.length),
// hard conversion from element id to Element is done by browser driver
this.elementId ? this : void 0
);
elem = Array.isArray(elem) ? elem[0] : elem;
return getElementFromResponse(elem) ? elem : notFoundError;
}
if (selector && typeof selector === "object" && typeof selector.strategy === "function") {
const { strategy, strategyName, strategyArguments } = selector;
const notFoundError = new Error(`Custom Strategy "${strategyName}" did not return an HTMLElement`);
let elem = await browserObject.execute(strategy, ...strategyArguments);
elem = Array.isArray(elem) ? elem[0] : elem;
return getElementFromResponse(elem) ? elem : notFoundError;
}
if (typeof selector === "string" && selector.startsWith(ROLE_SELECTOR)) {
const { value } = findStrategy(selector, this.isMobile);
const [elem] = await findRoleElementsByScript(this, browserObject, value);
return elem || new Error(`Couldn't find element with selector "${selector}"`);
}
if (typeof selector === "string" || isPlainObject3(selector)) {
const { using, value } = findStrategy(selector, this.isMobile);
return this.elementId ? this.findElementFromElement(this.elementId, using, value) : this.findElement(using, value);
}
if (typeof selector === "function") {
const notFoundError = new Error(`Function selector "${selector.toString()}" did not return an HTMLElement`);
let elem = await fetchElementByJSFunction(selector, this);
elem = Array.isArray(elem) ? elem[0] : elem;
return getElementFromResponse(elem) ? elem : notFoundError;
}
if (isElement(selector)) {
if (!window.__wdio_element) {
window.__wdio_element = {};
}
const notFoundError = new Error("DOM Node couldn't be found anymore");
const uid = Math.random().toString().slice(2);
window.__wdio_element[uid] = selector;
selector = ((id) => window.__wdio_element[id]);
let elem = await fetchElementByJSFunction(selector, this, uid).catch((err) => {
if (isStaleElementError(err)) {
return void 0;
}
throw err;
});
elem = Array.isArray(elem) ? elem[0] : elem;
return getElementFromResponse(elem) ? elem : notFoundError;
}
throw new Error(`${INVALID_SELECTOR_ERROR}, but found: \`${typeof selector}\``);
}
async function findElements(selector) {
const browserObject = getBrowserObject44(this);
if (typeof selector === "string" && selector.startsWith(DEEP_SELECTOR)) {
const elems = await browserObject.execute(
querySelectorAllDeep,
true,
selector.slice(DEEP_SELECTOR.length),
// hard conversion from element id to Element is done by browser driver
this.elementId ? this : void 0
);
const elemArray = Array.isArray(elems) ? elems : [elems];
return elemArray.filter((elem) => elem && getElementFromResponse(elem));
}
if (isPlainObject3(selector) && typeof selector.strategy === "function") {
const { strategy, strategyArguments } = selector;
const elems = await browserObject.execute(strategy, ...strategyArguments);
const elemArray = Array.isArray(elems) ? elems : [elems];
return elemArray.filter((elem) => elem && getElementFromResponse(elem));
}
if (typeof selector === "string" && selector.startsWith(ROLE_SELECTOR)) {
const { value } = findStrategy(selector, this.isMobile);
return findRoleElementsByScript(this, browserObject, value);
}
if (typeof selector === "string" || isPlainObject3(selector)) {
const { using, value } = findStrategy(selector, this.isMobile);
return this.elementId ? this.findElementsFromElement(this.elementId, using, value) : this.findElements(using, value);
}
if (typeof selector === "function") {
const elems = await fetchElementByJSFunction(selector, this);
const elemArray = Array.isArray(elems) ? elems : [elems];
return elemArray.filter((elem) => elem && getElementFromResponse(elem));
}
throw new Error(`${INVALID_SELECTOR_ERROR}, but found: \`${typeof selector}\``);
}
function verifyArgsAndStripIfElement(args) {
function verify(arg) {
if (isLoadedElement2(arg)) {
if (!arg.elementId) {
throw new Error(`The element with selector "${arg.selector}" you are trying to pass into the execute method wasn't found`);
}
return {
[ELEMENT_KEY19]: arg.elementId
};
}
return arg;
}
return !Array.isArray(args) ? verify(args) : args.map(verify);
}
async function getElementRect(scope) {
const rect = await scope.getElementRect(scope.elementId);
const defaults = { x: 0, y: 0, width: 0, height: 0 };
if (Object.keys(defaults).some((key) => rect[key] === void 0)) {
const rectJs = await getBrowserObject44(scope).execute(function(el) {
if (!el || !el.getBoundingClientRect) {
return;
}
const { left, top, width, height } = el.getBoundingClientRect();
return {
x: left + this.scrollX,
y: top + this.scrollY,
width,
height
};
}, scope);
Object.keys(defaults).forEach((key) => {
if (typeof rect[key] !== "undefined") {
return;
}
if (rectJs && typeof rectJs[key] === "number") {
rect[key] = Math.floor(rectJs[key]);
} else {
log28.error("getElementRect", { rect, rectJs, key });
throw new Error("Failed to receive element rects via execute command");
}
});
}
return rect;
}
function validateUrl(url2, origError) {
try {
const urlObject = new URL(url2);
return urlObject.href;
} catch {
if (origError) {
throw origError;
}
return validateUrl(`http://${url2}`, new Error(`Invalid URL: ${url2}`));
}
}
async function hasElementId(element) {
if (!element.elementId) {
element.elementId = (await findElementAgain(element))?.elementId;
if (element.elementId) {
element[ELEMENT_KEY19] = element.elementId;
delete element.error;
}
}
if (!element.elementId) {
return false;
}
return true;
}
function addLocatorStrategyHandler(scope) {
return (name, func) => {
if (scope.strategies.get(name)) {
throw new Error(`Strategy ${name} already exists`);
}
scope.strategies.set(name, func);
if (scope.isMultiRemote) {
const multiRemoteScope = scope;
for (const instanceName of multiRemoteScope.instances) {
const instance = multiRemoteScope.getInstance(instanceName);
instance?.strategies?.set(name, func);
}
}
};
}
var isStub = (automationProtocol) => automationProtocol === "./protocol-stub.js";
function createFunctionDeclarationFromString(userScript) {
if (typeof userScript === "string") {
return `(${SCRIPT_PREFIX}function () {
${userScript.toString()}
}${SCRIPT_SUFFIX}).apply(this, arguments);`;
}
return new Function(`return (${SCRIPT_PREFIX}${userScript.toString()}${SCRIPT_SUFFIX}).apply(this, arguments);`).toString();
}
// src/middlewares.ts
var IMPLICIT_WAIT_EXCLUSION_LIST = ["getElement", "getElements", "emit"];
async function invokeElementCommand(element, commandName, execute3) {
if (IMPLICIT_WAIT_EXCLUSION_LIST.includes(commandName)) {
return execute3();
}
const fetched = await implicitWait(element, commandName);
element.elementId = fetched.elementId;
element[ELEMENT_KEY20] = fetched.elementId;
try {
const result = await execute3();
const caps = getBrowserObject45(element).capabilities;
if (caps?.browserName === "safari" && result?.error === "no such element") {
const errorName = "stale element reference";
const err = new Error(errorName);
err.name = errorName;
throw err;
}
return result;
} catch (_err) {
const err = _err;
if (err.name === "element not interactable") {
try {
await fetched.waitForClickable();
return await execute3();
} catch {
const elementHTML = await fetched.getHTML();
err.name = "webdriverio(middleware): element did not become interactable";
err.message = `Element ${elementHTML} did not become interactable`;
err.stack = err.stack ?? Error.captureStackTrace(err) ?? "";
}
}
if (err.name === "stale element reference" || isStaleElementError(err)) {
try {
const refetched = await refetchElement(element, commandName);
element.elementId = refetched.elementId;
element[ELEMENT_KEY20] = refetched.elementId;
element.parent = refetched.parent;
return await execute3();
} catch (refetchErr) {
if (refetchErr instanceof StrictSelectorError) {
throw refetchErr;
}
if (!(refetchErr instanceof Error) || !isStaleElementError(refetchErr)) {
throw err;
}
throw refetchErr;
}
}
throw err;
}
}
var elementErrorHandler = (fn) => (commandName, commandFn) => {
return function elementErrorHandlerCallback(...args) {
if (ELEMENT_ARRAY_COMMANDS.includes(commandName)) {
return fn(commandName, function(...commandArgs) {
const list = commandFn.apply(this, commandArgs);
const wrap = list?.[ELEMENT_ARRAY_WRAP2];
if (typeof wrap === "function") {
const element = this;
wrap(async (load2) => invokeElementCommand(element, commandName, () => load2()));
}
return list;
}).apply(this, args);
}
return fn(commandName, async function elementErrorHandlerCallbackFn() {
return invokeElementCommand(this, commandName, () => fn(commandName, commandFn).apply(this, args));
}).apply(this);
};
};
var multiRemoteHandler = (wrapCommand5) => (commandName) => {
return wrapCommand5(commandName, function(...args) {
const commandResults = this.instances.map((instanceName) => {
const instance = this.getInstance(instanceName);
if (!instance) {
throw new Error(`Multi-remote object has no instance named "${instanceName}"`);
}
const command = instance[commandName];
return command.call(instance, ...args);
});
return Promise.all(commandResults);
});
};
// src/multiRemoteMock.ts
import { setWdioKind as setWdioKind4 } from "@wdio/utils";
var MISSING_INSTANCE = (name) => `Multi-remote object has no instance named "${name}"`;
var MultiRemoteMock = class {
instances;
isMultiRemote = true;
#mocks = /* @__PURE__ */ new Map();
constructor(instanceNames, mocks) {
if (instanceNames.length !== mocks.length) {
throw new Error(
`Cannot build a multi-remote mock: instance names (${instanceNames.length}) and mocks (${mocks.length}) differ`
);
}
this.instances = [...instanceNames];
instanceNames.forEach((name, index) => {
this.#mocks.set(name, mocks[index]);
});
}
/**
* The mock registered on one instance.
*/
getInstance(name) {
const found = this.#mocks.get(name);
if (!found) {
throw new Error(MISSING_INSTANCE(name));
}
return found;
}
abort(...args) {
return this.#each("abort", args);
}
abortOnce(...args) {
return this.#each("abortOnce", args);
}
clear(...args) {
return this.#each("clear", args);
}
reset(...args) {
return this.#each("reset", args);
}
redirect(...args) {
return this.#each("redirect", args);
}
redirectOnce(...args) {
return this.#each("redirectOnce", args);
}
request(...args) {
return this.#each("request", args);
}
requestOnce(...args) {
return this.#each("requestOnce", args);
}
respond(...args) {
return this.#each("respond", args);
}
respondOnce(...args) {
return this.#each("respondOnce", args);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
on(event, callback) {
return this.#each("on", [event, callback]);
}
async restore(...args) {
const pending = this.instances.map((name) => {
try {
return Promise.resolve(this.getInstance(name).restore(...args));
} catch (error) {
return Promise.reject(error);
}
});
const settled = await Promise.allSettled(pending);
this.#throwIfFailed("restore", settled.flatMap((result, index) => result.status === "rejected" ? [{ name: this.instances[index], error: result.reason }] : []));
return this;
}
waitForResponse(...args) {
return Promise.all(this.instances.map((name) => this.getInstance(name).waitForResponse(...args)));
}
/**
* Run `method` on every instance. A throw from one instance does not skip
* the rest; the original error is rethrown, or an `AggregateError` when
* more than one instance failed.
*/
#each(method, args) {
const failures = [];
for (const name of this.instances) {
const mock2 = this.getInstance(name);
const fn = mock2[method];
try {
fn.apply(mock2, args);
} catch (error) {
failures.push({ name, error });
}
}
this.#throwIfFailed(method, failures);
return this;
}
#throwIfFailed(method, failures) {
if (failures.length === 0) {
return;
}
if (failures.length === 1) {
throw failures[0].error;
}
const names = failures.map(({ name }) => name).join(", ");
throw new AggregateError(
failures.map(({ error }) => error),
`Multi-remote mock ${method}() failed for ${names}`
);
}
};
setWdioKind4(MultiRemoteMock.prototype, "mock");
// src/multiRemote.ts
var overridableCommands = new Set(Object.keys(browser_exports));
var SINGLE_QUERIES = /* @__PURE__ */ new Set(["$", "custom$", "react$", "shadow$", "nextElement", "previousElement", "parentElement"]);
var LIST_QUERIES = /* @__PURE__ */ new Set(["$$", "custom$$", "react$$", "shadow$$"]);
function zipElements(lists) {
const length = Math.max(0, ...lists.map((list) => list.length));
return Array.from({ length }, (_, index) => lists.map((list) => index < list.length ? list[index] : void 0));
}
var MultiRemote = class _MultiRemote {
/**
* Browser sessions in capability order. Instance names are map keys,
* not properties of the browser client.
*/
instances = /* @__PURE__ */ new Map();
baseInstance;
sessionId;
/**
* add instance to multibrowser instance
*/
async addInstance(browserName, client) {
this.instances.set(browserName, client);
return client;
}
/**
* modifier for multibrowser instance
*/
modifier(wrapperClient) {
const modifierThis = this;
const propertiesObject = Object.fromEntries(
Object.entries(wrapperClient.__propertiesObject__ ?? {}).map(([name, descriptor]) => [name, { ...descriptor }])
);
propertiesObject.commandList = { value: wrapperClient.commandList };
propertiesObject.options = { value: wrapperClient.options };
propertiesObject.getInstance = {
value: (browserName) => {
const instance = this.instances.get(browserName);
if (!instance) {
throw new Error(`Multi-remote object has no instance named "${browserName}"`);
}
return instance;
}
};
propertiesObject.select = {
value: function select(...instanceNames) {
const newMultiRemote = new _MultiRemote();
for (const name of instanceNames) {
const instance = modifierThis.instances.get(name);
if (instance) {
newMultiRemote.instances.set(name, instance);
}
}
if (newMultiRemote.instances.size === 0) {
throw new Error("None of the following requested instances are valid: " + instanceNames.join(", "));
}
return newMultiRemote.modifier(this);
},
configurable: true,
writable: true
};
for (const commandName of wrapperClient.commandList) {
if (!Object.prototype.hasOwnProperty.call(wrapperClient, commandName) && overridableCommands.has(commandName)) {
delete propertiesObject[commandName];
continue;
}
const isFunction = typeof wrapperClient[commandName] === "function";
propertiesObject[commandName] = {
value: isFunction ? this.commandWrapper(commandName) : wrapperClient[commandName],
configurable: true
};
}
const inheritedStrategies = wrapperClient.__propertiesObject__?.strategies?.value;
propertiesObject.strategies = { value: inheritedStrategies ?? /* @__PURE__ */ new Map() };
propertiesObject.__propertiesObject__ = {
value: propertiesObject
};
this.baseInstance = new MultiRemoteDriver(this.instances, propertiesObject);
const client = Object.create(this.baseInstance, propertiesObject);
if (Object.prototype.hasOwnProperty.call(wrapperClient, "addLocatorStrategy")) {
client.addLocatorStrategy = addLocatorStrategyHandler(client);
}
return client;
}
/**
* helper method to generate element objects from results, so that we can call, e.g.
*
* ```
* const elem = $('#elem')
* elem.getHTML()
* ```
*
* or in case multi-remote is used
*
* ```
* const elems = $$('div')
* elems[0].getHTML()
* ```
*/
static elementWrapper(instances, result, propertiesObject, scope) {
const prototype = { ...propertiesObject, ...clone2(getPrototype("element")), scope: { value: "element" } };
const results = Array.isArray(result) ? result : [];
const element = webdriverMonad3({}, (client) => {
const byName = /* @__PURE__ */ new Map();
let index = 0;
for (const identifier of instances.keys()) {
byName.set(identifier, results[index]);
index++;
}
client.instances = [...instances.keys()];
client.isMultiRemote = true;
setWdioKind5(client, "element");
const firstElement = results.find(Boolean);
if (firstElement) {
client.selector = firstElement.selector;
}
delete client.sessionId;
Object.defineProperty(client, "getInstance", {
configurable: true,
writable: true,
value(browserName) {
const found = byName.get(browserName);
if (!found) {
throw new Error(`Multi-remote object has no instance named "${browserName}"`);
}
return found;
}
});
client.select = function select(...instanceNames) {
const selectedResults = [];
const selectedInstances = /* @__PURE__ */ new Map();
for (const name of instanceNames) {
if (!client.instances.includes(name)) {
continue;
}
const browserInstance = scope.instances.get(name);
if (!browserInstance) {
continue;
}
selectedInstances.set(name, browserInstance);
selectedResults.push(client.getInstance(name));
}
if (selectedInstances.size === 0) {
throw new Error("None of the following requested instances are valid: " + instanceNames.join(", "));
}
return _MultiRemote.elementWrapper(selectedInstances, selectedResults, propertiesObject, scope);
};
return client;
}, prototype);
const sessionId = this.sessionId;
return element(sessionId, multiRemoteHandler(scope.commandWrapper.bind(scope)));
}
/**
* handle commands for multi-remote instances
*/
commandWrapper(commandName) {
const instances = this.instances;
const self = this;
return wrapCommand3(commandName, function(...args) {
const execute3 = async () => {
const thisElement = this;
const isElementScope = Boolean(thisElement.selector);
const scopeEntries = isElementScope ? thisElement.instances.map((instanceName) => [instanceName, thisElement.getInstance(instanceName)]) : [...instances.entries()];
const result = await Promise.all(
scopeEntries.map(([, instance]) => {
const command = instance[commandName];
return command.call(instance, ...args);
})
);
const activeInstances = isElementScope ? new Map(thisElement.instances.map((instanceName) => {
const browserInstance = instances.get(instanceName);
if (!browserInstance) {
throw new Error(`Multi-remote object has no instance named "${instanceName}"`);
}
return [instanceName, browserInstance];
})) : instances;
return { result, activeInstances, scopeEntries };
};
if (LIST_QUERIES.has(commandName)) {
const [selector, ...props] = args;
let loadedInstances = instances;
const wrapMultiRemote = (elements) => _MultiRemote.elementWrapper(
loadedInstances,
elements,
this.__propertiesObject__,
self
);
return ElementArray.fromAsyncCallback(async () => {
const { result, activeInstances } = await execute3();
loadedInstances = activeInstances;
return zipElements(result).map(wrapMultiRemote);
}, {
selector,
foundWith: commandName,
parent: this,
/**
* the arguments after the selector, so that
* `parent[foundWith](selector, ...props)` runs the same query again
*/
props,
isMultiRemote: true,
wrapMultiRemote
});
}
return (async () => {
const { result, activeInstances, scopeEntries } = await execute3();
if (SINGLE_QUERIES.has(commandName)) {
return _MultiRemote.elementWrapper(activeInstances, result, this.__propertiesObject__, self);
} else if (commandName === "mock") {
return new MultiRemoteMock(
scopeEntries.map(([instanceName]) => instanceName),
result
);
}
return result;
})();
});
}
};
var MultiRemoteDriver = class {
instances;
isMultiRemote = true;
__propertiesObject__;
constructor(instances, propertiesObject) {
this.instances = [...instances.keys()];
this.__propertiesObject__ = propertiesObject;
}
on(eventName, emitter) {
this.instances.forEach((instanceName) => this.getInstance(instanceName).on(eventName, emitter));
return void 0;
}
once(eventName, emitter) {
this.instances.forEach((instanceName) => this.getInstance(instanceName).once(eventName, emitter));
return void 0;
}
emit(eventName, emitter) {
return this.instances.map(
(instanceName) => this.getInstance(instanceName).emit(eventName, emitter)
).some(Boolean);
}
eventNames() {
return this.instances.map(
(instanceName) => this.getInstance(instanceName).eventNames()
);
}
getMaxListeners() {
return this.instances.map(
(instanceName) => this.getInstance(instanceName).getMaxListeners()
);
}
listenerCount(eventName) {
return this.instances.map(
(instanceName) => this.getInstance(instanceName).listenerCount(eventName)
);
}
listeners(eventName) {
return this.instances.map(
(instanceName) => this.getInstance(instanceName).listeners(eventName)
).reduce((prev, cur) => {
prev.concat(cur);
return prev;
}, []);
}
removeListener(eventName, emitter) {
this.instances.forEach((instanceName) => this.getInstance(instanceName).removeListener(eventName, emitter));
return void 0;
}
removeAllListeners(eventName) {
this.instances.forEach((instanceName) => this.getInstance(instanceName).removeAllListeners(eventName));
return void 0;
}
};
setWdioKind5(MultiRemoteDriver.prototype, "browser");
// src/utils/SevereServiceError.ts
var SevereServiceError = class extends Error {
constructor(message = "Severe Service Error occurred.") {
super(message);
this.name = "SevereServiceError";
}
};
// src/utils/detectBackend.ts
var DEFAULT_HOSTNAME = "127.0.0.1";
var DEFAULT_PORT = 4444;
var DEFAULT_PROTOCOL = "http";
var DEFAULT_PATH = "/";
var LEGACY_PATH = "/wd/hub";
var REGION_MAPPING = {
"us": "us-west-1.",
// default endpoint
"eu": "eu-central-1.",
"eu-central-1": "eu-central-1.",
"us-east-4": "us-east-4.",
"asia-south-2": "asia-south-2."
};
function getSauceEndpoint(region, { isRDC, isVisual } = {}) {
const shortRegion = REGION_MAPPING[region] ? region : "us";
if (isRDC) {
return `${shortRegion}1.appium.testobject.com`;
} else if (isVisual) {
return "hub.screener.io";
}
return `ondemand.${REGION_MAPPING[shortRegion]}saucelabs.com`;
}
function detectBackend(options = {}) {
const { port, hostname, user, key, protocol, region, path: path9, capabilities } = options;
if (typeof user === "string" && typeof key === "string" && key.length === 20) {
return {
protocol: protocol || "https",
hostname: hostname || "hub-cloud.browserstack.com",
port: port || 443,
path: path9 || LEGACY_PATH
};
}
if (typeof user === "string" && typeof key === "string" && key.length === 32) {
return {
protocol: protocol || "https",
hostname: hostname || "hub.testingbot.com",
port: port || 443,
path: path9 || LEGACY_PATH
};
}
const isVisual = Boolean(!Array.isArray(capabilities) && capabilities && capabilities["sauce:visual"]?.apiKey);
if (typeof user === "string" && typeof key === "string" && key.length === 36 || // Or only RDC or visual
isVisual) {
const sauceRegion = region;
return {
protocol: protocol || "https",
hostname: hostname || getSauceEndpoint(sauceRegion, { isVisual }),
port: port || 443,
path: path9 || LEGACY_PATH
};
}
if (typeof user === "string" && typeof key === "string" && key.length === 50) {
return {
protocol: protocol || DEFAULT_PROTOCOL,
hostname: hostname || "hub.lambdatest.com",
port: port || 80,
path: path9 || LEGACY_PATH
};
}
if (
/**
* user and key are set in config
*/
(typeof user === "string" || typeof key === "string") && /**
* but no custom WebDriver endpoint was configured
*/
!hostname
) {
throw new Error(
'A "user" or "key" was provided but could not be connected to a known cloud service (Sauce Labs, Browerstack, Testingbot or Lambdatest). Please check if given user and key properties are correct!'
);
}
if (hostname || port || protocol || path9) {
return {
hostname: hostname || DEFAULT_HOSTNAME,
port: port || DEFAULT_PORT,
protocol: protocol || DEFAULT_PROTOCOL,
path: path9 || DEFAULT_PATH
};
}
return { hostname, port, protocol, path: path9 };
}
// src/protocol-stub.ts
import { capabilitiesEnvironmentDetector, resolveCustomCommandOptions as resolveCustomCommandOptions2 } from "@wdio/utils";
var NOOP2 = () => {
};
var ProtocolStub = class {
static async newSession(options) {
const capabilities = emulateSessionCapabilities(options.capabilities);
const customCommands = [];
const overwrittenCommands = [];
const browser = {
options,
capabilities,
requestedCapabilities: capabilities,
customCommands,
// internally used to transfer custom commands to the actual protocol instance
overwrittenCommands,
// internally used to transfer overwritten commands to the actual protocol instance
commandList: [],
getWindowHandle: NOOP2,
on: NOOP2,
off: NOOP2,
addCommand(name, fn, commandOptions) {
customCommands.push([
name,
fn,
resolveCustomCommandOptions2("addCommand", commandOptions)
]);
},
overwriteCommand(name, fn, commandOptions) {
overwrittenCommands.push([
name,
fn,
resolveCustomCommandOptions2("overwriteCommand", commandOptions)
]);
},
...capabilitiesEnvironmentDetector(capabilities)
};
return browser;
}
/**
* added just in case user wants to somehow reload webdriver before it was started.
*/
static reloadSession() {
throw new Error("Protocol Stub: Make sure to start the session before reloading it.");
}
static attachToSession(options, modifier) {
if (options || !modifier) {
throw new Error("You are trying to attach to a protocol stub, this should never occur, please file an issue.");
}
return modifier({
commandList: []
});
}
};
function emulateSessionCapabilities(caps) {
const capabilities = {};
Object.entries(caps).forEach(([key, value]) => {
const newKey = key.replace("appium:", "");
capabilities[newKey] = value;
});
const c = "alwaysMatch" in caps ? caps.alwaysMatch : caps;
if (c.browserName && c.browserName.toLowerCase() === "chrome") {
capabilities["goog:chromeOptions"] = {};
}
return capabilities;
}
// src/utils/driver.ts
var webdriverImport;
async function getProtocolDriver(options) {
if (isStub(options.automationProtocol)) {
return { Driver: ProtocolStub, options };
}
if (typeof options.user === "string" && typeof options.key === "string") {
Object.assign(options, detectBackend(options));
}
const Driver = webdriverImport || (await import(
/* @vite-ignore */
options.automationProtocol || "webdriver"
)).default;
return { Driver, options };
}
// src/index.ts
import { WDIO_KIND as WDIO_KIND3, WDIO_CHAINABLE } from "@wdio/utils";
var Key2 = Key;
var SevereServiceError2 = SevereServiceError;
var remote = async function(params, remoteModifier) {
const keysToKeep = Object.keys(environment.value.variables.WDIO_WORKER_ID ? params : DEFAULTS);
const config = validateConfig(WDIO_DEFAULTS, params, keysToKeep);
await enableFileLogging(config.outputDir);
logger29.setLogLevelsConfig(config.logLevels, config.logLevel);
const modifier = (client, options2) => {
Object.assign(options2, Object.entries(config).reduce((a, [k, v]) => typeof v === "undefined" ? a : { ...a, [k]: v }, {}));
if (typeof remoteModifier === "function") {
client = remoteModifier(client, options2);
}
return client;
};
const { Driver, options } = await getProtocolDriver({ ...params, ...config });
const prototype = getPrototype("browser");
const instance = await Driver.newSession(options, modifier, prototype, wrapCommand4, IMPLICIT_WAIT_EXCLUSION_LIST);
if (!isStub(options.automationProtocol)) {
enableBrowsingContextCommands(instance);
}
if (params.framework && !isStub(params.automationProtocol)) {
instance.addCommand = instance.addCommand.bind(instance);
instance.overwriteCommand = instance.overwriteCommand.bind(instance);
}
instance.addLocatorStrategy = addLocatorStrategyHandler(instance);
if (!isStub(options.automationProtocol)) {
await registerSessionManager(instance);
}
return instance;
};
var attach = async function(attachOptions) {
const params = {
automationProtocol: "webdriver",
...detectBackend(attachOptions.options),
...attachOptions.options,
...attachOptions,
capabilities: attachOptions.capabilities || {},
requestedCapabilities: attachOptions.requestedCapabilities || {}
};
const prototype = getPrototype("browser");
const { Driver } = await getProtocolDriver(params);
const driver = Driver.attachToSession(
params,
void 0,
prototype,
wrapCommand4
);
enableBrowsingContextCommands(driver);
driver.addLocatorStrategy = addLocatorStrategyHandler(driver);
if (isBidi(driver.capabilities) && "_bidiHandler" in driver) {
await driver["_bidiHandler"].waitForConnected();
}
await registerSessionManager(driver);
return driver;
};
var multiRemote = async function(params, { automationProtocol } = {}) {
const multibrowser = new MultiRemote();
const browserNames = Object.keys(params);
const started = [];
let startupFailed = false;
const pending = browserNames.map(async (browserName) => {
const instance = await remote(params[browserName]);
if (startupFailed) {
await instance.deleteSession();
return [browserName, instance];
}
started.push(instance);
return [browserName, instance];
});
let sessions;
try {
sessions = await Promise.all(pending);
} catch (err) {
startupFailed = true;
const orphaned = started.splice(0);
void Promise.allSettled(pending).then(() => {
return Promise.allSettled(started.splice(0).map((instance) => instance.deleteSession()));
});
await Promise.allSettled(orphaned.map((instance) => instance.deleteSession()));
throw err;
}
for (const [browserName, instance] of sessions) {
await multibrowser.addInstance(browserName, instance);
}
const prototype = getPrototype("browser");
const firstBrowser = multibrowser.instances.get(browserNames[0]);
const sessionParams = isStub(automationProtocol) || !firstBrowser ? void 0 : {
sessionId: "",
logLevel: firstBrowser.options.logLevel
};
const ProtocolDriver = typeof automationProtocol === "string" ? (await import(
/* @vite-ignore */
automationProtocol
)).default : WebDriver;
const driver = ProtocolDriver.attachToSession(
sessionParams,
multibrowser.modifier.bind(multibrowser),
prototype,
wrapCommand4
);
if (!isStub(automationProtocol)) {
const origAddCommand = driver.addCommand.bind(driver);
driver.addCommand = function(name, fn, options) {
const resolved = resolveCustomCommandOptions3("addCommand", options);
driver.instances.forEach(
(instanceName) => driver.getInstance(instanceName).addCommand(name, fn, resolved)
);
if (resolved.attachToBrowsingContext) {
return;
}
return origAddCommand(
name,
fn,
{
attachToElement: resolved.attachToElement,
proto: Object.getPrototypeOf(multibrowser.baseInstance),
instances: Object.fromEntries(multibrowser.instances)
}
);
};
const origOverwriteCommand = driver.overwriteCommand.bind(driver);
driver.overwriteCommand = ((name, fn, options) => {
const resolved = resolveCustomCommandOptions3("overwriteCommand", options);
if (resolved.attachToBrowsingContext) {
driver.instances.forEach(
(instanceName) => driver.getInstance(instanceName).overwriteCommand(name, fn, resolved)
);
return;
}
return origOverwriteCommand(
name,
fn,
{
attachToElement: resolved.attachToElement,
proto: Object.getPrototypeOf(multibrowser.baseInstance),
instances: Object.fromEntries(multibrowser.instances)
}
);
});
}
driver.addLocatorStrategy = addLocatorStrategyHandler(driver);
return driver;
};
// src/node.ts
environment.value = {
osType: () => os.type(),
readFileSync: fs8.readFileSync,
downloadFile,
savePDF,
saveRecordingScreen,
extensionDataFromPath,
saveScreenshot,
saveElementScreenshot,
variables: process2.env
};
export {
Key2 as Key,
KeyAction,
MobileScrollDirection,
PointerAction,
SevereServiceError2 as SevereServiceError,
StrictSelectorError,
WDIO_CHAINABLE,
WDIO_KIND3 as WDIO_KIND,
WheelAction,
attach,
buttonValue,
deviceDescriptorsSource,
getContextManager,
multiRemote,
remote
};
/*!
* ensure that timeout and interval are set properly
*/