"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.build = exports.switchWorld = exports.getWorlds = exports.getCurrentWorld = exports.takeScreenshot = exports.performAction = exports.visit = exports.getUrl = exports.getTitle = exports.getCookies = exports.getSystemBars = exports.setOrientation = exports.getOrientation = exports.setViewportSize = exports.setWindowSize = exports.getWindowSize = exports.getCapabilities = exports.getDriverInfo = exports.childContext = exports.parentContext = exports.mainContext = exports.click = exports.hover = exports.getElementText = exports.setElementText = exports.getElementAttribute = exports.getElementRegion = exports.findElements = exports.findElement = exports.executeScript = exports.toSimpleCommonSelector = exports.toSelector = exports.toElement = exports.toDriver = exports.isStaleElementError = exports.isEqualElements = exports.isSelector = exports.isShadowRoot = exports.isSecondaryElement = exports.isElement = exports.isSecondaryDriver = exports.isDriver = void 0;
const url_1 = require("url");
const http_proxy_agent_1 = __importDefault(require("http-proxy-agent"));
const https_proxy_agent_1 = __importDefault(require("https-proxy-agent"));
const http_1 = __importDefault(require("http"));
const https_1 = __importDefault(require("https"));
const utils = __importStar(require("@applitools/utils"));
const LEGACY_ELEMENT_ID = 'ELEMENT';
const ELEMENT_ID = 'element-6066-11e4-a52e-4f735466cecf';
const SHADOW_ROOT_ID = 'shadow-6066-11e4-a52e-4f735466cecf';
const W3C_CAPABILITIES = ['platformName', 'platformVersion'];
const W3C_SECONDARY_CAPABILITIES = ['pageLoadStrategy'];
const W3C_SAFARI_CAPABILITIES = ['browserVersion', 'setWindowRect'];
const APPIUM_CAPABILITIES = ['appiumVersion', 'deviceType', 'deviceOrientation', 'deviceName', 'automationName'];
const LEGACY_APPIUM_CAPABILITIES = ['appium-version', 'device-type', 'device-orientation'];
const CHROME_CAPABILITIES = ['chrome', 'goog:chromeOptions'];
const MOBILE_BROWSER_NAMES = ['ipad', 'iphone', 'android'];
const ANDROID_PLATFORM_NAME = 'android';
const ANDROID_AUTOMATION_NAME = 'uiautomator2';
function extractElementId(element) {
    var _a;
    return ((_a = element[ELEMENT_ID]) !== null && _a !== void 0 ? _a : element[LEGACY_ELEMENT_ID]);
}
function extractShadowRootId(shadowRoot) {
    return shadowRoot[SHADOW_ROOT_ID];
}
function extractEnvironment(capabilities) {
    var _a, _b, _c, _d;
    const isAppium = APPIUM_CAPABILITIES.some(capability => capabilities.hasOwnProperty(capability)) ||
        APPIUM_CAPABILITIES.some(capability => capabilities.hasOwnProperty(`appium:${capability}`));
    const isChrome = CHROME_CAPABILITIES.includes((_a = capabilities.browserName) === null || _a === void 0 ? void 0 : _a.toLowerCase());
    const isW3C = isAppium ||
        W3C_SECONDARY_CAPABILITIES.every(capability => capabilities.hasOwnProperty(capability)) ||
        W3C_CAPABILITIES.every(capability => capabilities.hasOwnProperty(capability)) ||
        W3C_SAFARI_CAPABILITIES.every(capability => capabilities.hasOwnProperty(capability));
    const isMobile = capabilities.browserName === '' ||
        isAppium ||
        LEGACY_APPIUM_CAPABILITIES.some(capability => capabilities.hasOwnProperty(capability)) ||
        MOBILE_BROWSER_NAMES.includes((_b = capabilities.browserName) === null || _b === void 0 ? void 0 : _b.toLowerCase());
    const isAndroid = ((_c = capabilities.platformName) === null || _c === void 0 ? void 0 : _c.toLowerCase()) === ANDROID_PLATFORM_NAME ||
        ((_d = capabilities.automationName) === null || _d === void 0 ? void 0 : _d.toLowerCase()) === ANDROID_AUTOMATION_NAME;
    return {
        isAndroid,
        isChrome,
        isMobile,
        isW3C,
    };
}
function getFrameworkMajorVersion() {
    const version = require('webdriver/package.json').version;
    return Number.parseInt(version);
}
function getWebDriver() {
    return getFrameworkMajorVersion() < 8 ? require('webdriver').default : require('webdriver');
}
function isDriver(driver) {
    if (!driver)
        return false;
    return utils.types.instanceOf(driver, 'Browser');
}
exports.isDriver = isDriver;
function isSecondaryDriver(driver) {
    if (!driver)
        return false;
    return utils.types.has(driver, ['sessionId', 'serverUrl']);
}
exports.isSecondaryDriver = isSecondaryDriver;
function isElement(element) {
    if (!element)
        return false;
    return !!extractElementId(element);
}
exports.isElement = isElement;
function isSecondaryElement(element) {
    if (!element)
        return false;
    return !!element.elementId;
}
exports.isSecondaryElement = isSecondaryElement;
function isShadowRoot(shadowRoot) {
    if (!shadowRoot)
        return false;
    return !!extractShadowRootId(shadowRoot);
}
exports.isShadowRoot = isShadowRoot;
function isSelector(selector) {
    if (!selector)
        return false;
    return utils.types.has(selector, ['using', 'value']);
}
exports.isSelector = isSelector;
function isEqualElements(_driver, element1, element2) {
    if (!element1 || !element2)
        return false;
    const elementId1 = extractElementId(element1);
    const elementId2 = extractElementId(element2);
    return elementId1 === elementId2;
}
exports.isEqualElements = isEqualElements;
function isStaleElementError(error) {
    if (!error)
        return false;
    const errOrResult = error.originalError || error;
    return errOrResult instanceof Error && errOrResult.name === 'stale element reference';
}
exports.isStaleElementError = isStaleElementError;
function toDriver(driver) {
    var _a, _b, _c;
    let transformedDriver;
    if (utils.types.has(driver, ['sessionId', 'serverUrl'])) {
        const url = new URL(driver.serverUrl);
        const environment = extractEnvironment(driver.capabilities);
        const options = {
            sessionId: driver.sessionId,
            protocol: url.protocol ? url.protocol.replace(/:$/, '') : undefined,
            hostname: url.hostname,
            port: Number(url.port) || undefined,
            path: url.pathname,
            capabilities: driver.capabilities,
            logLevel: 'silent',
            ...environment,
        };
        if (!options.port) {
            if (options.protocol === 'http')
                options.port = 80;
            if (options.protocol === 'https')
                options.port = 443;
        }
        if ((_a = driver.proxy) === null || _a === void 0 ? void 0 : _a.url) {
            const proxyUrl = new URL(driver.proxy.url);
            proxyUrl.username = (_b = driver.proxy.username) !== null && _b !== void 0 ? _b : proxyUrl.username;
            proxyUrl.password = (_c = driver.proxy.password) !== null && _c !== void 0 ? _c : proxyUrl.password;
            const proxyOptions = { ...(0, url_1.parse)(proxyUrl.href), rejectUnauthorized: false };
            const httpAgent = (0, http_proxy_agent_1.default)(proxyOptions);
            const httpsAgent = (0, https_proxy_agent_1.default)(proxyOptions);
            httpsAgent.callback = utils.general.wrap(httpsAgent.callback.bind(httpsAgent), (fn, request, options, ...rest) => {
                return fn(request, { ...options, rejectUnauthorized: false }, ...rest);
            });
            options.agent = { http: httpAgent, https: httpsAgent };
        }
        else {
            const httpAgent = http_1.default.globalAgent;
            const httpsAgent = new https_1.default.Agent({ rejectUnauthorized: false });
            options.agent = { http: httpAgent, https: httpsAgent };
        }
        const WebDriver = getWebDriver();
        transformedDriver = WebDriver.attachToSession(options);
        transformedDriver.original = driver.original;
    }
    else {
        // NOTE: this is needed to a rare case when this function is used not on the SecondaryDriver but on a derivative of the Driver
        transformedDriver = Object.create(driver, {
            original: { enumerable: false, get: () => driver },
        });
    }
    if (utils.types.isFunction(transformedDriver, 'addCommand')) {
        const command = getFrameworkMajorVersion() < 8 ? require('webdriver/build/command').default : require('webdriver').command;
        transformedDriver.addCommand('_getWindowSize', command('GET', '/session/:sessionId/window/current/size', {
            command: '_getWindowSize',
            description: '',
            ref: '',
            parameters: [],
        }));
        transformedDriver.addCommand('_setWindowSize', command('POST', '/session/:sessionId/window/current/size', {
            command: '_setWindowSize',
            parameters: [
                { name: 'width', type: 'number', required: true, description: '' },
                { name: 'height', type: 'number', required: true, description: '' },
            ],
            description: '',
            ref: '',
        }));
        transformedDriver.addCommand('setWindowPosition', command('POST', '/session/:sessionId/window/current/position', {
            command: 'setWindowPosition',
            parameters: [
                { name: 'x', type: 'number', required: true, description: '' },
                { name: 'y', type: 'number', required: true, description: '' },
            ],
            description: '',
            ref: '',
        }));
        transformedDriver.addCommand('sendCommandAndGetResult', command('POST', '/session/:sessionId/chromium/send_command_and_get_result', {
            command: 'sendCommandAndGetResult',
            parameters: [
                { name: 'cmd', type: 'string', required: true, description: '' },
                { name: 'params', type: 'object', required: true, description: '' },
            ],
            description: 'Send a command to the DevTools debugger and wait for the result.',
            ref: '',
        }));
    }
    return transformedDriver;
}
exports.toDriver = toDriver;
function toElement(element) {
    const elementId = utils.types.has(element, 'elementId') ? element.elementId : extractElementId(element);
    return { [ELEMENT_ID]: elementId, [LEGACY_ELEMENT_ID]: elementId };
}
exports.toElement = toElement;
function toSelector(selector) {
    if (utils.types.has(selector, 'selector')) {
        if (utils.types.has(selector, 'type') && selector.type && utils.types.isString(selector.selector)) {
            return { using: selector.type === 'css' ? 'css selector' : selector.type, value: selector.selector };
        }
        else if (isSelector(selector.selector)) {
            return selector.selector;
        }
        else {
            selector = selector.selector;
        }
    }
    if (utils.types.isString(selector)) {
        return { using: 'css selector', value: selector };
    }
    return selector;
}
exports.toSelector = toSelector;
function toSimpleCommonSelector(selector) {
    if (utils.types.has(selector, ['using', 'value'])) {
        return { type: selector.using === 'css selector' ? 'css' : selector.using, selector: selector.value };
    }
    return selector;
}
exports.toSimpleCommonSelector = toSimpleCommonSelector;
async function executeScript(driver, script, arg) {
    script = utils.types.isFunction(script) ? `return (${script}).apply(null, arguments)` : script;
    // temporary solution for webdriver package not throwing on stale elements starting from Chrome 122
    // https://github.com/webdriverio/webdriverio/blob/5df72f58475c4f580c57d5b805588c204c44c2af/packages/webdriver/src/request/index.ts#L258
    const result = await driver.executeScript(script, [arg]);
    if (result === null || result === void 0 ? void 0 : result.error) {
        throw new Error(result.error);
    }
    return result;
}
exports.executeScript = executeScript;
async function findElement(driver, selector, parent) {
    const parentId = parent ? (isShadowRoot(parent) ? extractShadowRootId(parent) : extractElementId(parent)) : null;
    try {
        const element = parentId
            ? await driver.findElementFromElement(parentId, selector.using, selector.value)
            : await driver.findElement(selector.using, selector.value);
        return isElement(element) ? element : null;
    }
    catch (error) {
        if (/element could not be located/i.test(error.message) ||
            /cannot locate an element/i.test(error.message) ||
            /wasn\'t found/i.test(error.message)) {
            return null;
        }
        throw error;
    }
}
exports.findElement = findElement;
async function findElements(driver, selector, parent) {
    const parentId = parent ? (isShadowRoot(parent) ? extractShadowRootId(parent) : extractElementId(parent)) : null;
    return parentId
        ? await driver.findElementsFromElement(parentId, selector.using, selector.value)
        : await driver.findElements(selector.using, selector.value);
}
exports.findElements = findElements;
async function getElementRegion(driver, element) {
    return driver.getElementRect(extractElementId(element));
}
exports.getElementRegion = getElementRegion;
async function getElementAttribute(driver, element, attr) {
    return driver.getElementAttribute(extractElementId(element), attr);
}
exports.getElementAttribute = getElementAttribute;
async function setElementText(driver, element, text) {
    await driver.elementClear(extractElementId(element));
    await driver.elementSendKeys(extractElementId(element), text);
}
exports.setElementText = setElementText;
async function getElementText(driver, element) {
    return driver.getElementText(extractElementId(element));
}
exports.getElementText = getElementText;
async function hover(driver, element) {
    if (!driver.isW3C)
        return await driver.moveToElement(extractElementId(element));
    await driver.performActions([
        {
            type: 'pointer',
            id: 'mouse',
            parameters: { pointerType: 'mouse' },
            actions: [{ type: 'pointerMove', duration: 0, origin: element, x: 0, y: 0 }],
        },
    ]);
}
exports.hover = hover;
async function click(driver, element) {
    await driver.elementClick(extractElementId(element));
}
exports.click = click;
async function mainContext(driver) {
    await driver.switchToFrame(null);
    return driver;
}
exports.mainContext = mainContext;
async function parentContext(driver) {
    await driver.switchToParentFrame();
    return driver;
}
exports.parentContext = parentContext;
async function childContext(driver, element) {
    await driver.switchToFrame(element);
    return driver;
}
exports.childContext = childContext;
async function getDriverInfo(driver) {
    var _a;
    const driverServerUrl = `${driver.options.protocol}://${driver.options.hostname}${driver.options.port ? `:${driver.options.port}` : ''}${(_a = driver.options.path) !== null && _a !== void 0 ? _a : ''}`;
    return { sessionId: driver.sessionId, driverServerUrl };
}
exports.getDriverInfo = getDriverInfo;
async function getCapabilities(driver) {
    return driver.capabilities;
}
exports.getCapabilities = getCapabilities;
async function getWindowSize(driver) {
    try {
        const rect = await driver.getWindowRect();
        return { width: rect.width, height: rect.height };
    }
    catch {
        return driver._getWindowSize();
    }
}
exports.getWindowSize = getWindowSize;
async function setWindowSize(driver, size) {
    try {
        await driver.setWindowRect(0, 0, size.width, size.height);
    }
    catch {
        await driver.setWindowPosition(0, 0);
        await driver._setWindowSize(size.width, size.height);
    }
}
exports.setWindowSize = setWindowSize;
async function setViewportSize(driver, size) {
    await driver.sendCommandAndGetResult('Emulation.setDeviceMetricsOverride', {
        ...size,
        deviceScaleFactor: 0,
        mobile: false,
    });
}
exports.setViewportSize = setViewportSize;
async function getOrientation(browser) {
    const orientation = await browser.getOrientation();
    return orientation.toLowerCase();
}
exports.getOrientation = getOrientation;
async function setOrientation(browser, orientation) {
    return await browser.setOrientation(orientation);
}
exports.setOrientation = setOrientation;
async function getSystemBars(browser) {
    return browser.getSystemBars();
}
exports.getSystemBars = getSystemBars;
async function getCookies(driver, context) {
    if (context)
        return driver.getAllCookies();
    const response = await driver.sendCommandAndGetResult('Network.getAllCookies', {});
    const cookies = response.cookies;
    return cookies.map((cookie) => {
        const copy = { ...cookie, expiry: cookie.expires };
        delete copy.expires;
        delete copy.size;
        delete copy.priority;
        delete copy.session;
        delete copy.sameParty;
        delete copy.sourceScheme;
        delete copy.sourcePort;
        return copy;
    });
}
exports.getCookies = getCookies;
async function getTitle(driver) {
    return driver.getTitle();
}
exports.getTitle = getTitle;
async function getUrl(driver) {
    return driver.getUrl();
}
exports.getUrl = getUrl;
async function visit(driver, url) {
    await driver.navigateTo(url);
}
exports.visit = visit;
async function performAction(driver, steps) {
    return driver.touchPerform(steps.map(({ action, ...options }) => ({ action, options })));
}
exports.performAction = performAction;
async function takeScreenshot(driver) {
    return driver.takeScreenshot();
}
exports.takeScreenshot = takeScreenshot;
async function getCurrentWorld(driver) {
    const world = await driver.getContext();
    return utils.types.isString(world) ? world : world.id;
}
exports.getCurrentWorld = getCurrentWorld;
async function getWorlds(driver) {
    const worlds = await driver.getContexts();
    return worlds.map(world => (utils.types.isString(world) ? world : world.id));
}
exports.getWorlds = getWorlds;
async function switchWorld(driver, id) {
    await driver.switchContext(id);
}
exports.switchWorld = switchWorld;
const browserOptionsNames = {
    chrome: 'goog:chromeOptions',
    firefox: 'moz:firefoxOptions',
};
/*
 * Spawn a browser with a given configuration (INTERNAL USE ONLY)
 *
 * NOTE:
 * This function is intended for internal use only. As a result it relies on some dev dependencies.
 * When wiring the spec-driver up to an SDK and calling this function, if you don't have the same dev deps
 * installed in the SDK, then this function will error.
 */
async function build(env) {
    const WebDriver = getWebDriver();
    const parseEnv = require('@applitools/test-utils/src/parse-env');
    const { browser = '', capabilities, url, proxy, configurable = true, args = [], headless, logLevel = 'silent', } = parseEnv(env);
    const options = {
        capabilities: { browserName: browser, ...capabilities },
        logLevel,
    };
    options.protocol = url.protocol ? url.protocol.replace(/:$/, '') : undefined;
    options.hostname = url.hostname;
    if (url.port)
        options.port = Number(url.port);
    else if (options.protocol === 'http')
        options.port = 80;
    else if (options.protocol === 'https')
        options.port = 443;
    options.path = url.pathname;
    if (configurable) {
        const browserOptionsName = browserOptionsNames[browser || options.capabilities.browserName];
        if (browserOptionsName) {
            const browserOptions = options.capabilities[browserOptionsName] || {};
            browserOptions.args = [...(browserOptions.args || []), ...args];
            if (headless)
                browserOptions.args.push('headless');
            options.capabilities[browserOptionsName] = browserOptions;
        }
    }
    if (proxy) {
        options.capabilities.proxy = {
            proxyType: 'manual',
            httpProxy: proxy.http || proxy.server,
            sslProxy: proxy.https || proxy.server,
            ftpProxy: proxy.ftp,
            noProxy: proxy.bypass.join(','),
        };
    }
    options.agent = { https: require('https').Agent({ rejectUnauthorized: false }) };
    const driver = await WebDriver.newSession(options);
    return [driver, () => driver.deleteSession()];
}
exports.build = build;
