"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.register = register;
// @ts-nocheck
const selenium_webdriver_1 = require("selenium-webdriver");
const WebDriverErrors = require("selenium-webdriver/lib/error");
// Import OjWebElement directly from its file rather than through
// 'elements' package, to avoid circular dependency (elements will
// call to register themselves upon loading)
const OjWebElement_1 = require("../elements/OjWebElement");
const stale_element_1 = require("./stale-element");
const busy_context_utils_1 = require("./busy-context-utils");
/**
 * Register a constructor for a custom WebElement for a given tag name.
 * @param tagName The DOM tag name for the custom element
 * @param ctor The constructor function for the custom WebElement
 */
function register(tagName, ctor) {
    WebElementRegistry.register(tagName, ctor);
}
/**
 * Holds the value of the last page-type check. Updated each time when
 * WebDriver.findElement / findElements is called because any subsequent action
 * could've triggered a page change.
 * WebElement.findElement / findElements uses this var to avoid checking the
 * page type again because those methods can only be run on an element on the
 * existing page.
 * @private
 */
let lastIsJetPage = false;
/**
 * A registry which associates the constructors for custom WebElements with their
 * associated tag names. This registry should only be used internally by custom
 * WebElement implementations, and shouldn't be used directly in tests.
 * @private
 */
class WebElementRegistry {
    /**
     * Register a constructor for a custom WebElement for a given tag name.
     * @param tagName The DOM tag name for the custom element
     * @param ctor The constructor function for the custom WebElement
     */
    static register(tagName, ctor) {
        WebElementRegistry.tagToCtorMap[tagName.toLowerCase()] = ctor;
    }
    /**
     * Get the constructor for a custom WebElement given a tag name.
     * @param tagName The DOM tag name for the custom element
     * @return A custom WebElement constructor for the given tagName, or undefined
     * if none exists.
     */
    static get(tagName) {
        return WebElementRegistry.tagToCtorMap[tagName.toLowerCase()];
    }
}
/**
 * A mapping of element tag names to WebElement constructors
 */
WebElementRegistry.tagToCtorMap = {};
selenium_webdriver_1.WebDriver.prototype.findElement = wrapFindElement(selenium_webdriver_1.WebDriver.prototype.findElement);
selenium_webdriver_1.WebDriver.prototype.findElements = wrapFindElements(selenium_webdriver_1.WebDriver.prototype.findElements);
selenium_webdriver_1.WebElement.prototype.findElement = wrapFindElement(selenium_webdriver_1.WebElement.prototype.findElement);
selenium_webdriver_1.WebElement.prototype.findElements = wrapFindElements(selenium_webdriver_1.WebElement.prototype.findElements);
selenium_webdriver_1.WebDriver.prototype.executeScript = wrapExecuteScript(selenium_webdriver_1.WebDriver.prototype.executeScript);
selenium_webdriver_1.WebDriver.prototype.executeAsyncScript = wrapExecuteScript(selenium_webdriver_1.WebDriver.prototype.executeAsyncScript);
/**
 * Get a mapping of WebDriver error class names to error class instances
 * @returns { errorClassName: errorClassInstance, ... }
 */
function getWebDriverErrors() {
    return Object.keys(WebDriverErrors)
        .filter((c) => c.match(/^[A-Z].+Error$/))
        .reduce((accum, className) => {
        accum[className] = new WebDriverErrors[className]();
        return accum;
    }, {});
}
/**
 * Create a wrapper for findElement()
 * @param origFunc The original findElement function
 * @private
 */
function wrapFindElement(origFunc) {
    return function findElement(locator) {
        const isDriver = this instanceof selenium_webdriver_1.WebDriver;
        const driver = isDriver ? this : this.getDriver();
        return new selenium_webdriver_1.WebElementPromise(driver, new Promise(async (resolve, reject) => {
            // create an error before calling async webdriver command to retain original stack from test
            const webdriverErrors = getWebDriverErrors();
            try {
                // Only inspect page when called by driver object (not from WebElement)
                lastIsJetPage = isDriver ? await isJetPage(this) : lastIsJetPage;
                const el = await origFunc.call(this, locator);
                const parentEl = !isDriver ? this : undefined;
                try {
                    resolve(lastIsJetPage ? await createTestAdapter(el, locator, parentEl) : el);
                }
                catch (ex) {
                    // If exception appears while creating test adapter (an element probably disappeared)
                    // we throw away found element and start over
                    resolve(findElement.call(this, locator));
                }
            }
            catch (ex) {
                const errorWithStack = webdriverErrors[ex.name] || ex;
                errorWithStack.name = ex.name;
                errorWithStack.message = ex.message;
                reject(errorWithStack);
            }
        }));
    };
}
/**
 * Create a wrapper for findElements()
 * @param origFunc The original findElements function
 * @private
 */
function wrapFindElements(origFunc) {
    return async function findElements(locator) {
        const isDriver = this instanceof selenium_webdriver_1.WebDriver;
        // Only inspect page when called by driver object (not from WebElement)
        lastIsJetPage = isDriver ? await isJetPage(this) : lastIsJetPage;
        // Pass ourselves as parent if this is WebElement.findElements
        const elRef = !isDriver ? this : undefined;
        const matches = await origFunc.call(this, locator);
        let weMatches = [];
        if (lastIsJetPage) {
            for (let el, i = 0, len = matches.length; i < len; i++) {
                el = matches[i];
                try {
                    const ce = await createTestAdapter(el, locator, elRef, i);
                    weMatches.push(ce);
                }
                catch (ex) {
                    // If exception appears while creating test adapter (an element probably disappeared)
                    // we throw away found elements and start over
                    return findElements.call(this, locator);
                }
            }
        }
        else {
            weMatches = matches;
        }
        return weMatches;
    };
}
/**
 * Wrap WebDriver.executeScript/executeAsyncScript with a function that traps
 * StaleElementReferenceError and requeries the element(s) to try again
 * @param origFunc The original driver.executeScript/executeAsyncScript
 * @private
 */
function wrapExecuteScript(origFunc) {
    return async function executeScript(...args) {
        try {
            const ret = await origFunc.apply(this, args);
            return ret;
        }
        catch (ex) {
            if (ex instanceof selenium_webdriver_1.error.StaleElementReferenceError) {
                const newArgs = [];
                const locators = [];
                for (let arg of args) {
                    // Requery arguments which are OjWebElements
                    if (arg instanceof OjWebElement_1.OjWebElement) {
                        arg = await (0, stale_element_1.requeryElement)(arg);
                        locators.push(arg._locators?.elLocator);
                    }
                    newArgs.push(arg);
                }
                console.warn(`Trapped stale element executing script on ${locators}`);
                return executeScript.apply(this, newArgs);
            }
            throw ex;
        }
    };
}
/**
 * Test if the page is a JET page. This should be called each time
 * WebDriver findElement / findElements is called because any previous action
 * could've triggered a page change. It's not necessary to call for
 * WebElement findElement / findElements because that method can only be called
 * on an existing element reference within the same page.
 *
 * @param driver WebDriver instance
 * @returns {Promise<boolean>} Returns true if JET page, false otherwise
 * @private
 */
async function isJetPage(driver) {
    return (0, busy_context_utils_1.whenBusyContextReady)(driver);
}
/**
 * Create a custom WebElement from the original one. The WebElement constructor
 * is looked up in WebElementRegistry using the element's tag name, and the
 * registry is populated by each custom element. If no constructor is found,
 * OjWebElement is used.
 * @param el The original WebElement
 * @param elLocator The By locator used to originally locate the element
 * @param parentEl The immediate parent from which we were queried (WebElement.findElement)
 * @param elementsIndex The index within the array of WebElements returned from
 * findElements.
 * @return {OjWebElement} The test adapter for the given element, or OjWebElement
 * if none exists
 * @private
 */
async function createTestAdapter(el, elLocator, parentEl, elementsIndex) {
    // This call can throw an exception which is caught in calling function
    const tagName = await el.getTagName();
    const ctor = WebElementRegistry.get(tagName) || OjWebElement_1.OjWebElement;
    return new ctor(el, {
        elLocator,
        elementsIndex,
        parentEl,
        tagName
    });
}
//# sourceMappingURL=driver-override.js.map