"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.SeleniumPlugin = void 0;
exports.default = seleniumProxy;
const types_1 = require("@testring/types");
const path = __importStar(require("path"));
const fs = __importStar(require("fs"));
const webdriverio_1 = require("webdriverio");
const deepmerge = __importStar(require("deepmerge"));
const child_process_1 = require("@testring/child-process");
const logger_1 = require("@testring/logger");
const dwnld_collector_crx_1 = require("@testring/dwnld-collector-crx");
const tree_kill_1 = __importDefault(require("tree-kill"));
const webdriver_1 = __importDefault(require("webdriver"));
const DEFAULT_CONFIG = {
    recorderExtension: false,
    clientCheckInterval: 5 * 1000,
    clientTimeout: 15 * 60 * 1000,
    port: 4444,
    logLevel: 'error',
    capabilities: {
        browserName: 'chrome',
        'goog:chromeOptions': {
            args: [],
        },
        'wdio:enforceWebDriverClassic': true,
    },
    disableClientPing: false,
    localVersion: 'v3',
    seleniumArgs: [],
};
function delay(timeout) {
    return new Promise((resolve) => setTimeout(() => resolve(), timeout));
}
function stringifyWindowFeatures(windowFeatures) {
    let result;
    if (typeof windowFeatures === 'string') {
        result = windowFeatures;
    }
    else {
        const features = windowFeatures;
        result = Object.keys(features)
            .map((key) => `${key}=${features[key]}`)
            .join(',');
    }
    return result;
}
function getSeleniumJarPath(version) {
    if (version === 'v4') {
        // For Selenium v4, we expect the JAR to be in a specific location
        const seleniumV4Path = path.join(__dirname, '..', 'selenium-server-v4', 'selenium-server-4.34.0.jar');
        if (!fs.existsSync(seleniumV4Path)) {
            throw new Error(`Selenium v4 JAR not found at expected path: ${seleniumV4Path}. ` +
                'Please ensure selenium-server-4.34.0.jar is available in the package directory.');
        }
        return seleniumV4Path;
    }
    // For v3 and other versions, use the original logic
    const seleniumServer = require('selenium-server');
    return seleniumServer.path;
}
function setupProcessListeners(seleniumProcess, resolve, reject, version, logger) {
    var _a, _b, _c, _d;
    if (!seleniumProcess.stderr && !seleniumProcess.stdout) {
        reject(new Error('There is no STDERR or STDOUT on selenium worker'));
        return;
    }
    let isReady = false;
    const timeout = setTimeout(() => {
        if (!isReady) {
            reject(new Error(`Selenium server failed to start within 30 seconds (version: ${version})`));
        }
    }, 30000);
    // Check for server readiness
    const readyMessages = {
        v4: 'Started Selenium Standalone',
        v3: 'SeleniumServer.boot',
    };
    const checkForReadyMessage = (message, stream) => {
        logger.verbose(`[Selenium ${version}] [${stream.toUpperCase()}] ${message.trim()}`);
        if (message.includes(readyMessages[version])) {
            isReady = true;
            clearTimeout(timeout);
            setTimeout(resolve, 500); // Small delay to ensure server is fully ready
        }
    };
    // For v4, ready message appears in stdout
    if (version === 'v4') {
        (_a = seleniumProcess.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (data) => {
            checkForReadyMessage(data.toString(), 'stdout');
        });
        // Also listen to stderr for error messages
        (_b = seleniumProcess.stderr) === null || _b === void 0 ? void 0 : _b.on('data', (data) => {
            logger.verbose(`[Selenium ${version}] [STDERR] ${data.toString().trim()}`);
        });
    }
    else {
        // For v3, ready message appears in stderr
        (_c = seleniumProcess.stderr) === null || _c === void 0 ? void 0 : _c.on('data', (data) => {
            checkForReadyMessage(data.toString(), 'stderr');
        });
        // Also listen to stdout for other messages
        (_d = seleniumProcess.stdout) === null || _d === void 0 ? void 0 : _d.on('data', (data) => {
            logger.verbose(`[Selenium ${version}] [STDOUT] ${data.toString().trim()}`);
        });
    }
    seleniumProcess.on('error', (error) => {
        clearTimeout(timeout);
        reject(new Error(`Selenium process error: ${error.message}`));
    });
    seleniumProcess.on('exit', (code) => {
        if (!isReady) {
            clearTimeout(timeout);
            reject(new Error(`Selenium process exited with code ${code} before becoming ready`));
        }
    });
}
class SeleniumPlugin {
    constructor(config = {}) {
        this.logger = logger_1.loggerClient.withPrefix('[selenium-browser-process]');
        this.expiredBrowserClients = new Set();
        this.browserClients = new Map();
        this.customBrowserClientsConfigs = new Map();
        this.waitForReadyState = Promise.resolve();
        this.incrementWinId = 0;
        this.killed = false; // Flag to prevent operations after kill
        this.config = this.createConfig(config);
        if (this.config.host === undefined) {
            this.runLocalSelenium();
            this.setupProcessCleanup();
        }
        this.initIntervals();
    }
    // eslint-disable-next-line sonarjs/cognitive-complexity
    createConfig(config) {
        var _a;
        const mergedConfig = deepmerge.all([DEFAULT_CONFIG, config], {
            clone: true,
        });
        if (!mergedConfig.hostname && mergedConfig.host) {
            mergedConfig.hostname = mergedConfig.host;
        }
        const capabilities = mergedConfig.capabilities;
        const googleChromeOptions = capabilities === null || capabilities === void 0 ? void 0 : capabilities['goog:chromeOptions'];
        if ((_a = googleChromeOptions === null || googleChromeOptions === void 0 ? void 0 : googleChromeOptions.args) === null || _a === void 0 ? void 0 : _a.includes('--headless=new')) {
            const extensions = googleChromeOptions.extensions;
            const dowldMonitorCrx = (0, dwnld_collector_crx_1.getCrxBase64)();
            if (extensions) {
                extensions.push(dowldMonitorCrx);
            }
            else {
                googleChromeOptions.extensions = [dowldMonitorCrx];
            }
        }
        return mergedConfig;
    }
    initIntervals() {
        if (this.config.workerLimit !== 'local' &&
            !this.config.disableClientPing) {
            if (this.config.clientCheckInterval > 0) {
                this.clientCheckInterval = setInterval(() => this.checkClientsTimeout(), this.config.clientCheckInterval);
            }
            process.on('exit', () => {
                clearInterval(this.clientCheckInterval);
                this.stopAllSessions().catch((err) => {
                    this.logger.error('Clean process exit failed', err);
                });
            });
        }
    }
    setupProcessCleanup() {
        process.on('exit', () => this.forceKillSelenium());
        process.on('SIGINT', () => this.forceKillSelenium());
        process.on('SIGTERM', () => this.forceKillSelenium());
        // Debug mode specific cleanup handlers
        process.on('SIGUSR1', () => this.forceKillSelenium()); // Debugger disconnect
        process.on('SIGUSR2', () => this.forceKillSelenium()); // Debugger disconnect alternative
        // Note: SIGKILL cannot be caught or handled - it immediately terminates the process
    }
    forceKillSelenium() {
        if (this.localSelenium &&
            !this.localSelenium.killed &&
            this.localSelenium.pid) {
            this.logger.debug('Force killing Selenium process tree due to signal');
            (0, tree_kill_1.default)(this.localSelenium.pid, 'SIGKILL', (err) => {
                if (err) {
                    this.logger.error(`Failed to force kill Selenium process tree: ${err.message}`);
                }
                else {
                    this.logger.debug('Selenium process tree force killed');
                }
            });
        }
    }
    stopAllSessions() {
        const clientsRequests = [];
        for (const [applicant] of this.browserClients) {
            this.logger.debug(`Stopping sessions before process exit for applicant ${applicant}.`);
            clientsRequests.push(this.end(applicant).catch((err) => {
                this.logger.error(`Session stop before process exit error for applicant ${applicant}: \n`, err);
            }));
        }
        return Promise.all(clientsRequests);
    }
    getChromeDriverPath() {
        if (this.config.chromeDriverPath) {
            return this.config.chromeDriverPath;
        }
        return require('chromedriver').path;
    }
    getChromeDriverArgs() {
        const chromeDriverPath = this.getChromeDriverPath();
        return [`-Dwebdriver.chrome.driver=${chromeDriverPath}`];
    }
    buildSeleniumArgs(seleniumJarPath, version, port, chromeDriverArgs) {
        const args = [...chromeDriverArgs];
        if (version === 'v4') {
            args.push('-jar', seleniumJarPath, 'standalone', '--port', port.toString(), '--host', '127.0.0.1');
        }
        else {
            args.push('-jar', seleniumJarPath, '-port', port.toString());
        }
        // Append custom selenium arguments if provided
        if (this.config.seleniumArgs && this.config.seleniumArgs.length > 0) {
            args.push(...this.config.seleniumArgs);
        }
        return args;
    }
    async runLocalSelenium() {
        const version = this.config.localVersion || 'v3';
        this.logger.debug(`Init local selenium server (version: ${version})`);
        try {
            const seleniumJarPath = getSeleniumJarPath(version);
            const chromeDriverArgs = this.getChromeDriverArgs();
            const seleniumArgs = this.buildSeleniumArgs(seleniumJarPath, version, this.config.port || 4444, chromeDriverArgs);
            this.logger.debug(`Starting Selenium with command: java ${seleniumArgs.join(' ')}`);
            this.localSelenium = (0, child_process_1.spawnWithPipes)('java', seleniumArgs);
            this.waitForReadyState = new Promise((resolve, reject) => {
                if (this.localSelenium) {
                    setupProcessListeners(this.localSelenium, resolve, reject, version, this.logger);
                }
                else {
                    reject(new Error('Failed to spawn Selenium process'));
                }
            });
            // Wait for the server to be ready
            await this.waitForReadyState;
            this.logger.debug(`Selenium server (${version}) is ready`);
        }
        catch (err) {
            this.logger.error(`Local selenium server init failed (version: ${version})`, err);
            throw err; // Re-throw to allow upstream error handling
        }
    }
    getApplicantSessionId(applicant) {
        const item = this.browserClients.get(applicant);
        return item === null || item === void 0 ? void 0 : item.sessionId;
    }
    hasBrowserClient(applicant) {
        return this.browserClients.has(applicant);
    }
    getBrowserClient(applicant) {
        const item = this.browserClients.get(applicant);
        if (item) {
            return item.client;
        }
        throw new Error('Browser client is not found');
    }
    async pingClients() {
        for (const [applicant] of this.browserClients) {
            try {
                await this.execute(applicant, '(function () {})()', []);
            }
            catch (e) {
                /* ignore */
            }
        }
    }
    async closeExpiredClients() {
        const timeLimit = Date.now() - this.config.clientTimeout;
        for (const [applicant, clientData] of this.browserClients) {
            if (clientData.initTime < timeLimit) {
                this.logger.warn(`Session applicant ${applicant} marked as expired`);
                try {
                    await this.end(applicant);
                }
                catch (e) {
                    this.logger.error(`Session applicant ${applicant} failed to stop`, e);
                }
                this.expiredBrowserClients.add(applicant);
            }
        }
    }
    async checkClientsTimeout() {
        if (this.config.clientTimeout === 0) {
            await this.pingClients();
        }
        else {
            await this.closeExpiredClients();
        }
    }
    setCustomBrowserClientConfig(applicant, config) {
        this.customBrowserClientsConfigs.set(applicant, config);
    }
    getCustomBrowserClientConfig(applicant) {
        return this.customBrowserClientsConfigs.get(applicant);
    }
    async createClient(applicant, config) {
        if (this.killed) {
            throw new Error('SeleniumPlugin is being killed');
        }
        await this.waitForReadyState;
        const clientData = this.browserClients.get(applicant);
        if (clientData) {
            this.browserClients.set(applicant, {
                ...clientData,
                initTime: Date.now(),
            });
            return;
        }
        if (this.expiredBrowserClients.has(applicant)) {
            throw Error(`This session expired in ${this.config.clientTimeout}ms`);
        }
        const _config = deepmerge.all([
            {},
            this.config,
            config || {},
            this.customBrowserClientsConfigs.get(applicant) || {},
        ]);
        const client = await (0, webdriverio_1.remote)(_config);
        let sessionId;
        if (client.sessionId) {
            sessionId = client.sessionId;
        }
        else {
            throw Error('Session can not be null');
        }
        const customClient = this.addCustromMethods(client);
        this.browserClients.set(applicant, {
            client: customClient,
            sessionId,
            initTime: Date.now(),
        });
        this.logger.debug(`Started session for applicant: ${applicant}. Session id: ${sessionId}`);
    }
    addCustromMethods(client) {
        return client;
    }
    async getElement(applicant, selector) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        if ((0, types_1.isXpathSelector)(selector)) {
            return client.$(selector.xpath);
        }
        else if ((0, types_1.isShadowCssSelector)(selector)) {
            return this.getElementFromShadowCss(client, selector);
        }
        throw new Error('Unknown selector type');
    }
    async getElementFromShadowCss(client, selector) {
        const { css, parentSelectors } = selector;
        // Error first: validate selector structure
        this.validateShadowCssSelector(selector);
        try {
            return await this.traverseShadowDom(client, css, parentSelectors);
        }
        catch (error) {
            // Provide more context in error messages
            if (error instanceof Error) {
                throw new Error(`Shadow DOM traversal failed: ${error.message}. Selector: ${JSON.stringify(selector)}`);
            }
            throw error;
        }
    }
    validateShadowCssSelector(selector) {
        const { css, parentSelectors } = selector;
        if (!css || typeof css !== 'string') {
            throw new Error('Shadow CSS selector must have a valid CSS string');
        }
        if (!Array.isArray(parentSelectors) || parentSelectors.length === 0) {
            throw new Error('Shadow CSS selector must have at least one parent selector');
        }
        // Validate all parent selectors are non-empty strings
        for (const [index, parentSelector] of parentSelectors.entries()) {
            if (!parentSelector || typeof parentSelector !== 'string') {
                throw new Error(`Parent selector at index ${index} must be a non-empty string`);
            }
        }
    }
    async traverseToLastParentSelector(client, parentSelectors) {
        const [firstParentSelector, ...restParentSelectors] = parentSelectors;
        // TypeScript assertion: we know firstParentSelector exists due to validation
        if (!firstParentSelector) {
            throw new Error('First parent selector is required');
        }
        // Get the first parent element
        let currentElement = await client.$(firstParentSelector);
        if (!currentElement) {
            throw new Error(`Failed to find parent element with selector: ${firstParentSelector}`);
        }
        // Traverse through shadow DOM hierarchy
        for (const parentSelector of restParentSelectors) {
            const shadowElement = await currentElement.shadow$(parentSelector);
            if (!shadowElement) {
                throw new Error(`Failed to find shadow element with selector: ${parentSelector}`);
            }
            currentElement = shadowElement;
        }
        return currentElement;
    }
    async traverseShadowDom(client, css, parentSelectors) {
        // Traverse to the last parent selector
        const lastParentElement = await this.traverseToLastParentSelector(client, parentSelectors);
        // Get the final target element within the shadow DOM
        const targetElement = await lastParentElement.shadow$(css);
        if (!targetElement) {
            throw new Error(`Failed to find target element with CSS selector: ${css}`);
        }
        return targetElement;
    }
    async end(applicant) {
        await this.waitForReadyState;
        if (!this.hasBrowserClient(applicant)) {
            this.logger.warn(`No ${applicant} is registered`);
            return;
        }
        const client = this.getBrowserClient(applicant);
        try {
            await this.alertDismiss(applicant);
        }
        catch {
            /* ignore */
        }
        const startingSessionID = this.getApplicantSessionId(applicant);
        const sessionID = client.sessionId;
        if (startingSessionID === sessionID) {
            this.logger.debug(`Stopping sessions for applicant ${applicant}. Session id: ${sessionID}`);
            await client.deleteSession();
        }
        else {
            await this.logger.stepWarning(`Stopping sessions for applicant warning ${applicant}. ` +
                `Session ids are not equal, started with - ${startingSessionID}, ended with - ${sessionID}`, async () => {
                try {
                    if (startingSessionID) {
                        const attachedClient = webdriver_1.default.attachToSession({
                            sessionId: startingSessionID,
                        });
                        await attachedClient.deleteSession();
                    }
                }
                catch (err) {
                    this.logger.error(`Old session ${startingSessionID} delete error`, err);
                }
                try {
                    await client.deleteSession();
                }
                catch (err) {
                    this.logger.error(`New session ${client.sessionId} delete error`, err);
                }
            });
        }
        // Empirically: pause after close() to let Selenium kill ChromeDriver cleanly (no zombie process).
        if (this.config.delayAfterSessionClose) {
            await delay(this.config.delayAfterSessionClose);
        }
        this.browserClients.delete(applicant);
        this.customBrowserClientsConfigs.delete(applicant);
    }
    async closeAllBrowserSessions() {
        for (const applicant of this.browserClients.keys()) {
            try {
                await this.end(applicant);
            }
            catch (e) {
                this.logger.error(e);
            }
        }
    }
    cleanupSeleniumProcess() {
        var _a, _b, _c, _d;
        if (!this.localSelenium) {
            return;
        }
        // remove listener
        if (this.localSelenium.stderr) {
            this.localSelenium.stderr.removeAllListeners('data');
            (_a = this.localSelenium.stdout) === null || _a === void 0 ? void 0 : _a.removeAllListeners();
        }
        // Ensure all pipes are closed
        (_b = this.localSelenium.stdout) === null || _b === void 0 ? void 0 : _b.destroy();
        (_c = this.localSelenium.stderr) === null || _c === void 0 ? void 0 : _c.destroy();
        (_d = this.localSelenium.stdin) === null || _d === void 0 ? void 0 : _d.destroy();
    }
    async killSeleniumProcessTree(pid) {
        return new Promise((resolve) => {
            (0, tree_kill_1.default)(pid, 'SIGTERM', (err) => {
                if (err) {
                    this.logger.warn(`Failed to kill process tree with SIGTERM: ${err.message}`);
                    // Fallback to SIGKILL if SIGTERM fails
                    (0, tree_kill_1.default)(pid, 'SIGKILL', (killErr) => {
                        if (killErr) {
                            this.logger.error(`Failed to kill process tree with SIGKILL: ${killErr.message}`);
                        }
                        else {
                            this.logger.debug('Process tree killed with SIGKILL');
                        }
                        resolve();
                    });
                }
                else {
                    this.logger.debug('Process tree killed with SIGTERM');
                    resolve();
                }
            });
        });
    }
    async kill() {
        this.logger.debug('Kill command is called');
        // Set killed flag to prevent new operations
        this.killed = true;
        // Close all browser sessions
        await this.closeAllBrowserSessions();
        // If using 'local' mode, stop all active sessions
        if (this.config.workerLimit === 'local') {
            await this.stopAllSessions();
        }
        if (this.localSelenium) {
            // Check if already killed
            if (this.localSelenium.killed) {
                this.logger.debug('Selenium process already killed');
                return;
            }
            const pid = this.localSelenium.pid;
            if (!pid) {
                this.logger.warn('Selenium process has no PID, cannot kill process tree');
                return;
            }
            this.cleanupSeleniumProcess();
            this.logger.debug(`Stopping local Selenium server and all child processes (PID: ${pid})`);
            // Use tree-kill to properly terminate the process tree
            await this.killSeleniumProcessTree(pid);
            this.localSelenium.removeAllListeners();
            this.logger.debug('Selenium process tree and all associated pipes closed.');
        }
    }
    async refresh(applicant) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        return client.refresh();
    }
    async click(applicant, selector, options) {
        const element = await this.getElement(applicant, selector);
        return options && Object.keys(options).length > 0
            ? element.click(options)
            : element.click();
    }
    async getSize(applicant, selector) {
        const element = await this.getElement(applicant, selector);
        return element.getSize();
    }
    async url(applicant, val) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        if (!val) {
            return client.getUrl();
        }
        return client.url(val);
    }
    generateWinId() {
        this.incrementWinId++;
        return `window-${this.incrementWinId}`;
    }
    async newWindow(applicant, val, windowName, windowFeatures = {}) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        const args = stringifyWindowFeatures(windowFeatures);
        const newWindow = await client.newWindow(val, {
            windowName: windowName || this.generateWinId(),
            windowFeatures: args,
        });
        return (newWindow === null || newWindow === void 0 ? void 0 : newWindow.handle) || newWindow;
    }
    async waitForExist(applicant, selector, timeout) {
        const element = await this.getElement(applicant, selector);
        return element.waitForExist({ timeout });
    }
    async waitForVisible(applicant, selector, timeout) {
        const element = await this.getElement(applicant, selector);
        return element.waitForDisplayed({ timeout });
    }
    async isVisible(applicant, selector) {
        const element = await this.getElement(applicant, selector);
        return element.isDisplayed();
    }
    async moveToObject(applicant, selector, xOffset = 0, yOffset = 0) {
        const element = await this.getElement(applicant, selector);
        return element.moveTo({ xOffset, yOffset });
    }
    async execute(applicant, fn, args) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        return client.execute(fn, ...args);
    }
    async executeAsync(applicant, fn, args) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        return client.executeAsync(fn, ...args);
    }
    async getTitle(applicant) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        return client.getTitle();
    }
    async clearValue(applicant, selector) {
        const element = await this.getElement(applicant, selector);
        return element.clearValue();
    }
    async keys(applicant, value) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        return client.keys(value);
    }
    async elementIdText(applicant, elementId) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        return client.getElementText(elementId);
    }
    async elements(applicant, selector) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        if ((0, types_1.isXpathSelector)(selector)) {
            const elements = (await client.findElements('xpath', selector.xpath));
            return elements.map((o) => {
                const keys = Object.keys(o);
                const firstKey = keys[0];
                if (firstKey === undefined) {
                    return { ELEMENT: '' };
                }
                return { ELEMENT: o[firstKey] };
            });
        }
        else if ((0, types_1.isShadowCssSelector)(selector)) {
            const lastParentElement = await this.traverseToLastParentSelector(client, selector.parentSelectors);
            const elements = lastParentElement.shadow$$(selector.css);
            return elements.map((element) => {
                return { ELEMENT: element.elementId };
            });
        }
        throw new Error('Unknown selector type');
    }
    async frame(applicant, frameID) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        return client.switchFrame(frameID);
    }
    async frameParent(applicant) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        return client.switchToParentFrame();
    }
    async getValue(applicant, selector) {
        const element = await this.getElement(applicant, selector);
        return element.getValue();
    }
    async setValue(applicant, selector, value) {
        const element = await this.getElement(applicant, selector);
        return element.setValue(value);
    }
    async selectByIndex(applicant, selector, value) {
        const element = await this.getElement(applicant, selector);
        return element.selectByIndex(value);
    }
    async selectByValue(applicant, selector, value) {
        const element = await this.getElement(applicant, selector);
        return element.selectByAttribute('value', value);
    }
    async selectByVisibleText(applicant, selector, str) {
        const element = await this.getElement(applicant, selector);
        return element.selectByVisibleText(str);
    }
    async getAttribute(applicant, selector, attr) {
        const element = await this.getElement(applicant, selector);
        return element.getAttribute(attr);
    }
    async windowHandleMaximize(applicant) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        return client.maximizeWindow();
    }
    async isEnabled(applicant, selector) {
        const element = await this.getElement(applicant, selector);
        return element.isEnabled();
    }
    async scroll(applicant, selector, xOffset, yOffset) {
        const element = await this.getElement(applicant, selector);
        await element.scrollIntoView();
        return element.moveTo({ xOffset, yOffset });
    }
    async scrollIntoView(applicant, selector, scrollIntoViewOptions) {
        const element = await this.getElement(applicant, selector);
        await element.scrollIntoView(scrollIntoViewOptions !== null ? scrollIntoViewOptions : undefined);
    }
    async isAlertOpen(applicant) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        return client.isAlertOpen();
    }
    async alertAccept(applicant) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        if (await this.isAlertOpen(applicant)) {
            return client.acceptAlert();
        }
        throw Error('There is no open alert');
    }
    async alertDismiss(applicant) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        if (await this.isAlertOpen(applicant)) {
            return client.dismissAlert();
        }
        throw Error('There is no open alert');
    }
    async alertText(applicant) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        if (await this.isAlertOpen(applicant)) {
            return client.getAlertText();
        }
        throw Error('There is no open alert');
    }
    async dragAndDrop(applicant, sourceSelector, destinationSelector) {
        const sourceElement = await this.getElement(applicant, sourceSelector);
        const destinationElement = await this.getElement(applicant, destinationSelector);
        return sourceElement.dragAndDrop(destinationElement);
    }
    async setCookie(applicant, cookieObj) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        return await client.setCookies(cookieObj);
    }
    async getCookie(applicant, cookieName) {
        var _a;
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        if (cookieName) {
            try {
                const cookies = await client.getCookies([cookieName]);
                return (_a = cookies[0]) === null || _a === void 0 ? void 0 : _a.value;
            }
            catch (e) {
                return undefined;
            }
        }
        return client.getAllCookies();
    }
    async deleteCookie(applicant, cookieName) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        if (cookieName) {
            return client.deleteCookie(cookieName);
        }
        return client.deleteAllCookies();
    }
    async getHTML(applicant, selector, b) {
        const element = await this.getElement(applicant, selector);
        return element.getHTML(b);
    }
    async getCurrentTabId(applicant) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        return client.getWindowHandle();
    }
    async getTabIds(applicant) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        return client.getWindowHandles();
    }
    // @deprecated
    async windowHandles(applicant) {
        return this.getTabIds(applicant);
    }
    async window(applicant, tabId) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        return client.switchToWindow(tabId);
    }
    async switchTab(applicant, tabId) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        const result = await client.switchToWindow(tabId);
        const body = await client.$('body');
        await client.waitUntil(async () => body.isExisting(), { timeout: 10000 });
        return result;
    }
    async close(applicant, tabId) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        const tabs = await this.getTabIds(applicant);
        if (tabs.length === 1 && tabs[0] === tabId) {
            return this.end(applicant);
        }
        await client.switchToWindow(tabId);
        return client.closeWindow();
    }
    async getTagName(applicant, selector) {
        const element = await this.getElement(applicant, selector);
        return element.getTagName();
    }
    async isSelected(applicant, selector) {
        const element = await this.getElement(applicant, selector);
        return element.isSelected();
    }
    async getText(applicant, selector) {
        const element = await this.getElement(applicant, selector);
        return element.getText();
    }
    async elementIdSelected(applicant, id) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        return client.isElementSelected(id);
    }
    async makeScreenshot(applicant) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        return client.takeScreenshot();
    }
    async makeElementScreenshot(applicant, selector, scroll = true) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        const element = await this.getElement(applicant, selector);
        return client.takeElementScreenshot(await element.elementId, scroll);
    }
    async uploadFile(applicant, filePath) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        return client.uploadFile(filePath);
    }
    async getCssProperty(applicant, selector, cssProperty) {
        const element = await this.getElement(applicant, selector);
        const property = await element.getCSSProperty(cssProperty);
        return property.value;
    }
    async getSource(applicant) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        return client.getPageSource();
    }
    async isExisting(applicant, selector) {
        const element = await this.getElement(applicant, selector);
        return element.isExisting();
    }
    async waitForValue(applicant, selector, timeout, reverse) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        return client.waitUntil(async () => {
            const element = await this.getElement(applicant, selector);
            const elemValue = await element.getValue();
            return reverse ? !elemValue : !!elemValue;
        }, { timeout });
    }
    async waitForSelected(applicant, selector, timeout, reverse) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        return client.waitUntil(async () => {
            const element = await this.getElement(applicant, selector);
            const isSelected = await element.isSelected();
            return reverse ? !isSelected : isSelected;
        }, { timeout });
    }
    async waitUntil(applicant, condition, timeout, timeoutMsg, interval) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        const options = {
            timeout: timeout || 5000,
        };
        if (timeoutMsg !== undefined) {
            options.timeoutMsg = timeoutMsg;
        }
        if (interval !== undefined) {
            options.interval = interval;
        }
        return client.waitUntil(condition, options);
    }
    async selectByAttribute(applicant, selector, attribute, value) {
        const element = await this.getElement(applicant, selector);
        return element.selectByAttribute(attribute, value);
    }
    async gridTestSession(applicant) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        if (this.localSelenium) {
            return {
                sessionId: client.sessionId,
                host: this.config.host,
                port: this.config.port,
                localSelenium: true,
            };
        }
        return client.gridTestSession(client.sessionId);
    }
    async getHubConfig(applicant) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        if (this.localSelenium) {
            return {
                sessionId: client.sessionId,
                host: this.config.host,
                port: this.config.port,
                localSelenium: true,
            };
        }
        return client.getHubConfig();
    }
    /**
     * @param overwrites should NOT be an arrow function, Otherwise it would throw an error
     */
    async mock(applicant, url, overwrites, filterOptions, mockResponseParams) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        const mock = await client.mock(url, filterOptions);
        mock.respond(overwrites, mockResponseParams);
    }
    async emulateDevice(applicant, deviceName) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        await client.deleteSession();
        this.browserClients.delete(applicant);
        this.customBrowserClientsConfigs.delete(applicant);
        await this.createClient(applicant, {
            capabilities: {
                'goog:chromeOptions': {
                    mobileEmulation: {
                        deviceName,
                    },
                },
            },
        });
    }
    async status(applicant) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        return client.status();
    }
    async back(applicant) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        return client.back();
    }
    async forward(applicant) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        return client.forward();
    }
    async getActiveElement(applicant) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        return client.getActiveElement();
    }
    async getLocation(applicant, selector) {
        const element = await this.getElement(applicant, selector);
        return element.getLocation();
    }
    async setTimeZone(applicant, timeZone) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        return client.setTimeZone(timeZone);
    }
    async getWindowSize(applicant) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        return client.getWindowSize();
    }
    async savePDF(applicant, options) {
        await this.createClient(applicant);
        const client = this.getBrowserClient(applicant);
        const { filepath, ...restOptions } = options;
        return client.savePDF(filepath, restOptions);
    }
    async addValue(applicant, selector, value) {
        const element = await this.getElement(applicant, selector);
        return element.addValue(value);
    }
    async doubleClick(applicant, selector) {
        const element = await this.getElement(applicant, selector);
        return element.doubleClick();
    }
    async isClickable(applicant, selector) {
        const element = await this.getElement(applicant, selector);
        return element.isClickable();
    }
    async waitForClickable(applicant, selector, timeout) {
        const element = await this.getElement(applicant, selector);
        return element.waitForClickable({ timeout });
    }
    async isFocused(applicant, selector) {
        const element = await this.getElement(applicant, selector);
        return element.isFocused();
    }
    async isStable(applicant, selector) {
        const element = await this.getElement(applicant, selector);
        return element.isStable();
    }
    async waitForEnabled(applicant, selector, timeout) {
        const element = await this.getElement(applicant, selector);
        return element.waitForEnabled({ timeout });
    }
    async waitForStable(applicant, selector, timeout) {
        const element = await this.getElement(applicant, selector);
        return element.waitForStable({ timeout });
    }
}
exports.SeleniumPlugin = SeleniumPlugin;
function seleniumProxy(config) {
    return new SeleniumPlugin(config);
}
//# sourceMappingURL=index.js.map