UNPKG

@applitools/eyes.selenium

Version:

Applitools Eyes SDK for Selenium WebDriver

421 lines (366 loc) 10.5 kB
'use strict'; const { WebElement } = require('selenium-webdriver'); const { Region, MouseTrigger, ArgumentGuard, CoordinatesType } = require('@applitools/eyes.sdk.core'); const JS_GET_SCROLL_LEFT = 'return arguments[0].scrollLeft;'; const JS_GET_SCROLL_TOP = 'return arguments[0].scrollTop;'; const JS_GET_SCROLL_WIDTH = 'return arguments[0].scrollWidth;'; const JS_GET_SCROLL_HEIGHT = 'return arguments[0].scrollHeight;'; const JS_GET_OVERFLOW = 'return arguments[0].style.overflow;'; const JS_GET_CLIENT_WIDTH = 'return arguments[0].clientWidth;'; const JS_GET_CLIENT_HEIGHT = 'return arguments[0].clientHeight;'; /** * @param {string} styleProp * @return {string} */ const JS_GET_COMPUTED_STYLE_FORMATTED_STR = styleProp => `var elem = arguments[0], styleProp = '${styleProp}'; ` + // eslint-disable-line implicit-arrow-linebreak 'if (window.getComputedStyle) { ' + ' return window.getComputedStyle(elem, null).getPropertyValue(styleProp);' + '} else if (elem.currentStyle) { ' + ' return elem.currentStyle[styleProp];' + '} else { ' + ' return null;' + '}'; /** * @param {number} scrollLeft * @param {number} scrollTop * @return {string} */ const JS_SCROLL_TO_FORMATTED_STR = (scrollLeft, scrollTop) => `arguments[0].scrollLeft = ${scrollLeft}; ` + // eslint-disable-line implicit-arrow-linebreak `arguments[0].scrollTop = ${scrollTop};`; /** * @param {string} overflow * @return {string} */ const JS_SET_OVERFLOW_FORMATTED_STR = overflow => `arguments[0].style.overflow = '${overflow}'`; /** * Wraps a Selenium Web Element. */ class EyesWebElement extends WebElement { /** * @param {Logger} logger * @param {EyesWebDriver} eyesDriver * @param {WebElement} webElement * */ constructor(logger, eyesDriver, webElement) { // noinspection JSCheckFunctionSignatures super(eyesDriver.getRemoteWebDriver(), 'unused'); ArgumentGuard.notNull(logger, 'logger'); ArgumentGuard.notNull(eyesDriver, 'eyesDriver'); ArgumentGuard.notNull(webElement, 'webElement'); this._logger = logger; this._eyesDriver = eyesDriver; this._webElement = webElement; } /** * @return {Promise<Region>} */ getBounds() { const that = this; // noinspection JSValidateTypes return that.getLocation() .then(/** @type {{x: number, y: number}} */location_ => { let left = location_.x; let top = location_.y; let width = 0; let height = 0; return that.getSize() .then(/** @type {{width: number, height: number}} */size_ => { ({ width, height } = size_); }) .catch(() => { // Not supported on all platforms. }) .then(() => { if (left < 0) { width = Math.max(0, width + left); left = 0; } if (top < 0) { height = Math.max(0, height + top); top = 0; } return new Region(left, top, width, height, CoordinatesType.CONTEXT_RELATIVE); }); }); } /** * Returns the computed value of the style property for the current element. * * @param {string} propStyle The style property which value we would like to extract. * @return {Promise<string>} The value of the style property of the element, or {@code null}. */ getComputedStyle(propStyle) { return this.executeScript(JS_GET_COMPUTED_STYLE_FORMATTED_STR(propStyle)); } /** * @param {string} propStyle The style property which value we would like to extract. * @return {Promise<number>} The integer value of a computed style. */ getComputedStyleInteger(propStyle) { return this.getComputedStyle(propStyle).then(result => Math.round(parseFloat(result.trim().replace('px', '')))); } /** * @return {Promise<number>} The value of the scrollLeft property of the element. */ getScrollLeft() { return this.executeScript(JS_GET_SCROLL_LEFT).then(result => Math.ceil(parseFloat(result))); } /** * @return {Promise<number>} The value of the scrollTop property of the element. */ getScrollTop() { return this.executeScript(JS_GET_SCROLL_TOP).then(result => Math.ceil(parseFloat(result))); } /** * @return {Promise<number>} The value of the scrollWidth property of the element. */ getScrollWidth() { return this.executeScript(JS_GET_SCROLL_WIDTH).then(result => Math.ceil(parseFloat(result))); } /** * @return {Promise<number>} The value of the scrollHeight property of the element. */ getScrollHeight() { return this.executeScript(JS_GET_SCROLL_HEIGHT).then(result => Math.ceil(parseFloat(result))); } /** * @return {Promise<number>} */ getClientWidth() { return this.executeScript(JS_GET_CLIENT_WIDTH).then(result => Math.ceil(parseFloat(result))); } /** * @return {Promise<number>} */ getClientHeight() { return this.executeScript(JS_GET_CLIENT_HEIGHT).then(result => Math.ceil(parseFloat(result))); } // noinspection JSUnusedGlobalSymbols /** * @return {Promise<number>} The width of the left border. */ getBorderLeftWidth() { return this.getComputedStyleInteger('border-left-width'); } // noinspection JSUnusedGlobalSymbols /** * @return {Promise<number>} The width of the right border. */ getBorderRightWidth() { return this.getComputedStyleInteger('border-right-width'); } // noinspection JSUnusedGlobalSymbols /** * @return {Promise<number>} The width of the top border. */ getBorderTopWidth() { return this.getComputedStyleInteger('border-top-width'); } // noinspection JSUnusedGlobalSymbols /** * @return {Promise<number>} The width of the bottom border. */ getBorderBottomWidth() { return this.getComputedStyleInteger('border-bottom-width'); } /** * Scrolls to the specified location inside the element. * * @param {Location} location The location to scroll to. * @return {Promise<void>} */ scrollTo(location) { return this.executeScript(JS_SCROLL_TO_FORMATTED_STR(location.getX(), location.getY())); } /** * @return {Promise<string>} The overflow of the element. */ getOverflow() { return this.executeScript(JS_GET_OVERFLOW); } /** * @param {string} overflow The overflow to set * @return {Promise<void>} The overflow of the element. */ setOverflow(overflow) { return this.executeScript(JS_SET_OVERFLOW_FORMATTED_STR(overflow)); } /** * @param {string} script The script to execute with the element as last parameter * @return {Promise<*>} The result returned from the script */ executeScript(script) { // noinspection JSValidateTypes return this._eyesDriver.executeScript(script, this.getWebElement()); } /** * @override * @inheritDoc */ getDriver() { return this.getWebElement().getDriver(); } /** * @override * @inheritDoc */ getId() { return this.getWebElement().getId(); } /** * @override * @inheritDoc */ findElement(locator) { const that = this; // noinspection JSValidateTypes return this.getWebElement() .findElement(locator) .then(element => new EyesWebElement(that._logger, that._eyesDriver, element)); } /** * @override * @inheritDoc */ findElements(locator) { const that = this; return this.getWebElement() .findElements(locator) .then(elements => elements.map(element => new EyesWebElement(that._logger, that._eyesDriver, element))); } // noinspection JSCheckFunctionSignatures /** * @override * @inheritDoc * @return {Promise<void>} */ click() { // Letting the driver know about the current action. const that = this; return that.getBounds().then(currentControl => { that._eyesDriver.getEyes().addMouseTrigger(MouseTrigger.MouseAction.Click, this); that._logger.verbose(`click(${currentControl})`); return that.getWebElement().click(); }); } // noinspection JSCheckFunctionSignatures /** * @override * @inheritDoc */ sendKeys(...keysToSend) { const that = this; // noinspection JSValidateTypes return keysToSend.reduce((promise, keys) => promise.then(() => that._eyesDriver.getEyes() .addTextTriggerForElement(that, String(keys))), that._eyesDriver.getPromiseFactory().resolve()) .then(() => that.getWebElement().sendKeys(...keysToSend)); } /** * @override * @inheritDoc */ getTagName() { return this.getWebElement().getTagName(); } /** * @override * @inheritDoc */ getCssValue(cssStyleProperty) { return this.getWebElement().getCssValue(cssStyleProperty); } /** * @override * @inheritDoc */ getAttribute(attributeName) { return this.getWebElement().getAttribute(attributeName); } /** * @override * @inheritDoc */ getText() { return this.getWebElement().getText(); } /** * @override * @inheritDoc */ getSize() { return this.getWebElement().getSize(); } /** * @override * @inheritDoc */ getLocation() { // The workaround is similar to Java one, but in js we always get raw data with decimal value which we should round // up. return this.getWebElement() .getLocation() .then(value => { const x = Math.ceil(value.x) || 0; // noinspection JSSuspiciousNameCombination const y = Math.ceil(value.y) || 0; return { x, y }; }); } /** * @override * @inheritDoc */ isEnabled() { return this.getWebElement().isEnabled(); } /** * @override * @inheritDoc */ isSelected() { return this.getWebElement().isSelected(); } /** * @override * @inheritDoc */ submit() { return this.getWebElement().submit(); } /** * @override * @inheritDoc */ clear() { return this.getWebElement().clear(); } /** * @override * @inheritDoc */ isDisplayed() { return this.getWebElement().isDisplayed(); } /** * @override * @inheritDoc */ takeScreenshot(optScroll) { return this.getWebElement().takeScreenshot(optScroll); } /** * @return {WebElement} The original element object */ getWebElement() { // noinspection JSUnresolvedVariable if (this._webElement.getWebElement) { // noinspection JSUnresolvedFunction return this._webElement.getWebElement(); } return this._webElement; } } exports.EyesWebElement = EyesWebElement;