"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.OjWebElement = void 0;
exports.ojWebElement = ojWebElement;
const selenium_webdriver_1 = require("selenium-webdriver");
const __1 = require("../");
const stale_element_1 = require("../lib/stale-element");
const busy_context_utils_1 = require("../lib/busy-context-utils");
const oj_module_proxy_1 = require("../lib/oj-module-proxy");
/**
 * The base WebElement class from which all cusom WebElements should extend.
 * This class can also be used (instantiated) directly if no component-specific
 * WebElement exists for a custom component.
 * All WebElement commands which call remote operations on the browser are
 * overidden to first:
 * <ul>
 *   <li>
 *     Capture screenshots prior to command execution. This aids in debugging if
 *     the command should fail.
 *   </li>
 *   <li>
 *     Wait for the page-level BusyContext to clear. This ensures any asynchronous
 *     activity in the application is complete before the test continues. Note
 *     that commands which interact with the DOM (such as click, sendKeys, etc)
 *     will also wait for the element to be present and have a size in the DOM
 *     (whenReady), while other "read" methods (getAttribute, isEnabled, etc) only
 *     wait for the BusyContext to clear (whenBusyContextReady).
 *   </li>
 *   <li>
 *     Trap StaleElementException and retry. This automatic trapping and retrying
 *     for StaleElementExceptions allow tests to be more resilient against UI
 *     activities which may move/recreate DOM nodes, typically resulting in failed
 *     old references from WebElements to DOM nodes. If a StaleElementException is
 *     encountered for any remote command, OjWebElement will requery the DOM node
 *     and try the command again. <i>This is implemented for all remote commands
 *     except <code>getTagName</code> because, interally, WebDriver uses that
 *     command in its <code>until.stalenessOf</code> test condition.</i>
 *   </li>
 * </ul>
 */
class OjWebElement extends selenium_webdriver_1.WebElement {
    /**
     * Constructor.  Pass an instance of WebDriver's WebElement that
     * represents the DOM node on which we"ll perform operations.
     * @param el The basic WebElement with which this WebElement will
     * work.
     */
    constructor(el, locators) {
        super(el.getDriver(), el.getId());
        /**
         * Used by whenReady to avoid whenBusyContextReady being repeatedly called
         */
        this._skipBusyContext = false;
        this._locators = locators;
    }
    /**
     * @inheritdoc
     */
    async clear() {
        //get the active screenshotManager instance to take and save screenshot before action
        const screenshotManager = __1.ScreenshotManager.get();
        await screenshotManager.takeAndSaveScreenShot(this.getDriver(), 'before_clear');
        await this.whenReady();
        await OjWebElement.trapStaleElement(this, super.clear, undefined);
        // JET-41358
        // When input components embedded within oj-table, they can lose focus
        // after clear().
        return this.getDriver().executeScript((el) => el.focus(), this);
    }
    /**
     * @inheritdoc
     */
    async click() {
        //get the active screenshotManager instance to take and save screenshot before action
        const screenshotManager = __1.ScreenshotManager.get();
        await screenshotManager.takeAndSaveScreenShot(this.getDriver(), 'before_click');
        await this.whenReady();
        await this.getDriver().executeScript((el) => el.focus(), this);
        return OjWebElement.trapStaleElement(this, super.click, undefined);
    }
    /**
     * @inheritdoc
     */
    async getAttribute(attributeName) {
        await this.whenBusyContextReady();
        return OjWebElement.trapStaleElement(this, super.getAttribute, attributeName);
    }
    /**
     * @inheritdoc
     */
    async getCssValue(cssStyleProperty) {
        //get the active screenshotManager instance to take and save screenshot before action
        const screenshotManager = __1.ScreenshotManager.get();
        await screenshotManager.takeAndSaveScreenShot(this.getDriver(), 'before_getCssValue');
        await this.whenBusyContextReady();
        return OjWebElement.trapStaleElement(this, super.getCssValue, cssStyleProperty);
    }
    /**
     * @inheritdoc
     */
    async getLocation() {
        //get the active screenshotManager instance to take and save screenshot before action
        const screenshotManager = __1.ScreenshotManager.get();
        await screenshotManager.takeAndSaveScreenShot(this.getDriver(), 'before_getLocation');
        await this.whenBusyContextReady();
        return OjWebElement.trapStaleElement(this, super.getLocation, undefined);
    }
    /**
     * @inheritdoc
     */
    async getRect() {
        // @ts-ignore
        if (super.getRect) {
            // selenium-webdriver 4.x+
            //get the active screenshotManager instance to take and save screenshot before action
            const screenshotManager = __1.ScreenshotManager.get();
            await screenshotManager.takeAndSaveScreenShot(this.getDriver(), 'before_getRect');
            await this.whenBusyContextReady();
            return OjWebElement.trapStaleElement(this, super.getRect, undefined);
        }
        throw new selenium_webdriver_1.error.UnknownMethodError('WebElement.getRect() is not supported for this version of selenium-webdriver');
    }
    /**
     * @inheritdoc
     */
    async getSize() {
        // Deprecated in selenium-webdriver 4.x--needed for 3.x/4.x compat
        // @ts-ignore
        if (super.getSize) {
            //get the active screenshotManager instance to take and save screenshot before action
            const screenshotManager = __1.ScreenshotManager.get();
            await screenshotManager.takeAndSaveScreenShot(this.getDriver(), 'before_getSize');
            await this.whenBusyContextReady();
            return OjWebElement.trapStaleElement(this, super.getSize, undefined);
        }
        else {
            return this.getRect();
        }
    }
    /**
     * @inheritdoc
     */
    async getText() {
        //get the active screenshotManager instance to take and save screenshot before action
        const screenshotManager = __1.ScreenshotManager.get();
        await screenshotManager.takeAndSaveScreenShot(this.getDriver(), 'before_getText');
        await this.whenBusyContextReady();
        return OjWebElement.trapStaleElement(this, super.getText, undefined);
    }
    /**
     * @inheritdoc
     */
    async isDisplayed() {
        await this.whenBusyContextReady();
        return OjWebElement.trapStaleElement(this, super.isDisplayed, undefined);
    }
    /**
     * @inheritdoc
     */
    async isEnabled() {
        //get the active screenshotManager instance to take and save screenshot before action
        const screenshotManager = __1.ScreenshotManager.get();
        await screenshotManager.takeAndSaveScreenShot(this.getDriver(), 'before_isEnabled');
        await this.whenBusyContextReady();
        // See if component has a "disabled" property to test if it's disabled.
        let disabled = await this.getProperty('disabled');
        if (typeof disabled === 'boolean') {
            return !disabled;
        }
        // If component doesn't have a "disabled" property, defer to WebDriver to
        // test if the element is disabled.
        return await OjWebElement.trapStaleElement(this, super.isEnabled, undefined);
    }
    /**
     * @inheritdoc
     */
    async isSelected() {
        //get the active screenshotManager instance to take and save screenshot before action
        const screenshotManager = __1.ScreenshotManager.get();
        await screenshotManager.takeAndSaveScreenShot(this.getDriver(), 'before_isSelected');
        await this.whenBusyContextReady();
        return OjWebElement.trapStaleElement(this, super.isSelected, undefined);
    }
    /**
     * @inheritdoc
     */
    async sendKeys(...varArgs) {
        //get the active screenshotManager instance to take and save screenshot before action
        const screenshotManager = __1.ScreenshotManager.get();
        await screenshotManager.takeAndSaveScreenShot(this.getDriver(), 'before_sendKeys');
        await this.whenReady();
        return OjWebElement.trapStaleElement(this, super.sendKeys, ...varArgs);
    }
    /**
     * @inheritdoc
     */
    async submit() {
        //get the active screenshotManager instance to take and save screenshot before action
        const screenshotManager = __1.ScreenshotManager.get();
        await screenshotManager.takeAndSaveScreenShot(this.getDriver(), 'before_submit');
        await this.whenReady();
        return OjWebElement.trapStaleElement(this, super.submit, undefined);
    }
    /**
     * @inheritdoc
     */
    findElement(locator) {
        // Note that this function isn't declared async because the superclass
        // signature returns a WebElementPromise, but TS requires that all
        // async functions return Promise<T>.
        // Instead, we get the Promise from calling the superclass method, then return
        // that in a new WebElementPromise
        const p = this.whenReady().then(() => OjWebElement.trapStaleElement(this, super.findElement, locator));
        return new selenium_webdriver_1.WebElementPromise(this.getDriver(), p);
    }
    /**
     * @inheritdoc
     */
    async findElements(locator) {
        //get the active screenshotManager instance to take and save screenshot before action
        const screenshotManager = __1.ScreenshotManager.get();
        await screenshotManager.takeAndSaveScreenShot(this.getDriver(), 'before_findElements');
        await this.whenReady();
        const el = OjWebElement.trapStaleElement(this, super.findElements, locator);
        return el;
    }
    /**
     * @inheritdoc
     */
    async takeScreenshot(optScroll) {
        const ss = OjWebElement.trapStaleElement(this, super.takeScreenshot, optScroll || false);
        return ss;
    }
    /**
     * Returns a Promise that is resolved when the element is ready
     * for interaction--it is visible, has a size, and the app's
     * busy context is ready. Operations on the DOM which rely on its physical
     * location and size to be in a "ready" state should use this command, such as
     * clicking, sending keys, locating child elements, and so on.
     * @return A Promise that's resolved when all conditions
     * are met.
     */
    async whenReady() {
        const driver = this.getDriver();
        const timeout = 15000;
        const tag = `<${this._locators.tagName}>`;
        await this.whenBusyContextReady();
        // Tell whenBusyContextReady to skip its check for these methods, which will
        // continually be called by driver.wait()
        this._skipBusyContext = true;
        try {
            await driver.wait(() => this.isDisplayed(), timeout, `waiting for ${tag} to be visible`);
            return driver.wait(async () => {
                const size = await this.getSize();
                return size.height > 0 && size.width > 0;
            }, timeout, `waiting for ${tag} to have non-zero size`);
        }
        finally {
            this._skipBusyContext = false;
        }
    }
    /**
     * Returns a Promise that is resolved when the application's busy
     * state reports ready. This method checks only that the application's busy
     * context is ready, and not that the DOM element is actually displayed or has
     * a physical size, such as is the case with {@link #whenReady}. Operations
     * which are read-only in nature should call this method prior to execution.
     * @return A Promise which is resolved when the
     * page's busy state is ready.
     */
    async whenBusyContextReady() {
        if (!this._skipBusyContext) {
            await (0, busy_context_utils_1.whenBusyContextReady)(this.getDriver());
        }
    }
    /**
     * Get a named property value from this component.  This function
     * will wait for the application's busy context to complete before
     * attempting to get the property value.
     * @param {string} propertyName The name of the property
     * @return A Promise that yields a value for the named property.
     */
    async getProperty(propertyName) {
        await this.whenBusyContextReady();
        return OjWebElement.trapStaleElement(this, this._getProperty, propertyName);
    }
    /**
     * Set a property value on the remote element.  This function first waits for
     * the BusyContext to clear before setting the property, then after setting,
     * waits again to ensure that no additional busy states were created due to
     * the property setting.
     * @param {string} propertyName The property name to set
     * @param {T} value The value to set for the property
     * @return A Promise which resolves when the remote
     * property has been set to the value.  Any value returned from
     * the call to set the remote property is returned in the Promise.
     */
    async setProperty(propertyName, value) {
        await this.whenReady();
        await OjWebElement.trapStaleElement(this, this._setProperty, propertyName, value);
    }
    /**
     * Create a wait condition that is satisfied when the element's
     * DOM has a non-zero size.
     * @return The Condition to be used with driver.wait()
     * that's satisfied when the element's DOM has a non-zero size.
     * @deprecated Since 11.0.0. Call the appropriate WebElement method to test
     * the size of the element.
     */
    hasSize() {
        return new selenium_webdriver_1.Condition(`<${this._locators.tagName}> has a non-zero size`, async () => {
            const rect = await this.getSize();
            return rect.height > 0 && rect.width > 0;
        });
    }
    /**
     * Create a wait condition that is satisfid when the element is
     * visible.  The condition is satisfied when the element is displayed,
     * as reported by {@link #isDisplayed}, and is scrolled into view.
     * This is method is deprecated. Use {@link WebElement.isDisplayed} instead to
     * test the display of the element, and scroll it into view if necessary.
     * @deprecated
     */
    hasVisibility() {
        return new selenium_webdriver_1.Condition(`<${this._locators.tagName}> is displayed and scrolled into view`, async (driver) => {
            const displayed = await this.isDisplayed();
            if (displayed) {
                // Scroll the DOM into view and return true to fulfill
                // the Condition
                await driver.executeScript((el) => el.scrollIntoView(), this);
                return true;
            }
            return displayed;
        });
    }
    /**
     * Private accessor to get the component property value. This is for
     * trapStaleElement to call and handle the stale element exception when calling
     * getProperty.
     * @param propertyName The name of the property
     * @return A Promise that yields a value for the named property.
     * @typeparam T The value type of the property
     */
    async _getProperty(propertyName) {
        return (0, oj_module_proxy_1.executeWithModules)(this.getDriver(), ['CustomElementUtils'], [this, propertyName], ({ getElementProperty }, el, prop) => getElementProperty(el, prop));
    }
    /**
     * Private mutator to set the component property value. This is trapStaleElement
     * to call and handle the stale element exception when calling setProperty.
     * @param propertyName The name of the property
     * @param value The value to set for the property
     * @typeparam T The value type of the property
     */
    async _setProperty(propertyName, value) {
        await this.getDriver().executeScript(
        // Call setProperty on the component if it exists, otherwise, set the
        // property value directly.
        (el, propertyName, value) => {
            return el.setProperty ? el.setProperty(propertyName, value) : (el[propertyName] = value);
        }, this, propertyName, value);
    }
    /**
     * Trap commands against OjWebElement and look for stale element exceptions. If
     * found, re-query the element and try the command again.
     * @private
     * @typeparam T The type of value returned from the command.
     * @typeparam P The type of parameters passed to the command.
     * @param el The OjWebElement instance on which the command will work
     * @param cmd The command on the OjWebElement to execute
     * @param cmdParams Any parameters to pass to the command
     * @return A Promise with the results of the command, if any.
     */
    static async trapStaleElement(el, cmd, ...cmdParams) {
        let result;
        try {
            result = await cmd.apply(el, cmdParams);
        }
        catch (ex) {
            // Only requery if elLocator exists on the WebElement. This will be present
            // most of the time, except for when an element is returned through
            // driver.wait(until.elementLocated). That method doesn't allow access to
            // the original locator.
            if (ex instanceof selenium_webdriver_1.error.StaleElementReferenceError && el._locators.elLocator) {
                console.warn(`Trapped stale element calling '${cmd.name}' on ${el._locators.elLocator}`);
                const newEl = await (0, stale_element_1.requeryElement)(el);
                result = await this.trapStaleElement(newEl, cmd, ...cmdParams);
            }
            else {
                throw ex;
            }
        }
        return result;
    }
}
exports.OjWebElement = OjWebElement;
/**
 * Create an instance of [[OjWebElement]].  This function will first wait for
 * the element to appear in the DOM before trying to locate it.
 * @param driver A WebDriver/WebElement instance used to locate the component
 * element.
 * @param by The By locator with which to find the element
 */
async function ojWebElement(driver, by) {
    // in a JET app, driver.findElement will always return Promise<OjWebElement>
    return driver.findElement(by);
}
//# sourceMappingURL=OjWebElement.js.map