com.seleniumtests.uipage.htmlelements.HtmlElement.java Source code

Java tutorial

Introduction

Here is the source code for com.seleniumtests.uipage.htmlelements.HtmlElement.java

Source

/**
 * Orignal work: Copyright 2015 www.seleniumtests.com
 * Modified work: Copyright 2016 www.infotel.com
 *             Copyright 2017-2019 B.Hecquet
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.seleniumtests.uipage.htmlelements;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;
import org.openqa.selenium.By;
import org.openqa.selenium.By.ByXPath;
import org.openqa.selenium.Dimension;
import org.openqa.selenium.ElementNotVisibleException;
import org.openqa.selenium.HasCapabilities;
import org.openqa.selenium.InvalidElementStateException;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.Point;
import org.openqa.selenium.Rectangle;
import org.openqa.selenium.SearchContext;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.ie.InternetExplorerDriver;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.interactions.Coordinates;
import org.openqa.selenium.interactions.Locatable;
import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.RemoteWebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

import com.seleniumtests.browserfactory.FirefoxDriverFactory;
import com.seleniumtests.core.SeleniumTestsContextManager;
import com.seleniumtests.customexception.CustomSeleniumTestsException;
import com.seleniumtests.customexception.DriverExceptions;
import com.seleniumtests.customexception.ScenarioException;
import com.seleniumtests.driver.CustomEventFiringWebDriver;
import com.seleniumtests.driver.TestType;
import com.seleniumtests.driver.WebUIDriver;
import com.seleniumtests.reporter.logger.TestLogging;
import com.seleniumtests.uipage.PageObject;
import com.seleniumtests.uipage.ReplayOnError;
import com.seleniumtests.util.helper.WaitHelper;
import com.seleniumtests.util.logging.SeleniumRobotLogger;

import io.appium.java_client.MobileElement;
import io.appium.java_client.MultiTouchAction;
import io.appium.java_client.PerformsTouchActions;
import io.appium.java_client.TouchAction;
import io.appium.java_client.touch.WaitOptions;
import io.appium.java_client.touch.offset.ElementOption;
import io.appium.java_client.touch.offset.PointOption;

/**
 * Provides methods to interact with a web page. All HTML element (ButtonElement, LinkElement, TextFieldElement, etc.)
 * extends from this class.
 * 
 * 
 */
public class HtmlElement extends Element implements WebElement, Locatable {
    // WARNING!!!: we use the deprecated Locatable interface because it's used by Actions class
    // unit test TestPicutreElement.testClick() fails if the new interface is used
    // so wait to this old interface to be really removed

    protected static final Logger logger = SeleniumRobotLogger.getLogger(HtmlElement.class);
    public static final Integer FIRST_VISIBLE = Integer.MAX_VALUE;

    String JS_CLICK_TRIPLE = "var target = arguments[0];" + "emit('mousedown', {buttons: 1}); "
            + "emit('mouseup',   {});" + "emit('mousedown', {buttons: 1}); " + "emit('mouseup',   {});"
            + "emit('mousedown', {buttons: 1}); " + "emit('mouseup',   {});" + "emit('click',     {detail: 3});  "
            + "" + "function emit(name, init) {" + "target.dispatchEvent(new MouseEvent(name, init));" + "}";

    String JS_CLICK_DOUBLE = "if(document.createEvent){" + "      var evObj = document.createEvent('MouseEvents');"
            + "      evObj.initEvent('dblclick', true, false); " + "      arguments[0].dispatchEvent(evObj);"
            + "} else if(document.createEventObject) { " + "      arguments[0].fireEvent('ondblclick');" + "}";

    protected WebDriver driver;
    protected WebElement element = null;
    protected String label = null;
    protected HtmlElement parent = null;
    protected FrameElement frameElement = null;
    private Integer elementIndex = -1;
    private By by = null;
    private String origin = null;

    public HtmlElement() {
        this("", By.id(""));
    }

    /**
     * Find element using BY locator. Make sure to initialize the driver before calling findElement()
     *
     * @param   label  - element name for logging
     * @param   by     - By type
     *
     * @sample  {@code new HtmlElement("UserId", By.id(userid))}
     */

    public HtmlElement(final String label, final By by) {
        this(label, by, (Integer) null);
    }

    /**
     * Find element using BY locator. 
     *
     * @param   label  - element name for logging
     * @param   by     - By type
     * @param   index  - index of the element to find. In this case, robot will search the Nth element corresponding to
     *                 the By parameter. Equivalent to new HtmlElement(label, by).findElements().get(N)
     *                 If index is null, use <code>driver.findElement(By)</code> intenally
     *               If index is negative, search from the last one (-1)
     *                If index is HtmlElement.FIRST_VISIBLE, search the first visible element
     *
     * @sample  {@code new HtmlElement("UserId", By.id(userid), 2)}
     */
    public HtmlElement(final String label, final By by, final Integer index) {
        this(label, by, null, index);
    }

    public HtmlElement(final String label, final By by, final FrameElement frame) {
        this(label, by, frame, null);
    }

    public HtmlElement(final String label, final By by, final FrameElement frame, final Integer index) {
        this(label, by, frame, null, index);
    }

    public HtmlElement(final String label, final By by, final HtmlElement parent) {
        this(label, by, parent, null);
    }

    public HtmlElement(final String label, final By by, final HtmlElement parent, final Integer index) {
        this(label, by, null, parent, index);
    }

    public HtmlElement(final String label, final By by, final FrameElement frame, final HtmlElement parent,
            final Integer index) {
        this.label = label;
        this.by = by;
        this.elementIndex = index;
        this.frameElement = frame;
        this.parent = parent;

        origin = PageObject.getCallingPage(Thread.currentThread().getStackTrace());
    }

    /**
     * Native click
     */
    @ReplayOnError
    public void click() {
        findElement(true);
        element.click();
    }

    /**
     * Click with CompositeActions
     */
    @ReplayOnError
    public void clickAction() {
        findElement(true);

        try {
            new Actions(driver).click(element).perform();
        } catch (InvalidElementStateException e) {
            logger.error(e);
        }
    }

    /**
     * Double Click with CompositeActions
     */
    @ReplayOnError
    public void doubleClickAction() {
        findElement(true);

        try {
            new Actions(driver).doubleClick(element).perform();
        } catch (InvalidElementStateException e) {
            logger.error(e);
        }
    }

    /**
     * Click element in native way by Actions.
     *
     * <p/>
     * <pre class="code">
       clickAt(1, 1);
     * </pre>
     *
     * @param  value
     */
    @ReplayOnError
    public void clickAt(int xOffset, int yOffset) {
        findElement();
        ((CustomEventFiringWebDriver) driver).scrollToElement(element, yOffset);

        try {
            new Actions(driver).moveToElement(element, xOffset, yOffset).click().perform();
        } catch (InvalidElementStateException e) {
            logger.error(e);
            element.click();
        }
    }

    /**
     * Click with javascript
     */
    @ReplayOnError
    public void simulateClick() {
        if (SeleniumTestsContextManager.isWebTest()) {
            ((CustomEventFiringWebDriver) WebUIDriver.getWebDriver()).updateWindowsHandles();
        }

        findElement(true);

        String mouseOverScript = "if(document.createEvent){var evObj = document.createEvent('MouseEvents');evObj.initEvent('mouseover', true, false); arguments[0].dispatchEvent(evObj);} else if(document.createEventObject) { arguments[0].fireEvent('onmouseover');}";
        JavascriptExecutor js = (JavascriptExecutor) driver;
        js.executeScript(mouseOverScript, element);
        WaitHelper.waitForSeconds(2);

        String clickScript = "if(document.createEvent){var evObj = document.createEvent('MouseEvents');evObj.initEvent('click', true, false); arguments[0].dispatchEvent(evObj);} else if(document.createEventObject) { arguments[0].fireEvent('onclick');}";
        js.executeScript(clickScript, element);
        WaitHelper.waitForSeconds(2);
    }

    @ReplayOnError
    public void simulateDoubleClick() {
        findElement(true);

        JavascriptExecutor js = (JavascriptExecutor) driver;
        js.executeScript(JS_CLICK_DOUBLE, element);

    }

    @ReplayOnError
    public void simulateSendKeys(CharSequence... keysToSend) {
        findElement(true);

        // click on element before sending keys through keyboard
        element.click();
        JavascriptExecutor js = (JavascriptExecutor) driver;
        js.executeScript("arguments[0].focus();", element);

        WebDriver realDriver = ((CustomEventFiringWebDriver) driver).getWebDriver();

        // handle org.openqa.selenium.UnsupportedCommandException: sendKeysToActiveElement which are not available for firefox and IE
        if ((realDriver instanceof FirefoxDriver && FirefoxDriverFactory.isMarionetteMode())
                || realDriver instanceof InternetExplorerDriver) {
            logger.warn("using specific Marionette method");
            js.executeScript(String.format("arguments[0].value='%s';", keysToSend[0].toString()), element);
        } else {
            // use keyboard to type
            ((CustomEventFiringWebDriver) driver).getKeyboard().sendKeys(keysToSend);
        }
    }

    @ReplayOnError
    public void simulateMoveToElement(final int x, final int y) {
        findElement(true);
        ((JavascriptExecutor) driver).executeScript(
                "function simulate(f,c,d,e){var b,a=null;for(b in eventMatchers)if(eventMatchers[b].test(c)){a=b;break}if(!a)return!1;document.createEvent?(b=document.createEvent(a),a==\"HTMLEvents\"?b.initEvent(c,!0,!0):b.initMouseEvent(c,!0,!0,document.defaultView,0,d,e,d,e,!1,!1,!1,!1,0,null),f.dispatchEvent(b)):(a=document.createEventObject(),a.detail=0,a.screenX=d,a.screenY=e,a.clientX=d,a.clientY=e,a.ctrlKey=!1,a.altKey=!1,a.shiftKey=!1,a.metaKey=!1,a.button=1,f.fireEvent(\"on\"+c,a));return!0} var eventMatchers={HTMLEvents:/^(?:load|unload|abort|errorLogger|select|change|submit|reset|focus|blur|resize|scroll)$/,MouseEvents:/^(?:click|dblclick|mouse(?:down|up|over|move|out))$/}; "
                        + "simulate(arguments[0],\"mousemove\",arguments[1],arguments[2]);",
                element, x, y);

    }

    /**
     * Find elements inside this element
     * @param by
     * @return    List of selenium WebElement 
     */
    @Override
    @ReplayOnError
    public List<WebElement> findElements(By by) {

        // find the root element
        findElement(false, false);
        List<WebElement> elements = element.findElements(by);

        // throw exception so that behavior is the same as with 'findElements()' call which retries search
        if (elements.isEmpty()) {
            throw new NoSuchElementException("No elements found for " + by.toString());
        } else {
            return elements;
        }
    }

    /**
     * Find elements inside this element
     * @param by
     * @return   List of HtmlElement's based on real WebElement
     */
    @ReplayOnError
    public List<WebElement> findHtmlElements(By by) {

        // find the root element
        findElement(false, false);
        List<WebElement> htmlElements = new ArrayList<>();
        List<WebElement> elements = element.findElements(by);

        // throw exception so that behavior is the same as with 'findElements()' call which retries search
        if (elements.isEmpty()) {
            throw new NoSuchElementException("No elements found for " + by.toString());
        }

        for (int i = 0; i < elements.size(); i++) {
            htmlElements.add(new HtmlElement("", by, frameElement, parent, i));
        }
        return htmlElements;
    }

    /**
     * Find an element inside an other one
     * @param by
     * @return
     */
    @SuppressWarnings("unchecked")
    @Override
    public HtmlElement findElement(By by) {
        return new HtmlElement(label, by, this);
    }

    public ButtonElement findButtonElement(By by) {
        return new ButtonElement(label, by, this);
    }

    public CheckBoxElement findCheckBoxElement(By by) {
        return new CheckBoxElement(label, by, this);
    }

    public ImageElement findImageElement(By by) {
        return new ImageElement(label, by, this);
    }

    public LabelElement findLabelElement(By by) {
        return new LabelElement(label, by, this);
    }

    public LinkElement findLinkElement(By by) {
        return new LinkElement(label, by, this);
    }

    public RadioButtonElement findRadioButtonElement(By by) {
        return new RadioButtonElement(label, by, this);
    }

    public SelectList findSelectList(By by) {
        return new SelectList(label, by, this);
    }

    public Table findTable(By by) {
        return new Table(label, by, this);
    }

    public TextFieldElement findTextFieldElement(By by) {
        return new TextFieldElement(label, by, this);
    }

    /**
     * Find the Nth element inside an other one
     * Equivalent to HtmlElement("", By.id("0").findElements(By.id("1")).get(0); 
     * except that any action on the found element will be retried if it fails
     * @param by    locator of the element list
     * @param index   index in the list to get
     * @return
     */
    public HtmlElement findElement(By by, Integer index) {
        return new HtmlElement(label, by, this, index);
    }

    public ButtonElement findButtonElement(By by, Integer index) {
        return new ButtonElement(label, by, this, index);
    }

    public CheckBoxElement findCheckBoxElement(By by, Integer index) {
        return new CheckBoxElement(label, by, this, index);
    }

    public ImageElement findImageElement(By by, Integer index) {
        return new ImageElement(label, by, this, index);
    }

    public LabelElement findLabelElement(By by, Integer index) {
        return new LabelElement(label, by, this, index);
    }

    public LinkElement findLinkElement(By by, Integer index) {
        return new LinkElement(label, by, this, index);
    }

    public RadioButtonElement findRadioButtonElement(By by, Integer index) {
        return new RadioButtonElement(label, by, this, index);
    }

    public SelectList findSelectList(By by, Integer index) {
        return new SelectList(label, by, this, index);
    }

    public Table findTable(By by, Integer index) {
        return new Table(label, by, this, index);
    }

    public TextFieldElement findTextFieldElement(By by, Integer index) {
        return new TextFieldElement(label, by, this, index);
    }

    protected void findElement() {
        findElement(false, true);
    }

    protected void findElement(boolean waitForVisibility) {
        findElement(waitForVisibility, true);
    }

    /**
     * Finds the element using By type. Implicit Waits is built in createWebDriver() in WebUIDriver to handle dynamic
     * element problem. This method is invoked before all the basic operations like click, sendKeys, getText, etc. Use
     * waitForPresent to use Explicit Waits to deal with special element which needs long time to present.
     * @param waitForVisibility      wait for element to be visible
     * @param makeVisible         whether we try to make the element visible. Should be true except when trying to know if element is displayed
     */
    public void findElement(boolean waitForVisibility, boolean makeVisible) {
        // TODO: https://discuss.appium.io/t/how-can-i-scroll-to-an-element-in-appium-im-using-android-native-app/10618/14
        // String DESTINATION_ELEMENT_TEXT= "KUBO";
        //((AndroidDriver) driver).findElementByAndroidUIAutomator("new UiScrollable(new UiSelector())
        //      .scrollIntoView(new UiSelector().text(DESTINATION_ELEMENT_TEXT))");

        ElementInfo elementInfo = null;

        // search element information. Do not stop if something goes wrong here
        if (SeleniumTestsContextManager.getThreadContext().getAdvancedElementSearch() != ElementInfo.Mode.FALSE) {
            try {
                elementInfo = ElementInfo.getInstance(this);
            } catch (Throwable e) {
            }
        }

        // if a parent is defined, search for it before getting the sub element
        driver = updateDriver();
        if (parent != null) {
            parent.findElement(false, false);

            // issue #166: add a dot in front of xpath expression if we search the element inside a parent
            if (by instanceof ByXPath) {
                try {
                    Field xpathExpressionField = ByXPath.class.getDeclaredField("xpathExpression");
                    xpathExpressionField.setAccessible(true);
                    String xpath = (String) xpathExpressionField.get(by);

                    if (xpath.startsWith("//")) {
                        by = By.xpath("." + xpath);
                    }

                } catch (NoSuchFieldException | SecurityException | IllegalArgumentException
                        | IllegalAccessException e) {
                    throw new CustomSeleniumTestsException(e);
                }

            }

            element = findSeleniumElement(parent.element, elementInfo);

        } else {
            element = findSeleniumElement(driver, elementInfo);
        }

        if (makeVisible) {
            makeWebElementVisible(element);
            ((CustomEventFiringWebDriver) driver).scrollToElement(element, -200);
        }

        // wait for element to be really visible. should be done only for actions on element
        if (waitForVisibility && makeVisible) {
            new WebDriverWait(driver, 1).until(ExpectedConditions.visibilityOf(element));
        }

        // If we are here, element has been found, update elementInformation
        if (elementInfo != null) {
            try {
                elementInfo.updateInfo(this, driver);
                elementInfo.exportToJsonFile(this);
            } catch (Throwable e) {
                logger.warn("Error storing element information: " + e.getMessage());
            }
        }
    }

    /**
     * Call driver to really search the element
     * If index is specified, return the Nth element corresponding to search
     * 
     * @param context
     * @param elementInfo
     * @return
     */
    private WebElement findSeleniumElement(SearchContext context, ElementInfo elementInfo) {
        enterFrame();

        WebElement seleniumElement;
        try {
            if (elementIndex == null) {
                seleniumElement = context.findElement(by);
            } else {
                seleniumElement = getElementByIndex(context.findElements(by));
            }
            return seleniumElement;
        } catch (WebDriverException e) {

            // element not found, raise exception
            // this code is here to prepare advanced element search
            throw e;
        }
    }

    /**
     * returns an element depending on configured index
     * @param allElements
     */
    private WebElement getElementByIndex(List<WebElement> allElements) {
        if (elementIndex == FIRST_VISIBLE) {
            for (WebElement el : allElements) {
                if (el.isDisplayed()) {
                    return el;
                }
            }
            throw new WebDriverException("no visible element has been found for " + by.toString());
        } else if (elementIndex < 0) {
            return allElements.get(allElements.size() + elementIndex);
        } else {
            if (elementIndex == null) {
                elementIndex = 0;
            }
            return allElements.get(elementIndex);
        }
    }

    /**
     * Method for going into the right frame before doing anything else
     * this method should be called each time we need to get an element
     * Therefore, it's used inside findElement() method
     */
    private void enterFrame() {
        List<FrameElement> frameTree = new ArrayList<>();
        FrameElement frame = getFrameElement();

        while (frame != null) {
            frameTree.add(0, frame);
            frame = frame.getFrameElement();
        }

        for (FrameElement frameEl : frameTree) {
            WebElement frameWebElement = driver.findElement(frameEl.getBy());
            ((CustomEventFiringWebDriver) driver).scrollToElement(frameWebElement, -20);
            driver.switchTo().frame(frameWebElement);
        }
    }

    protected void changeCssAttribute(WebElement element, String cssProperty, String cssPropertyValue) {
        String javascript = "arguments[0].style." + cssProperty + "='" + cssPropertyValue + "';";
        ((JavascriptExecutor) driver).executeScript(javascript, element);
    }

    /**
    * Make element visible. Sometimes useful when real elements are backed by an image element
    */
    protected void makeWebElementVisible(WebElement element) {
        if (SeleniumTestsContextManager.isWebTest()) {
            if (element.isDisplayed()) {
                return;
            }
            try {

                if (element.getLocation().x < 0) {
                    Long viewportHeight = (Long) ((JavascriptExecutor) driver)
                            .executeScript("return document.documentElement.clientHeight");
                    Integer heightPosition = element.getLocation().y > viewportHeight
                            ? element.getLocation().y - viewportHeight.intValue()
                            : element.getLocation().y;
                    changeCssAttribute(element, "left", "20px");
                    changeCssAttribute(element, "top", heightPosition + "px");
                    changeCssAttribute(element, "position", "inherit");
                }
                if (element.getAttribute("style").toLowerCase().replace(" ", "").contains("display:none")) {
                    changeCssAttribute(element, "display", "block");
                }
                //            changeCssAttribute(element, "clip", "auto");
                changeCssAttribute(element, "zIndex", "100000");
            } catch (Exception e) {
                return;
            }

            // wait for element to be displayed
            try {
                new WebDriverWait(driver, 1).until(ExpectedConditions.visibilityOf(element));
            } catch (ElementNotVisibleException e) {
                TestLogging.info(String.format("element %s not visible", element));
            } catch (Exception e) {
                logger.warn("Could not make element visible", e);
            }

        }
    }

    /**
     * Move to element
     * @param element
     */
    public void scrollToElement(int yOffset) {
        findElement();
        ((CustomEventFiringWebDriver) driver).scrollToElement(element, yOffset);
    }

    /**
     * Get all elements in the current page with same locator.
     *
     * @return
     */
    @ReplayOnError
    public List<WebElement> findElements() {

        // call findElement to enter any specified frame and search for parent elements
        findElement(false, false);

        // issue #167: if we have a parent, search elements inside it
        if (parent != null) {
            return parent.element.findElements(by);
        } else {
            return driver.findElements(by);
        }
    }

    /**
     * Gets an attribute (using standard key-value pair) from the underlying attribute.
     *
     * @param   name
     *
     * @return
     */
    @ReplayOnError
    public String getAttribute(final String name) {
        findElement(false, false);

        return element.getAttribute(name);
    }

    /**
     * Returns the BY locator stored in the HtmlElement.
     *
     * @return
     */
    public By getBy() {
        return by;
    }

    /**
     * Returns the value for the specified CSS key.
     *
     * @param   propertyName
     *
     * @return
     */
    @Override
    @ReplayOnError
    public String getCssValue(final String propertyName) {
        findElement(false, false);

        return element.getCssValue(propertyName);
    }

    /**
     * Get and refresh underlying WebDriver.
     */
    protected WebDriver updateDriver() {
        return WebUIDriver.getWebDriver();
    }

    public WebDriver getDriver() {
        return driver;
    }

    public void setDriver(WebDriver driver) {
        this.driver = driver;
    }

    /**
      * Returns the underlying WebDriver WebElement.
      *
      * @return
      */
    @ReplayOnError
    public WebElement getElement() {
        findElement(true);
        return element;
    }

    /**
     * Executes the given JavaScript against the underlying WebElement.
     *
     * @param   script
     *
     * @return
     */
    @ReplayOnError
    public String getEval(final String script) {
        findElement(false, false);

        return (String) ((JavascriptExecutor) driver).executeScript(script, element);
    }

    /**
     * Returns the 'height' property of the underlying WebElement's Dimension.
     *
     * @return
     */
    @ReplayOnError
    public int getHeight() {
        findElement(false, false);

        return element.getSize().getHeight();
    }

    /**
     * Returns the label used during initialization.
     *
     * @return
     */
    public String getLabel() {
        return label;
    }

    /**
     * Gets the Point location of the underlying WebElement.
     *
     * @return
     */
    @Override
    @ReplayOnError
    public Point getLocation() {
        findElement(false, false);

        return element.getLocation();
    }

    @Override
    @ReplayOnError
    public Rectangle getRect() {
        findElement(false, false);
        return new Rectangle(element.getLocation(), element.getSize());
    }

    /**
     * Returns the Dimension property of the underlying WebElement.
     *
     * @return
     */
    @Override
    @ReplayOnError
    public Dimension getSize() {
        findElement(false, false);

        return element.getSize();
    }

    /**
     * Returns the HTML Tag for the underlying WebElement (div, a, input, etc).
     *
     * @return
     */
    @Override
    @ReplayOnError
    public String getTagName() {
        findElement(false, false);

        return element.getTagName();
    }

    /**
     * Returns the text body of the underlying WebElement.
     *
     * @return
     */
    @Override
    @ReplayOnError
    public String getText() {
        findElement(false, false);

        return element.getText();
    }

    /**
     * Returns the 'value' attribute of the underlying WebElement.
     *
     * @return
     */
    @ReplayOnError
    public String getValue() {
        findElement(false, false);

        return element.getAttribute("value");
    }

    /**
     * Returns the 'width' property of the underlying WebElement's Dimension.
     *
     * @return
     */
    @ReplayOnError
    public int getWidth() {
        findElement(false, false);

        return element.getSize().getWidth();
    }

    /**
     * Indicates whether or not the web element is currently displayed in the browser.
     *
     * @return
     */
    @Override
    public boolean isDisplayed() {
        try {
            return isDisplayedRetry();
        } catch (Exception e) {
            return false;
        }
    }

    @ReplayOnError
    public boolean isDisplayedRetry() {
        findElement(false, false);
        return element.isDisplayed();
    }

    /**
     * Searches for the element using the BY locator, and indicates whether or not it exists in the page. This can be
     * used to look for hidden objects, whereas isDisplayed() only looks for things that are visible to the user
     * @param timeout timeout in seconds
     * @return
     */
    public boolean isElementPresent(int timeout) {
        try {
            waitForPresent(timeout);
            return true;
        } catch (TimeoutException e) {
            return false;
        }
    }

    public boolean isElementPresent() {
        return isElementPresent(SeleniumTestsContextManager.getThreadContext().getExplicitWaitTimeout());
    }

    /**
     * Indicates whether or not the element is enabled in the browser.
     *
     * @return
     */
    @Override
    @ReplayOnError
    public boolean isEnabled() {
        findElement(false, false);

        return element.isEnabled();
    }

    /**
     * Indicates whether or not the element is selected in the browser.
     *
     * @return
     */
    @Override
    @ReplayOnError
    public boolean isSelected() {
        findElement(false, false);

        return element.isSelected();
    }

    /**
     * Whether or not the indicated text is contained in the element's getText() attribute.
     *
     * @param   text
     *
     * @return
     */
    public boolean isTextPresent(final String pattern) {
        String text = getText();
        return text != null && (text.contains(pattern) || text.matches(pattern));
    }

    /**
     * @deprecated (due to selenium Mouse deprecation)
     * Forces a mouseDown event on the WebElement.
     */
    @Deprecated
    public void mouseDown() {
        logger.error("use 'new Actions(driver).moveToElement(element).click().perform();' instead");
    }

    /**
     * @deprecated (due to selenium Mouse deprecation)
     * Forces a mouseOver event on the WebElement.
     */
    @Deprecated
    public void mouseOver() {
        logger.error("use 'new Actions(driver).moveToElement(element).click().perform();' instead");
    }

    /**
     * Forces a mouseOver event on the WebElement using simulate by JavaScript way for some dynamic menu.
     */
    @ReplayOnError
    public void simulateMouseOver() {
        findElement(true);

        String mouseOverScript = "if(document.createEvent){var evObj = document.createEvent('MouseEvents');evObj.initEvent('mouseover', true, false); arguments[0].dispatchEvent(evObj);} else if(document.createEventObject) { arguments[0].fireEvent('onmouseover');}";
        JavascriptExecutor js = (JavascriptExecutor) driver;
        js.executeScript(mouseOverScript, element);
    }

    @Override
    public void sendKeys(CharSequence... keysToSend) {
        sendKeys(true, keysToSend);
    }

    public void sendKeys(final boolean blurAfter, CharSequence... keysToSend) {
        // Appium seems to clear field before writing
        if (SeleniumTestsContextManager.getThreadContext().getTestType().family() == TestType.APP) {
            sendKeys(false, blurAfter, keysToSend);
        } else {
            sendKeys(true, blurAfter, keysToSend);
        }
    }

    protected void blur() {
        if (SeleniumTestsContextManager.isWebTest() && "input".equalsIgnoreCase(element.getTagName())) {
            try {
                ((JavascriptExecutor) driver).executeScript("arguments[0].blur();", element);
            } catch (Exception e) {
                logger.error(e);
            }
        }
    }

    /**
     * Send keys through composite actions
     * /!\ does not clear text before and no blur after
     * 
     * @param keysToSend
     */
    @ReplayOnError
    public void sendKeysAction(CharSequence... keysToSend) {
        findElement(true);
        new Actions(driver).sendKeys(element, keysToSend).build().perform();
    }

    /**
     * Sends the indicated CharSequence to the WebElement.
     *
     * @param    clear      if true, clear field before writing
     * @param   blurAfter   if true, do blur() after sendKeys has been done
     * @param   keysToSend   write this text
     */
    @ReplayOnError
    public void sendKeys(final boolean clear, final boolean blurAfter, CharSequence... keysToSend) {
        findElement(true);

        if (clear) {
            element.clear();
        }
        element.sendKeys(keysToSend);

        if (blurAfter) {
            blur();
        }
    }

    @Override
    @ReplayOnError
    public void clear() {
        findElement(true);
        element.clear();
    }

    /**
     * Method, which should never be used.
     */
    protected void sleep(final int waitTime) throws InterruptedException {
        Thread.sleep(waitTime);
    }

    /**
    * Returns string matching the pattern
    * 
    * @param pattern         pattern to find in element text or one of its attribute
    * @param attributeName      name of the attribute to look for
    * @return found string
    */
    @ReplayOnError
    public String findPattern(Pattern pattern, String attributeName) {
        findElement(false, false);
        String attributeValue;
        if ("text".equals(attributeName)) {
            attributeValue = element.getText();
        } else {
            attributeValue = element.getAttribute(attributeName);
        }

        Matcher matcher = pattern.matcher(attributeValue);
        if (matcher.matches() && matcher.groupCount() > 0) {
            return matcher.group(1);
        }

        return "";
    }

    /**
    * Returns URL present in one of the element attributes
    * @param attributeName      attribute name in which we should look at. Give "text" to search in value
    * @return                the found link
    */
    public String findLink(String attributeName) {

        // link <a href="#" id="linkPopup2" onclick="window.open('http://www.infotel.com/', '_blank');">
        String link = findPattern(Pattern.compile(".*(http://.*?)'\"?.*"), attributeName);
        if (!"".equals(link)) {
            return link;
        }

        // link with simple quotes  <a href="#" id="linkPopup" onclick='window.open("http://www.infotel.com/", "_blank");'>
        link = findPattern(Pattern.compile(".*(http://.*?)\"'?.*"), attributeName);
        if (!"".equals(link)) {
            return link;
        }

        // no quotes
        return findPattern(Pattern.compile(".*(http://.*)"), attributeName);
    }

    /**
     * Converts the Type, Locator and LabelElement attributes of the HtmlElement into a readable and report-friendly
     * string.
     *
     * @return
     */
    public String toHTML() {
        return getClass().getSimpleName().toLowerCase()
                + " <a style=\"font-style:normal;color:#8C8984;text-decoration:none;\" href=# \">" + getLabel()
                + ",: " + getBy().toString() + "</a>";
    }

    /**
     * Returns a friendly string, representing the HtmlElement's Type, LabelElement and Locator.
     */
    @Override
    public String toString() {
        String elDescr = getClass().getSimpleName() + " " + getLabel() + ", by={" + getBy().toString() + "}";
        if (parent != null) {
            elDescr += ", sub-element of " + parent.toString();
        }
        return elDescr;
    }

    /**
     * Method created for test purpose only
     */
    @ReplayOnError
    public void doNothing() {
        // do nothing
    }

    /*
     * Methods for mobile actions only
     */

    /**
     * findElement returns a EventFiringWebElement which is not compatible with MobileElement
     * Get the unerlying element and return it
     */
    private WebElement getUnderlyingElement(WebElement element) {

        if (element.getClass().getName().contains("EventFiringWebElement")) {
            try {
                Method getWrappedElementMethod = element.getClass().getDeclaredMethod("getWrappedElement");
                getWrappedElementMethod.setAccessible(true);
                return (WebElement) getWrappedElementMethod.invoke(element);
            } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
                    | InvocationTargetException e) {
                throw new DriverExceptions("cannot get wrapped Element", e);
            }
        } else {
            return element;
        }

    }

    @ReplayOnError
    public Point getCenter() {
        try {
            checkForMobile();
            return ((MobileElement) getUnderlyingElement(element)).getCenter();
        } catch (ScenarioException e) {
            Rectangle rectangle = element.getRect();
            return new Point(rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height / 2);
        }
    }

    @ReplayOnError
    public void pinch() {
        PerformsTouchActions performTouchActions = checkForMobile();
        MobileElement mobElement = (MobileElement) getUnderlyingElement(element);

        // code taken from appium
        MultiTouchAction multiTouch = new MultiTouchAction(performTouchActions);

        Point upperLeft = mobElement.getLocation();
        Point center = mobElement.getCenter();
        int yOffset = center.getY() - upperLeft.getY();

        TouchAction<?> action0 = createTouchAction()
                .press(ElementOption.element(mobElement, center.getX(), center.getY() - yOffset))
                .moveTo(ElementOption.element(mobElement)).release();
        TouchAction<?> action1 = createTouchAction()
                .press(ElementOption.element(mobElement, center.getX(), center.getY() + yOffset))
                .moveTo(ElementOption.element(mobElement)).release();

        multiTouch.add(action0).add(action1).perform();

    }

    /**
     * Convenience method for swiping on the given element to the given direction
     * @param xOffset   X offset from the top-left corner of the element
     * @param yOffset   Y offset from the top-left corner of the element
     * @param xMove      Movement amplitude on x axis
     * @param yMove      Movement amplitude on y axis
     */
    @ReplayOnError
    public void swipe(int xOffset, int yOffset, int xMove, int yMove) {
        MobileElement mobElement = (MobileElement) getUnderlyingElement(element);

        createTouchAction().press(ElementOption.element(mobElement, xOffset, yOffset)).waitAction()
                .moveTo(ElementOption.element(mobElement, xMove, yMove)).release().perform();
    }

    /**
     * Tap with X fingers on screen
     * @param fingers   number of fingers to tap with
     * @param duration   duration in ms to wait before releasing
     */
    @ReplayOnError
    public void tap(int fingers, int duration) {
        PerformsTouchActions performTouchActions = checkForMobile();
        MobileElement mobElement = (MobileElement) getUnderlyingElement(element);

        // code from appium
        MultiTouchAction multiTouch = new MultiTouchAction(performTouchActions);

        for (int i = 0; i < fingers; i++) {
            TouchAction<?> tap = createTouchAction();
            multiTouch.add(tap.press(ElementOption.element(mobElement))
                    .waitAction(WaitOptions.waitOptions(Duration.ofMillis(duration))).release());
        }

        multiTouch.perform();
    }

    @ReplayOnError
    public void zoom() {
        PerformsTouchActions performTouchActions = checkForMobile();
        MobileElement mobElement = (MobileElement) getUnderlyingElement(element);

        MultiTouchAction multiTouch = new MultiTouchAction(performTouchActions);

        Point upperLeft = mobElement.getLocation();
        Point center = mobElement.getCenter();
        int yOffset = center.getY() - upperLeft.getY();

        TouchAction<?> action0 = createTouchAction().press(PointOption.point(center.getX(), center.getY()))
                .moveTo(ElementOption.element(mobElement, center.getX(), center.getY() - yOffset)).release();
        TouchAction<?> action1 = createTouchAction().press(PointOption.point(center.getX(), center.getY()))
                .moveTo(ElementOption.element(mobElement, center.getX(), center.getY() + yOffset)).release();
        multiTouch.add(action0).add(action1).perform();
    }

    /**
     * Wait element to present using Explicit Waits with default EXPLICIT_WAIT_TIME_OUT = 15 seconds.
     */
    public void waitForPresent() {
        waitForPresent(SeleniumTestsContextManager.getThreadContext().getExplicitWaitTimeout());
    }

    /**
     * Wait element to present using Explicit Waits with timeout in seconds. This method is used for special element
     * which needs long time to present.
     * This method is replayed because it may fail if frame is not present at start. The replay is not done if TimeOutException raises (see ReplayAction class)
     * @param timeout   timeout in seconds
     */
    @ReplayOnError
    public void waitForPresent(final int timeout) {

        // refresh driver
        driver = updateDriver();
        enterFrame();
        WebDriverWait wait = new WebDriverWait(driver, timeout);
        wait.until(ExpectedConditions.presenceOfElementLocated(by));

    }

    public FrameElement getFrameElement() {
        return frameElement;
    }

    public void setFrameElement(FrameElement frameElement) {
        this.frameElement = frameElement;
    }

    /**
     * Sometimes, the frame defined for the element may change
     * @param frameElement
     * @return
     */
    @SuppressWarnings("unchecked")
    public <T extends HtmlElement> T changeFrame(FrameElement frameElement, Class<T> type) {
        setFrameElement(frameElement);

        return (T) this;
    }

    @Override
    public <X> X getScreenshotAs(OutputType<X> target) {
        findElement();
        if (((HasCapabilities) driver).getCapabilities().getCapability(CapabilityType.TAKES_SCREENSHOT) != null) {
            return element.getScreenshotAs(target);
        } else {
            return null;
        }
    }

    @Override
    @ReplayOnError
    public void submit() {
        findElement(true);
        element.submit();
    }

    @Override
    @ReplayOnError
    public Coordinates getCoordinates() {
        findElement(false, false);
        return ((Locatable) element).getCoordinates();
    }

    public Map<String, Object> toJson() {
        findElement();
        return ((RemoteWebElement) getUnderlyingElement(element)).toJson();
    }

    public HtmlElement getParent() {
        return parent;
    }

    public void setParent(HtmlElement parent) {
        this.parent = parent;
    }

    public void setBy(By by) {
        this.by = by;
    }

    /**
     * USE ONLY for testing
     * @param element
     */
    public void setElement(WebElement element) {
        this.element = element;
    }

    public Integer getElementIndex() {
        return elementIndex;
    }

    /**
     * Returns the real web element or null if it has not been searched
     * @return
     */
    public WebElement getRealElement() {
        return element;
    }

    /**
     * Change the search index of the element (its order in element list)
     * @param elementIndex
     */
    public void setElementIndex(Integer elementIndex) {
        this.elementIndex = elementIndex;
    }

    public String getOrigin() {
        return origin;
    }

}