Js.java

package io.github.giulong.spectrum.utils.js;

import io.github.giulong.spectrum.interfaces.WebElementFinder;
import io.github.giulong.spectrum.utils.StringUtils;
import lombok.Builder;
import org.openqa.selenium.*;

import java.util.*;

import static java.util.stream.Collectors.joining;

@Builder
@SuppressWarnings("checkstyle:MultipleStringLiterals")
public class Js {

    private static final Map<String, String> CONVERSION_MAP = new HashMap<>() {{
        put("class", "className");
        put("readonly", "readOnly");
    }};

    private final StringUtils stringUtils = StringUtils.getInstance();

    private JavascriptExecutor driver;

    /**
     * Find the first WebElement using the given method starting from the provided context
     *
     * @param context          the context where to search the element
     * @param webElementFinder locating mechanism for finding the WebElement
     * @param locatorValue     the value used by the locating mechanism
     * @return the found WebElement
     */
    public WebElement findElement(final WebElement context, final WebElementFinder webElementFinder, final String locatorValue) {
        return webElementFinder.findElement(driver, context, stringUtils.escape(locatorValue));
    }

    /**
     * Find the first WebElement using the given method starting from document
     *
     * @param webElementFinder the locating mechanism for finding the WebElement
     * @param locatorValue     the value used by the locating mechanism
     * @return the found WebElement
     */
    public WebElement findElement(final WebElementFinder webElementFinder, final String locatorValue) {
        return webElementFinder.findElement(driver, null, stringUtils.escape(locatorValue));
    }

    /**
     * Find all WebElements using the given method starting from the provided context
     *
     * @param context          the context where to search the elements
     * @param webElementFinder the locating mechanism for finding all WebElements
     * @param locatorValue     the value used by the locating mechanism
     * @return the list of found WebElements
     */
    public List<WebElement> findElements(final WebElement context, final WebElementFinder webElementFinder, final String locatorValue) {
        return webElementFinder.findElements(driver, context, stringUtils.escape(locatorValue));
    }

    /**
     * Find all WebElements using the given method starting from the provided context
     *
     * @param webElementFinder the locating mechanism for finding all WebElements
     * @param locatorValue     the value used by the locating mechanism
     * @return the list of found WebElements
     */
    public List<WebElement> findElements(final WebElementFinder webElementFinder, final String locatorValue) {
        return webElementFinder.findElements(driver, null, stringUtils.escape(locatorValue));
    }

    /**
     * Get the innerText of the provided webElement
     *
     * @param webElement the WebElement from which the innerText has to be taken
     * @return the value of the innerText
     */
    public String getText(final WebElement webElement) {
        return (String) driver.executeScript("return arguments[0].innerText;", webElement);
    }

    /**
     * Get the CSS Value of the provided property
     *
     * @param webElement  the WebElement from which the tag CSS value is taken
     * @param cssProperty the CSS property to read
     * @return the value of the CSS property as String
     */
    public String getCssValue(final WebElement webElement, final String cssProperty) {
        return (String) driver.executeScript(String.format("return window.getComputedStyle(arguments[0]).getPropertyValue('%s');", cssProperty), webElement);
    }

    /**
     * Get the shadowRoot of the provided WebElement
     *
     * @param webElement the WebElement from which the shadowRoot is taken
     * @return the shadowRoot of the WebElement
     */
    public SearchContext getShadowRoot(final WebElement webElement) {
        return (SearchContext) driver.executeScript("return arguments[0].shadowRoot;", webElement);
    }

    /**
     * Get the Tag of the provided WebElement
     *
     * @param webElement the WebElement from which the tag name is taken
     * @return the tag name of the WebElement
     */
    public String getTagName(final WebElement webElement) {
        final String tagName = (String) driver.executeScript("return arguments[0].tagName;", webElement);

        return tagName.toLowerCase();
    }

    /**
     * Get the static attribute of the provided WebElement
     *
     * @param webElement   the WebElement from which the static attribute is taken
     * @param domAttribute the static Attribute to retrieve
     * @return the DOM Attribute of the WebElement or null if there isn't
     */
    public String getDomAttribute(final WebElement webElement, final String domAttribute) {
        final String jsCommand = String.format("return arguments[0].getAttribute('%s');", domAttribute);

        return (String) driver.executeScript(jsCommand, webElement);
    }

    /**
     * Get the property of the provided WebElement
     *
     * @param webElement  the webElement from which the property is taken
     * @param domProperty the property to retrieve
     * @return the DOM property of the WebElement or null if there isn't
     */
    public String getDomProperty(final WebElement webElement, final String domProperty) {
        final String jsCommand = String.format("return arguments[0].%s;", domProperty);

        return (String) driver.executeScript(jsCommand, webElement);
    }

    /**
     * Get the property of the provided WebElement, if is null tries to take the dom attribute with the same name
     *
     * @param webElement the webElement from which the property is taken
     * @param attribute  the property/attribute to retrieve
     * @return the attribute/property current value or null if the value is not set.
     */
    public String getAttribute(final WebElement webElement, final String attribute) {
        final String checkedAttribute = convert(attribute);
        final String domProperty = this.getDomProperty(webElement, checkedAttribute);

        return domProperty == null ? this.getDomAttribute(webElement, checkedAttribute) : domProperty;
    }

    /**
     * Determine whether the element is selected or not
     *
     * @param webElement the webElement to check
     * @return true if element is currently selected/checked, false otherwise
     */
    public boolean isSelected(final WebElement webElement) {
        return (boolean) driver.executeScript("return arguments[0].checked;", webElement);
    }

    /**
     * Determine whether the element is enabled or not
     *
     * @param webElement the webElement to check
     * @return true if element is enabled, false otherwise
     */
    public boolean isEnabled(final WebElement webElement) {
        final boolean disabled = (boolean) driver.executeScript("return arguments[0].disabled;", webElement);

        return !disabled;
    }

    /**
     * Determine whether the element is displayed or not
     *
     * @param webElement the webElement to check
     * @return true if element is displayed, false otherwise
     */
    public boolean isDisplayed(final WebElement webElement) {
        return (boolean) driver.executeScript("var rectangle = arguments[0].getBoundingClientRect();" +
                "return arguments[0].checkVisibility({visibilityProperty:true, opacityProperty:true}) && " +
                "rectangle.height > 0 && rectangle.width > 0", webElement);
    }

    /**
     * Get the size of the provided WebElement
     *
     * @param webElement the WebElement from which the size is taken
     * @return the rendered Size of the WebElement
     */
    public Dimension getSize(final WebElement webElement) {
        @SuppressWarnings("unchecked") final List<Object> dimensions = (List<Object>) driver.executeScript("var rectangle = arguments[0].getBoundingClientRect(); " +
                "return [rectangle.width, rectangle.height];", webElement);

        return new Dimension(((Number) dimensions.get(0)).intValue(), ((Number) dimensions.get(1)).intValue());
    }

    /**
     * Get the location and size of the provided WebElement
     *
     * @param webElement the WebElement from which the location and size are taken
     * @return the location and size of the rendered WebElement
     */
    public Rectangle getRect(final WebElement webElement) {
        @SuppressWarnings("unchecked") final List<Number> rectangle = (List<Number>) driver.executeScript("var rectangle = arguments[0].getBoundingClientRect(); " +
                "return [rectangle.x, rectangle.y, rectangle.width, rectangle.height];", webElement);

        final Point point = new Point((rectangle.get(0)).intValue(), (rectangle.get(1)).intValue());
        final Dimension dimension = new Dimension((rectangle.get(2)).intValue(), (rectangle.get(3)).intValue());

        return new Rectangle(point, dimension);
    }

    /**
     * Get the location of the provided WebElement
     *
     * @param webElement the WebElement from which the location and size are taken
     * @return the top left-hand point of the rendered WebElement
     */
    public Point getLocation(final WebElement webElement) {
        @SuppressWarnings("unchecked") final List<Object> point = (List<Object>) driver.executeScript("var rectangle = arguments[0].getBoundingClientRect(); " +
                "return [rectangle.x, rectangle.y];", webElement);

        return new Point(((Number) point.get(0)).intValue(), ((Number) point.get(1)).intValue());
    }

    /**
     * Performs a click with javascript on the provided WebElement
     *
     * @param webElement the WebElement to click on
     * @return the calling SpectrumEntity instance
     */
    public Js click(final WebElement webElement) {
        driver.executeScript("arguments[0].click();", webElement);

        return this;
    }

    /**
     * Send input value with javascript to the provided WebElement
     *
     * @param webElement the WebElement to target the send method
     * @param keysToSend the String to send to webElement
     * @return the calling SpectrumEntity instance
     */
    public Js sendKeys(final WebElement webElement, final CharSequence... keysToSend) {
        final String escapedKeysToSend = Arrays
                .stream(keysToSend)
                .map(key -> key instanceof String ? stringUtils.escape((String) key) : key)
                .collect(joining());

        driver.executeScript(String.format("arguments[0].value='%s';", escapedKeysToSend), webElement);

        return this;
    }

    /**
     * Performs a submit action on the provided form
     *
     * @param webElement the WebElement to submit
     * @return the calling SpectrumEntity instance
     */
    public Js submit(final WebElement webElement) {
        driver.executeScript("arguments[0].submit();", webElement);

        return this;
    }

    /**
     * Clear input value with javascript on the provided WebElement
     *
     * @param webElement the WebElement used to clear the input field
     * @return the calling SpectrumEntity instance
     */
    public Js clear(final WebElement webElement) {
        driver.executeScript("arguments[0].value='';", webElement);

        return this;
    }

    protected String convert(final String stringToConvert) {
        return CONVERSION_MAP.getOrDefault(stringToConvert, stringToConvert);
    }
}