Source: pom/element.js

"use strict";
/**
 * Creates a new instance of `Element`.
 *
 * `Element` binds DOM element in browser context with virtual element in test.
 *
 * @class
 * @name Element
 * @arg {string} name - Element name.
 * @arg {object} selector - CSS selector of DOM element.
 * @arg {Page} page - Page with element.
 */

const _ = require("lodash");
var weak = require("weak");
var U = require("glace-utils");

var CONF = require("../config");
var PointerEvents = require("./event");
const utils = require("../utils");

var Element = function (name, selector, page) {

    this.name = name;
    this.selector = selector;
    this._event = new PointerEvents(selector, page);

    this._page = weak(page);
    this._elCmd = utils.getElementCommand(selector);
    this._elsCmd = utils.getElementsCommand(selector);
    this._webdriver = null;
};
/**
 * Helper to get webdriver.
 *
 * @method
 * @return {object} webdriver instance
 */
Element.prototype._getDriver = function () {
    if (!this._webdriver) {
        this._webdriver = this._page.getDriver();
    };
    return this._webdriver;
};
/**
 * Gets webdriver element.
 *
 * @async
 * @method
 * @return {Promise<object>} Webdriver element.
 */
Element.prototype.getElement = function () {
    return this._getDriver().element(this.selector);
};
/**
 * Gets webdriver elements.
 *
 * @async
 * @method
 * @return {Promise<array<object>>} Webdriver elements.
 */
Element.prototype.getElements = function () {
    return this._getDriver().elements(this.selector);
};
/**
 * Gets count of elements in browser.
 *
 * @async
 * @method
 * @return {Promise<integer>} Count of elements
 */
Element.prototype.getCount = async function () {
    const cmd = `return ${this._elsCmd}.length`;
    return this._getDriver().execute(cmd).then(result => result.value);
};
/**
 * Gets text content of DOM element.
 *
 * @async
 * @method
 * @arg {object} [opts] - Options.
 * @arg {boolean} [opts.now=false] - Make it immediately.
 * @arg {?number} [opts.timeout] - Time to wait for element visibility, sec.
 * @return {Promise<string>} Text value or null.
 */
Element.prototype.getText = async function (opts) {

    opts = U.defVal(opts, {});
    var now = U.defVal(opts.now, false);

    if (!now) {
        await this.waitForVisible(opts.timeout);
    };

    var value = await this._getDriver().getText(this.selector);
    if (!_.isEmpty(value)) return utils.clearElementText(value);

    value = await this._getDriver().getAttribute(this.selector, "value");
    if (!_.isEmpty(value)) return utils.clearElementText(value);

    value = await this._getDriver().getAttribute(this.selector, "innerHTML");
    if (!_.isEmpty(value)) return utils.clearElementText(value);

    return null;
};
/**
 * Sets text to DOM element.
 *
 * @async
 * @method
 * @arg {string} text - Text value to assign.
 * @arg {object} [opts] - Options.
 * @arg {boolean} [opts.now=false] - Make it immediately.
 * @arg {?number} [opts.timeout] - Time to wait for element visibility, sec.
 * @arg {boolean} [opts.scroll=true] - Scroll to element.
 * @arg {boolean} [opts.enter=false] - Send `Enter` after text.
 * @return {Promise}
 */
Element.prototype.setText = async function (text, opts) {

    opts = U.defVal(opts, {});
    var now = U.defVal(opts.now, false);
    var scroll = U.defVal(opts.scroll, true);
    const enter = U.defVal(opts.enter, false);

    if (scroll) {
        await this.scrollIntoView(opts);
    } else if (!now) {
        await this.waitForVisible(opts.timeout);
    };
    if (enter) text += "\n";

    await this._getDriver().setValue(this.selector, text);
};
/**
 * Gets DOM element location with attributes:
 *  `x`, `y`, `midX`, `midY`, `width`, `height`.
 *
 * @async
 * @method
 * @return {Promise<object>} Location of element.
 */
Element.prototype.location = function () {

    var cmd = ` \
        var ctrl = ${this._elCmd}; \
        var rect = ctrl.getBoundingClientRect(); \
        var location = {}; \
        location.x = Math.ceil(rect.left); \
        location.y = Math.ceil(rect.top); \
        location.width = Math.ceil(rect.width); \
        location.height = Math.ceil(rect.height); \
        location.midX = Math.ceil(location.x + location.width / 2); \
        location.midY = Math.ceil(location.y + location.height / 2); \
        return location;`;

    return this._getDriver().execute(cmd).then(result => result.value);
};
/**
 * Scrolls element into browser viewport.
 *
 * @async
 * @method
 * @arg {object} [opts] - Options.
 * @arg {boolean} [opts.now=false] - Make it immediately.
 * @arg {?number} [opts.timeout] - Time to wait for element visibility, sec.
 * @return {Promise}
 */
Element.prototype.scrollIntoView = async function (opts) {

    opts = U.defVal(opts, {});
    var now = U.defVal(opts.now, false);

    if (!now) {
        await this.waitForVisible(opts.timeout);
    };

    var cmd = "return " + this._elCmd + ".scrollIntoView();";
    await this._getDriver().execute(cmd);
};
/**
 * Clicks element in browser.
 *
 * @async
 * @method
 * @arg {object} [opts] - Options.
 * @arg {boolean} [opts.now=false] - Make it immediately.
 * @arg {?number} [opts.timeout] - Time to wait for element visibility, sec.
 * @arg {boolean} [opts.scroll=true] - Scroll to element.
 * @return {Promise}
 */
Element.prototype.click = async function (opts) {

    opts = U.defVal(opts, {});
    var now = U.defVal(opts.now, false);
    var scroll = U.defVal(opts.scroll, true);

    if (scroll) {
        await this.scrollIntoView(opts);
    } else if (!now) {
        await this.waitForVisible(opts.timeout);
    };
    await this._getDriver().click(this.selector);
};
/**
 * Clicks element in browser via pointer events.
 *
 * @async
 * @method
 * @arg {object} [opts] - Options.
 * @arg {boolean} [opts.now=false] - Make it immediately.
 * @arg {?number} [opts.timeout] - Time to wait for element visibility, sec.
 * @arg {boolean} [opts.scroll=true] - Scroll to element.
 * @return {Promise}
 */
Element.prototype.pClick = async function (opts) {

    opts = U.defVal(opts, {});
    var now = U.defVal(opts.now, false);
    var scroll = U.defVal(opts.scroll, true);

    if (scroll) {
        await this.scrollIntoView(opts);
    } else if (!now) {
        await this.waitForVisible(opts.timeout);
    };

    var loc = await this.location();
    await this._event.move(loc.midX, loc.midY);
    await this._event.down(loc.midX, loc.midY);
    await this._event.up(loc.midX, loc.midY);
};
/**
 * Taps element in browser.
 *
 * @async
 * @method
 * @arg {object} [opts] - Options.
 * @arg {boolean} [opts.now=false] - Make it immediately.
 * @arg {?number} [opts.timeout] - Time to wait for element visibility, sec.
 * @arg {boolean} [opts.scroll=true] - Scroll to element.
 * @return {Promise}
 */
Element.prototype.tap = Element.prototype.click;
/**
 * Defines whether element is selected or no.
 *
 * @async
 * @method
 * @arg {object} [opts] - Options.
 * @arg {boolean} [opts.now=false] - Make it immediately.
 * @arg {?number} [opts.timeout] - Time to wait for element visibility, sec.
 * @return {Promise<boolean>} `true` if element is selected, `false` otherwise.
 */
Element.prototype.isSelected = async function (opts) {

    opts = U.defVal(opts, {});
    var now = U.defVal(opts.now, false);

    if (!now) {
        await this.waitForVisible(opts.timeout);
    };

    var result = await this._getDriver().isSelected(this.selector);
    if (result) return true;

    result = await this._getDriver().getAttribute(this.selector, "class");
    return result.includes("selected");
};
/**
 * Defines whether element is exist or no.
 *
 * @async
 * @method
 * @return {Promise<boolean>} `true` if element is exist, `false` otherwise.
 */
Element.prototype.isExist = function () {
    var cmd = "return !!" + this._elCmd + ";";
    return this._getDriver().execute(cmd).then(result => result.value);
};
/**
 * Waits for element is exist.
 *
 * @async
 * @method
 * @arg {number} [timeout] - Timeout to wait, sec.
 * @return {Promise}
 * @throws {TimeoutError} If element doesn't exist after timeout.
 */
Element.prototype.waitForExist = function (timeout) {
    timeout = U.defVal(timeout, CONF.web.uiTimeout) * 1000;
    var errMsg = `${this.name} (${this.selector}) doesn't exist after ${timeout}s`;

    return this._getDriver().waitUntil(async () => {
        return await this.isExist();
    }, timeout, errMsg);
};
/**
 * Waits for element isn't exist.
 *
 * @async
 * @method
 * @arg {number} [timeout] - Timeout to wait, sec.
 * @return {Promise}
 * @throws {TimeoutError} If element is still exist after timeout.
 */
Element.prototype.waitForNonExist = function (timeout) {
    timeout = U.defVal(timeout, CONF.web.uiTimeout) * 1000;
    var errMsg = `${this.name} (${this.selector}) still exists after ${timeout}s`;

    return this._getDriver().waitUntil(async () => {
        return !(await this.isExist());
    }, timeout, errMsg);
};
/**
 * Defines whether element is visible or no.
 *
 * @method
 * @return {Promise<boolean>} `true` if element is visible, `false` otherwise.
 */
Element.prototype.isVisible = function () {
    return this.isExist().then(result => {
        if (!result) return false;
        return this._getDriver().isVisible(this.selector);
    });
};
/**
 * Waits for element is visible.
 *
 * @async
 * @method
 * @arg {number} [timeout] - Timeout to wait, sec.
 * @return {Promise}
 * @throws {TimeoutError} If element isn't visible after timeout.
 */
Element.prototype.waitForVisible = function (timeout) {
    timeout = U.defVal(timeout, CONF.web.uiTimeout) * 1000;
    var errMsg = `${this.name} (${this.selector}) isn't visible ` +
                 `after ${timeout}s`;

    return this._getDriver().waitUntil(async () => {
        return await this.isVisible();
    }, timeout, errMsg);
};
/**
 * Waits for element is invisible.
 *
 * @async
 * @method
 * @arg {number} [timeout] - Timeout to wait, sec.
 * @return {Promise}
 * @throws {TimeoutError} If element is still visible after timeout.
 */
Element.prototype.waitForInvisible = function (timeout) {
    timeout = U.defVal(timeout, CONF.web.uiTimeout) * 1000;
    var errMsg = `${this.name} (${this.selector}) is still visible after ${timeout}s`;

    return this._getDriver().waitUntil(async () => {
        return !(await this.isVisible());
    }, timeout, errMsg);
};
/**
 * Clones element with custom properties.
 *
 * @async
 * @method
 * @arg {object} [opts] - Clone options.
 * @arg {string} [opts.name] - Element name.
 * @arg {object} [opts.selector] - CSS selector of DOM element.
 * @arg {Page} [opts.page] - Page with element.
 * @return {Element} Cloned element.
 */
Element.prototype.clone = function (opts = {}) {
    const name = U.defVal(opts.name, this.name);
    const selector = U.defVal(opts.selector, this.selector);
    const page = U.defVal(opts.page, weak.get(this._page));
    return new this.constructor(name, selector, page);
};

module.exports = Element;