All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.serenitybdd.core.pages.WebElementFacadeImpl Maven / Gradle / Ivy

There is a newer version: 4.2.8
Show newest version
package net.serenitybdd.core.pages;

import com.google.common.base.Splitter;
import net.serenitybdd.core.Serenity;
import net.serenitybdd.core.SystemTimeouts;
import net.serenitybdd.model.time.InternalSystemClock;
import net.thucydides.model.ThucydidesSystemProperty;
import net.thucydides.core.annotations.locators.MethodTiming;
import net.thucydides.core.annotations.locators.WithConfigurableTimeout;
import net.thucydides.model.environment.SystemEnvironmentVariables;
import net.thucydides.core.steps.StepEventBus;
import net.thucydides.model.util.EnvironmentVariables;
import net.thucydides.core.webdriver.ConfigurableTimeouts;
import net.thucydides.core.webdriver.TemporalUnitConverter;
import net.thucydides.core.webdriver.WebDriverFacade;
import net.thucydides.core.webdriver.exceptions.*;
import net.thucydides.core.webdriver.javascript.JavascriptExecutorFacade;
import net.thucydides.core.webdriver.stubs.WebElementFacadeStub;
import org.apache.commons.lang3.StringUtils;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.*;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.interactions.Coordinates;
import org.openqa.selenium.interactions.Locatable;
import org.openqa.selenium.support.pagefactory.ElementLocator;
import org.openqa.selenium.support.ui.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.Clock;
import java.time.Duration;
import java.time.temporal.TemporalUnit;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static net.serenitybdd.core.pages.ParameterisedLocator.withArguments;
import static net.serenitybdd.core.pages.WebElementExpectations.*;
import static net.serenitybdd.core.selectors.Selectors.isXPath;
import static net.thucydides.model.ThucydidesSystemProperty.LEGACY_WAIT_FOR_TEXT;
import static org.openqa.selenium.support.ui.ExpectedConditions.elementToBeClickable;


/**
 * A proxy class for a web element, providing some more methods.
 */
public class WebElementFacadeImpl implements WebElementFacade, net.thucydides.core.pages.WebElementFacade {

    public static final int NUMBER_OF_TRIES_FOR_UNREADY_ELEMENTS = 12;
    private final WebElement webElement;
    private final WebDriver driver;
    private final long implicitTimeoutInMilliseconds;
    private final long waitForTimeoutInMilliseconds;
    private static final int WAIT_FOR_ELEMENT_PAUSE_LENGTH = 100;
    private final Sleeper sleeper;
    private final Clock webdriverClock;
    private final By bySelector;
    private final JavascriptExecutorFacade javascriptExecutorFacade;
    private final InternalSystemClock clock = new InternalSystemClock();
    private final EnvironmentVariables environmentVariables;
    private String foundBy;

    private final ElementLocator locator;
    private WebElement resolvedELement;

    private static final Logger LOGGER = LoggerFactory.getLogger(WebElementFacadeImpl.class);

    public WebElementFacadeImpl(final WebDriver driver, final ElementLocator locator, final WebElement webElement, final long implicitTimeoutInMilliseconds, final long waitForTimeoutInMilliseconds, final By bySelector) {
        this.webElement = webElement;
        this.driver = driver;
        this.locator = locator;
        this.bySelector = bySelector;
        this.webdriverClock = Clock.systemDefaultZone();
        this.sleeper = Sleeper.SYSTEM_SLEEPER;
        this.javascriptExecutorFacade = new JavascriptExecutorFacade(driver);
        this.environmentVariables = SystemEnvironmentVariables.currentEnvironmentVariables();
        this.implicitTimeoutInMilliseconds = implicitTimeoutInMilliseconds;
        this.waitForTimeoutInMilliseconds = (waitForTimeoutInMilliseconds >= 0) ? waitForTimeoutInMilliseconds : defaultWaitForTimeout();
    }

    public WebElementFacadeImpl(final WebDriver driver, final WebElement webElement, final long implicitTimeoutInMilliseconds, final long waitForTimeoutInMilliseconds, final By bySelector) {
        this.webElement = webElement;
        this.driver = driver;
        this.locator = null;
        this.bySelector = bySelector;
        this.webdriverClock = Clock.systemDefaultZone();
        this.sleeper = Sleeper.SYSTEM_SLEEPER;
        this.javascriptExecutorFacade = new JavascriptExecutorFacade(driver);
        this.environmentVariables = SystemEnvironmentVariables.currentEnvironmentVariables();
        this.implicitTimeoutInMilliseconds = implicitTimeoutInMilliseconds;
        this.waitForTimeoutInMilliseconds = (waitForTimeoutInMilliseconds >= 0) ? waitForTimeoutInMilliseconds : defaultWaitForTimeout();
    }

    public WebElementFacadeImpl(final WebDriver driver, final ElementLocator locator, final WebElement webElement, final long implicitTimeoutInMilliseconds, final long waitForTimeoutInMilliseconds) {
        this.webElement = webElement;
        this.driver = driver;
        this.locator = locator;
        this.bySelector = null;
        this.webdriverClock = Clock.systemDefaultZone();
        this.sleeper = Sleeper.SYSTEM_SLEEPER;
        this.javascriptExecutorFacade = new JavascriptExecutorFacade(driver);
        this.environmentVariables = SystemEnvironmentVariables.currentEnvironmentVariables();
        this.implicitTimeoutInMilliseconds = implicitTimeoutInMilliseconds;
        this.waitForTimeoutInMilliseconds = (waitForTimeoutInMilliseconds >= 0) ? waitForTimeoutInMilliseconds : defaultWaitForTimeout();
    }

    public WebElementFacadeImpl(final WebDriver driver, final ElementLocator locator, final WebElement webElement, final long implicitTimeoutInMilliseconds) {
        this(driver, locator, webElement, implicitTimeoutInMilliseconds, implicitTimeoutInMilliseconds);
    }

    public WebElementFacadeImpl(WebDriver driver, ElementLocator locator, WebElement webElement, WebElement resolvedELement, By bySelector, long timeoutInMilliseconds, long waitForTimeoutInMilliseconds) {
        this.webElement = webElement;
        this.resolvedELement = resolvedELement;
        this.driver = driver;
        this.locator = locator;
        this.bySelector = bySelector;
        this.webdriverClock = Clock.systemDefaultZone();
        this.sleeper = Sleeper.SYSTEM_SLEEPER;
        this.javascriptExecutorFacade = new JavascriptExecutorFacade(driver);
        this.environmentVariables = SystemEnvironmentVariables.currentEnvironmentVariables();
        this.implicitTimeoutInMilliseconds = timeoutInMilliseconds;
        this.waitForTimeoutInMilliseconds = (waitForTimeoutInMilliseconds >= 0) ? waitForTimeoutInMilliseconds : defaultWaitForTimeout();

    }


    private long defaultWaitForTimeout() {
        return ThucydidesSystemProperty.WEBDRIVER_WAIT_FOR_TIMEOUT.integerFrom(environmentVariables, (int) DefaultTimeouts.DEFAULT_WAIT_FOR_TIMEOUT.toMillis());
    }

    private WebElementFacadeImpl copy() {
        return BuildWebElementFacade.from(driver, locator, webElement, implicitTimeoutInMilliseconds, waitForTimeoutInMilliseconds);
    }

    public WebElementFacadeImpl(final WebDriver driver, final ElementLocator locator, final long implicitTimeoutInMilliseconds) {
        this(driver, locator, null, implicitTimeoutInMilliseconds, implicitTimeoutInMilliseconds);
    }


    public WebElementFacadeImpl(final WebDriver driver, final ElementLocator locator, final long implicitTimeoutInMilliseconds, final long waitForTimeoutInMilliseconds) {
        this(driver, locator, null, implicitTimeoutInMilliseconds, waitForTimeoutInMilliseconds);
    }

    public static  T wrapWebElement(final WebDriver driver, final WebElement element, final long timeoutInMilliseconds, final long waitForTimeoutInMilliseconds) {
        return BuildWebElementFacade.from(driver, element, timeoutInMilliseconds, waitForTimeoutInMilliseconds);
    }

    public static  T wrapWebElement(final WebDriver driver, final WebElement element, final long timeoutInMilliseconds, final long waitForTimeoutInMilliseconds, final String foundBy) {
        return BuildWebElementFacade.from(driver, element, timeoutInMilliseconds, waitForTimeoutInMilliseconds, foundBy);
    }

    public static  T wrapWebElement(WebDriver driver, WebElement resolvedELement, WebElement element, By bySelector, ElementLocator locator, long timeoutInMilliseconds, long waitForTimeoutInMilliseconds, String foundBy) {
        return BuildWebElementFacade.from(driver, resolvedELement, element, bySelector, locator, timeoutInMilliseconds, waitForTimeoutInMilliseconds, foundBy);
    }

    public static  T wrapWebElement(final WebDriver driver, final By bySelector, final long timeoutInMilliseconds, final long waitForTimeoutInMilliseconds, final String foundBy) {
        return BuildWebElementFacade.from(driver, bySelector, timeoutInMilliseconds, waitForTimeoutInMilliseconds, foundBy);
    }

    public static WebElementFacade wrapWebElement(final WebDriver driver, final WebElement element, final long timeout) {
        return BuildWebElementFacade.from(driver, element, timeout);
    }

    public static WebElementFacade wrapWebElement(final WebDriver driver, final WebElement element) {
        return wrapWebElement(driver, element, SystemTimeouts.forTheCurrentTest().getImplicitTimeout());
    }

    private WebElementResolver getElementResolver() {
        if (webElement != null) {
            return WebElementResolver.forWebElement(webElement);
        }
        if (bySelector != null) {
            return WebElementResolver.by(bySelector);
        }
        return WebElementResolver.byLocator(locator).withImplicitTimeout(implicitTimeoutInMilliseconds);
    }

    public WebElement getElement() {
        if (driverIsDisabled()) {
            return new WebElementFacadeStub();
        }
        return getResolvedElement();
    }

    private WebElement getResolvedElement() {
        if (resolvedELement == null) {
            resolvedELement = getElementResolver().resolveForDriver(driver);
        }

        return resolvedELement;

    }

    protected JavascriptExecutorFacade getJavascriptExecutorFacade() {
        return javascriptExecutorFacade;
    }

    protected InternalSystemClock getClock() {
        return clock;
    }


    @Override
    public WebElementFacade then(String xpathOrCssSelector, Object... arguments) {
        return findBy(xpathOrCssSelector, arguments);
    }

    @Override
    public WebElementFacade thenFind(String xpathOrCssSelector, Object... arguments) {
        return findBy(xpathOrCssSelector, arguments);
    }

    @Override
    public WebElementFacade thenFind(String xpathOrCssSelector) {
        return findBy(xpathOrCssSelector);
    }

    @Override
    public WebElementFacade then(String xpathOrCssSelector) {
        return findBy(xpathOrCssSelector);
    }

    @Override
    public  T findBy(String xpathOrCssSelector) {
        logIfVerbose("findBy " + xpathOrCssSelector);
        WebElement nestedElement;
        if (driverIsDisabled()) {
            nestedElement = this;
        } else if (isXPath(xpathOrCssSelector)) {
            nestedElement = getElement().findElement((By.xpath(xpathOrCssSelector)));
        } else {
            nestedElement = getElement().findElement((By.cssSelector(xpathOrCssSelector)));
        }

        return wrapWebElement(driver, nestedElement, timeoutInMilliseconds(), waitForTimeoutInMilliseconds, "element located by " + xpathOrCssSelector);
    }

    public  T findBy(String xpathOrCssSelector, Object... arguments) {
        return findBy(withArguments(xpathOrCssSelector, arguments));
    }


    public long timeoutInMilliseconds() {
        if (driver instanceof WebDriverFacade) {
            return ((WebDriverFacade) driver).getCurrentImplicitTimeout().toMillis();
        } else {
            return implicitTimeoutInMilliseconds;
        }

    }

    @Override
    public ListOfWebElementFacades thenFindAll(String xpathOrCssSelector) {
        logIfVerbose("findAll " + xpathOrCssSelector);
        if (driverIsDisabled()) {
            return new ListOfWebElementFacades(new ArrayList<>());
        }

        List nestedElements;
        if (isXPath(xpathOrCssSelector)) {
            nestedElements = findElements((By.xpath(xpathOrCssSelector)));
        } else {
            nestedElements = findElements((By.cssSelector(xpathOrCssSelector)));
        }

        return webElementFacadesFrom(nestedElements);
    }

    public ListOfWebElementFacades thenFindAll(String xpathOrCssSelector, Object... arguments) {
        return thenFindAll(withArguments(xpathOrCssSelector, arguments));
    }

    private ListOfWebElementFacades webElementFacadesFrom(List nestedElements) {
        List results = new ArrayList<>();
        for (WebElement element : nestedElements) {
            results.add(wrapWebElement(driver, element, timeoutInMilliseconds(), waitForTimeoutInMilliseconds, element.toString()));
        }
        return new ListOfWebElementFacades(results);
    }

    @Override
    public WebElementFacade findBy(By selector) {
        logIfVerbose("findBy " + selector);

        if (driverIsDisabled()) {
            return this;
        }

        WebElement nestedElement = getElement().findElement(selector);
        return wrapWebElement(driver, nestedElement, timeoutInMilliseconds(), waitForTimeoutInMilliseconds, "element located by " + selector.toString());
    }

    @Override
    public WebElementFacade find(By bySelector) {
        return findBy(bySelector);
    }

    @Override
    public WebElementFacade then(By bySelector) {
        return findBy(bySelector);
    }

    @Override
    public String getAttribute(String name) {
        return getElement().getAttribute(name);
    }

    @Override
    public ListOfWebElementFacades thenFindAll(By... selectors) {
        logIfVerbose("findAll " + selectors);
        if (driverIsDisabled()) {
            return new ListOfWebElementFacades(new ArrayList<>());
        }

        List nestedElements = new ArrayList<>();
        for (By selector : selectors) {
            nestedElements = findElements(selector);
            if (!nestedElements.isEmpty()) {
                break;
            }
        }
        return webElementFacadesFrom(nestedElements);
    }


    @Override
    public long getImplicitTimeoutInMilliseconds() {
        return implicitTimeoutInMilliseconds;
    }

    public Duration getImplicitTimeout() {
        return ((ConfigurableTimeouts) driver).getCurrentImplicitTimeout();
    }


    @Override
    public WebElementFacade withTimeoutOf(int timeout, TimeUnit unit) {
        return withTimeoutOf(timeout, TemporalUnitConverter.fromTimeUnit(unit));
    }

    public WebElementFacade withTimeoutOf(int timeout, TemporalUnit unit) {
        return withTimeoutOf(Duration.of(timeout, unit));
    }

    public WebElementFacade withTimeoutOf(Duration duration) {
        return wrapWebElement(driver, resolvedELement, webElement, bySelector, locator, duration.toMillis(), duration.toMillis(), foundBy);
    }

    /**
     * Is this web element present and visible on the screen
     * This method will not throw an exception if the element is not on the screen at all.
     * If the element is not visible, the method will wait a bit to see if it appears later on.
     */
    @Override
    public boolean isVisible() {

        if (driverIsDisabled()) {
            return false;
        }

        try {
            WebElement element = getElement();

            if (element == null) {
                return false;
            }

            if (shouldWaitForResult()) {
                return waitForCondition().until(ExpectedConditions.visibilityOf(element)).isDisplayed();
            } else {
                return element.isDisplayed();
            }

        } catch (ElementNotInteractableException | NoSuchElementException | StaleElementReferenceException |
                 TimeoutException e) {
            return false;
        }
    }

    private boolean shouldWaitForResult() {
        return !MethodTiming.forThisThread().isInQuickMethod();
    }

    /**
     * Convenience method to chain method calls more fluently.
     */
    @Override
    public WebElementFacade and() {
        return this;
    }

    /**
     * Convenience method to chain method calls more fluently.
     */
    @Override
    public WebElementFacade then() {
        return this;
    }

    /**
     * Is this web element present and visible on the screen
     * This method will not throw an exception if the element is not on the screen at all.
     * The method will fail immediately if the element is not visible on the screen.
     * There is a little black magic going on here - the web element class will detect if it is being called
     * by a method called "isCurrently*" and, if so, fail immediately without waiting as it would normally do.
     */
    @Override
    public boolean isCurrentlyVisible() {
        return isVisible();
    }

    @Override
    public boolean isCurrentlyEnabled() {
        if (driverIsDisabled()) {
            return false;
        }

        try {
            return (getElement() != null) && getElement().isEnabled();
        } catch (NoSuchElementException | StaleElementReferenceException e) {
            return false;
        }
    }

    /**
     * Checks whether a web element is visible.
     * Throws an AssertionError if the element is not rendered.
     */
    @Override
    public WebElementState shouldBeVisible() {
        if (!isVisible()) {
            failWithMessage("Element should be visible");
        }
        return this;
    }

    /**
     * Checks whether a web element is visible.
     * Throws an AssertionError if the element is not rendered.
     */
    @Override
    public WebElementState shouldBeCurrentlyVisible() {
        if (!isCurrentlyVisible()) {
            failWithMessage("Element should be visible");
        }
        return this;
    }

    /**
     * Checks whether a web element is not visible.
     * Throws an AssertionError if the element is not rendered.
     */
    @Override
    public WebElementState shouldNotBeVisible() {
        if (isCurrentlyVisible()) {
            failWithMessage("Element should not be visible");
        }
        return this;
    }

    /**
     * Checks whether a web element is not visible straight away.
     * Throws an AssertionError if the element is not rendered.
     */
    @Override
    public WebElementState shouldNotBeCurrentlyVisible() {
        if (isCurrentlyVisible()) {
            failWithMessage("Element should not be visible");
        }
        return this;
    }

    /**
     * Does this element currently have the focus.
     */
    @Override
    public boolean hasFocus() {
        if (driverIsDisabled()) {
            return false;
        }

        JavascriptExecutorFacade js = new JavascriptExecutorFacade(driver);
        WebElement activeElement = (WebElement) js.executeScript("return window.document.activeElement");
        return getElement().equals(activeElement);
    }

    /**
     * Does this element contain a given text?
     */
    @Override
    public boolean containsText(final String value) {
        if (driverIsDisabled()) {
            return false;
        }

        WebElement element = getElement();
        return ((element != null) && (element.getText().contains(value)));
    }

    @Override
    public boolean containsValue(String value) {
        if (driverIsDisabled()) {
            return false;
        }

        WebElement element = getElement();
        return ((element != null) && (element.getAttribute("value").contains(value)));
    }


    /**
     * Does this element exactly match  given text?
     */
    @Override
    public boolean containsOnlyText(final String value) {
        if (driverIsDisabled()) {
            return false;
        }

        WebElement element = getElement();

        if (element == null) return false;

        String text = element.getText();
        if (text.isEmpty()) {
            // https://github.com/serenity-bdd/serenity-core/issues/2134
            // maybe it's an input element?
            text = element.getAttribute("value");
        }
        return value.equals(text);
    }

    /**
     * Does this dropdown contain the specified value.
     */
    @Override
    public boolean containsSelectOption(final String value) {
        if (driverIsDisabled()) {
            return false;
        }

        return getSelectOptions().contains(value);
    }

    @Override
    public List getSelectOptions() {
        if (driverIsDisabled()) {
            return new ArrayList<>();
        }

        if (getElement() != null) {
            return findElements(By.tagName("option")).stream().map(WebElement::getText).collect(Collectors.toList());
        }
        return Collections.emptyList();
    }

    @Override
    public List getSelectOptionValues() {
        if (driverIsDisabled()) {
            return new ArrayList<>();
        }

        if (getElement() != null) {
            return findElements(By.tagName("option")).stream().map(elt -> elt.getAttribute("value")).collect(Collectors.toList());
        }
        return Collections.emptyList();
    }

    public ElementLocator getLocator() {
        if ((locator instanceof WithConfigurableTimeout) && (driver instanceof ConfigurableTimeouts)) {
            ((WithConfigurableTimeout) locator).setTimeOutInSeconds((int) getLocatorTimeout());
        }
        return locator;
    }

    private long getLocatorTimeout() {
        if (StepEventBus.getParallelEventBus().webdriverCallsAreSuspended() || (MethodTiming.forThisThread().isInQuickMethod())) {
            return 0;
        } else {
            return TimeUnit.SECONDS.convert(implicitTimeoutInMilliseconds, TimeUnit.MILLISECONDS);
        }
    }

    @Override
    public void setImplicitTimeout(Duration implicitTimeout) {
        if (driverIsDisabled()) {
            return;
        }

        if (driver instanceof ConfigurableTimeouts) {
            ((ConfigurableTimeouts) driver).setImplicitTimeout(implicitTimeout);
        }
    }

    @Override
    public Duration getCurrentImplicitTimeout() {
        if (driverIsDisabled()) {
            return Duration.ofSeconds(0);
        }

        if (driver instanceof ConfigurableTimeouts) {
            return ((ConfigurableTimeouts) driver).getCurrentImplicitTimeout();
        }
        return DefaultTimeouts.DEFAULT_IMPLICIT_WAIT_TIMEOUT;
    }

    @Override
    public Duration resetTimeouts() {
        if (driverIsDisabled()) {
            return Duration.ofSeconds(0);
        }
        if (driver instanceof ConfigurableTimeouts) {
            return ((ConfigurableTimeouts) driver).resetTimeouts();
        }
        return DefaultTimeouts.DEFAULT_IMPLICIT_WAIT_TIMEOUT;
    }

    public String getFoundBy() {
        return foundBy;
    }


    /**
     * Check that an element contains a text value
     *
     * @param textValue
     */
    @Override
    public WebElementState shouldContainText(String textValue) {
        if (!containsText(textValue)) {
            String errorMessage = String.format("The text '%s' was not found in the web element. Element text '%s'.", textValue, getElement().getText());
            failWithMessage(errorMessage);
        }
        return this;
    }

    /**
     * Check that an element exactly matches a text value
     *
     * @param textValue
     */
    @Override
    public WebElementState shouldContainOnlyText(String textValue) {
        if (!containsOnlyText(textValue)) {
            String errorMessage = String.format("The text '%s' does not match the elements text '%s'.", textValue, getElement().getText());
            failWithMessage(errorMessage);
        }
        return this;
    }

    @Override
    public WebElementState shouldContainSelectedOption(String textValue) {
        if (!containsSelectOption(textValue)) {
            String errorMessage = String.format("The list element '%s' was not found in the web element", textValue);
            failWithMessage(errorMessage);
        }
        return this;
    }

    /**
     * Check that an element does not contain a text value
     *
     * @param textValue
     */
    @Override
    public WebElementState shouldNotContainText(String textValue) {
        if (containsText(textValue)) {
            String errorMessage = String.format("The text '%s' was found in the web element when it should not have. Element text '%s'.", textValue, getElement().getText());
            failWithMessage(errorMessage);
        }
        return this;
    }

    @Override
    public WebElementState shouldBeEnabled() {
        if (!isCurrentlyEnabled()) {
            String errorMessage = String.format("Field '%s' should be enabled", this);
            failWithMessage(errorMessage);
        }
        return this;
    }

    @Override
    public boolean isEnabled() {
        if (driverIsDisabled()) {
            return false;
        }
        if (getElement() == null) {
            return false;
        }

        if (shouldWaitForResult()) {
            try {
                waitForCondition().until(webDriver -> getElement().isEnabled());
            } catch (TimeoutException timeout) {
                return false;
            }
        }

        return getElement().isEnabled();
    }


    @Override
    public WebElementState shouldNotBeEnabled() {

        if (isCurrentlyEnabled()) {
            String errorMessage = String.format("Field '%s' should not be enabled", this);
            failWithMessage(errorMessage);
        }
        return this;
    }

    /**
     * Check to see if the element is clickable
     */
    public boolean isClickable() {
        try {
            if (!driverIsDisabled() && shouldWaitForResult() && getElement() != null) {
                waitForCondition().until(elementToBeClickable(getElement()));
                return true;
            }
        } catch (ElementNotInteractableException | NoSuchElementException | StaleElementReferenceException |
                 TimeoutException e) {
            return false;
        }
        return false;
    }

    /**
     * Type a value into a field, making sure that the field is empty first.
     *
     * @param keysToSend
     */
    @Override
    public WebElementFacade type(final CharSequence... keysToSend) {
        final CharSequence[] keyValue = nonNullCharSequenceFrom(keysToSend);
        logIfVerbose("Type '" + keysToSend + "'");

        if (driverIsDisabled()) {
            return this;
        }
        WithRetries.on(this).perform(elementFacade -> {
            elementFacade.getElement().clear();
            elementFacade.getElement().sendKeys(nonNullCharSequenceFrom(keyValue));
        }, 12);
        notifyScreenChange();
        return this;
    }

    private CharSequence[] nonNullCharSequenceFrom(CharSequence... charSequences) {
        return Arrays.stream(charSequences).filter(chars -> chars != null).collect(Collectors.toList()).toArray(new CharSequence[]{});
    }

    /**
     * Type a value into a field and then press Enter, making sure that the field is empty first.
     *
     * @param value
     */
    @Override
    public WebElementFacade typeAndEnter(final String value) {
        logIfVerbose("Type and enter '" + value + "'");

        if (driverIsDisabled()) {
            return this;
        }

        WithRetries.on(this).perform(elementFacade -> {
            elementFacade.getElement().clear();
            elementFacade.getElement().sendKeys(nonNullCharSequenceFrom(value, Keys.ENTER));
        }, NUMBER_OF_TRIES_FOR_UNREADY_ELEMENTS);
        notifyScreenChange();

        return this;
    }

    /**
     * Type a value into a field and then press TAB, making sure that the field is empty first.
     * This currently is not supported by all browsers, notably Firefox.
     *
     * @param value
     */
    @Override
    public WebElementFacade typeAndTab(final String value) {
        logIfVerbose("Type and tab '" + value + "'");

        if (driverIsDisabled()) {
            return this;
        }

        WithRetries.on(this).perform(elementFacade -> {
            elementFacade.getElement().clear();
            elementFacade.getElement().sendKeys(value);
            elementFacade.getElement().sendKeys(Keys.TAB);
        }, NUMBER_OF_TRIES_FOR_UNREADY_ELEMENTS);

        notifyScreenChange();
        return this;
    }

    @Override
    public void setWindowFocus() {
        if (driverIsDisabled()) {
            return;
        }

        getJavascriptExecutorFacade().executeScript("window.focus()");
    }


    @Override
    public FluentDropdownSelect select() {
        return new FluentDropdownSelect(this);
    }

    @Override
    public FluentDropdownDeselect deselect() {
        return new FluentDropdownDeselect(this);
    }

    private DropdownSelector dropdownSelect() {
        return new DropdownSelector(this);
    }

    private DropdownDeselector dropdownDeselect() {
        return new DropdownDeselector(this);
    }

    @Override
    public WebElementFacade deselectAll() {
        return dropdownDeselect().all();
    }

    @Override
    public WebElementFacade deselectByIndex(int indexValue) {
        return dropdownDeselect().byIndex(indexValue);
    }

    @Override
    public WebElementFacade deselectByVisibleText(String label) {
        return dropdownDeselect().byVisibleText(label);
    }

    @Override
    public WebElementFacade deselectByValue(String value) {
        return dropdownDeselect().byValue(value);
    }

    @Override
    public WebElementFacade selectByVisibleText(final String label) {
        return dropdownSelect().byVisibleText(label);
    }

    @Override
    public String getSelectedVisibleTextValue() {
        return dropdownSelect().visibleTextValue();
    }

    @Override
    public String getFirstSelectedOptionVisibleText() {
        return new Select(this).getFirstSelectedOption().getText();
    }

    @Override
    public List getSelectedVisibleTexts() {
        return dropdownSelect().visibleTextValues();
    }

    @Override
    public String getFirstSelectedOptionValue() {
        return new Select(this).getFirstSelectedOption().getAttribute("value");
    }

    @Override
    @Deprecated
    public WebElementFacade selectByValue(String value) {
        return dropdownSelect().byValue(value);
    }

    @Override
    public String getSelectedValue() {
        return dropdownSelect().value();
    }

    @Override
    public List getSelectedValues() {
        return dropdownSelect().values();
    }

    @Override
    @Deprecated
    public WebElementFacade selectByIndex(int indexValue) {
        return dropdownSelect().byIndex(indexValue);
    }

    public  X getScreenshotAs(OutputType target) throws WebDriverException {
        if (driverIsDisabled()) {
            return null;
        }
        return getElement().getScreenshotAs(target);
    }

    protected void waitUntilElementAvailable() {
        if (driverIsDisabled()) {
            return;
        }
        withTimeoutOf((int) waitForTimeoutInMilliseconds, TimeUnit.MILLISECONDS).waitUntilEnabled();
    }

    protected void waitUntilElementPresent() {
        if (driverIsDisabled()) {
            return;
        }
        withTimeoutOf((int) waitForTimeoutInMilliseconds, TimeUnit.MILLISECONDS).waitUntilPresent();
    }

    protected boolean driverIsDisabled() {
        return StepEventBus.getParallelEventBus().webdriverCallsAreSuspended();
    }

    private boolean elementIsPresent() {
        try {
            WebElement element = getElement();

            if (getElement() == null) {
                return false;
            }
            element.isDisplayed();
            return true;
        } catch (ElementNotInteractableException e) {
            return true;
        } catch (NotFoundException | ElementNotFoundAfterTimeoutError e) {
            return false;
        }
    }

    /**
     * Returns true if an element is present on the screen, whether visible or not.
     */
    public boolean isPresent() {
        if (driverIsDisabled()) {
            return false;
        }
        return elementIsPresent();
    }


    @Override
    public WebElementState shouldBePresent() {
        if (!isPresent()) {
            failWithMessage("Field should be present");
        }
        return this;
    }

    @Override
    public WebElementState shouldNotBePresent() {
        if (isPresent()) {
            failWithMessage("Field should not be present");
        }
        return this;
    }

    @Override
    public WebElementState shouldBeSelected() {
        if (!isSelected()) {
            failWithMessage("Field should be selected");
        }
        return this;
    }

    @Override
    public WebElementState shouldNotBeSelected() {
        if (isSelected()) {
            failWithMessage("Field should not be selected");
        }
        return this;
    }

    private void failWithMessage(String errorMessage) {
        throw new AssertionError(getErrorMessage(errorMessage));
    }

    private void checkPresenceOfWebElement() {
        try {
            if (!driverIsDisabled() && shouldWaitForResult()) {
                waitForCondition().until(elementIsDisplayed(this));
            }
        } catch (Throwable error) {
            if (webElement != null) {
                throwShouldBeVisibleErrorWithCauseIfPresent(error, error.getMessage());
            } else {
                throwNoSuchElementExceptionWithCauseIfPresent(error, error.getMessage());
            }
        }
    }

    @Override
    public WebElementFacade waitUntilVisible() {
        checkPresenceOfWebElement();
        return this;
    }

    @Override
    public WebElementFacade waitUntilPresent() {
        try {
            if (!driverIsDisabled()) {
                waitForCondition().until(WebElementExpectations.elementIsPresent(this));
            }
        } catch (TimeoutException timeout) {
            throwShouldBePresentErrorWithCauseIfPresent(timeout, timeout.getMessage());
        }
        return this;
    }


    private void throwNoSuchElementExceptionWithCauseIfPresent(final Throwable timeout, final String defaultMessage) {
        String timeoutMessage = (timeout.getCause() != null) ? timeout.getCause().getMessage() : timeout.getMessage();
        String finalMessage = (StringUtils.isNotEmpty(timeoutMessage)) ? timeoutMessage : defaultMessage;
        throw new NoSuchElementException(finalMessage, timeout);
    }

    private void throwShouldBeVisibleErrorWithCauseIfPresent(final Throwable timeout, final String defaultMessage) {
        String timeoutMessage = (timeout.getCause() != null) ? timeout.getCause().getMessage() : timeout.getMessage();
        String finalMessage = (StringUtils.isNotEmpty(timeoutMessage)) ? timeoutMessage : defaultMessage;
        throw new ElementShouldBeVisibleException(finalMessage, timeout);
    }

    private void throwShouldBeInvisibleErrorWithCauseIfPresent(final Throwable timeout, final String defaultMessage) {
        String timeoutMessage = (timeout.getCause() != null) ? timeout.getCause().getMessage() : timeout.getMessage();
        String finalMessage = (StringUtils.isNotEmpty(timeoutMessage)) ? timeoutMessage : defaultMessage;
        throw new ElementShouldBeInvisibleException(finalMessage, timeout);
    }

    private void throwShouldBePresentErrorWithCauseIfPresent(final Throwable timeout, final String defaultMessage) {
        String timeoutMessage = (timeout.getCause() != null) ? timeout.getCause().getMessage() : timeout.getMessage();
        String finalMessage = (StringUtils.isNotEmpty(timeoutMessage)) ? timeoutMessage : defaultMessage;
        throw new ElementShouldBePresentException(finalMessage, timeout);
    }

    private boolean hasValueAttribute(WebElement element) {
        try {
            return element.getAttribute("value") != null;
        } catch (UnsupportedCommandException exception) {
            return false;
        }
    }

    @Override
    public Wait waitForCondition() {
        return new FluentWait<>(driver, webdriverClock, sleeper)
                .withTimeout(Duration.ofMillis(waitForTimeoutInMilliseconds))
                .pollingEvery(Duration.ofMillis(WAIT_FOR_ELEMENT_PAUSE_LENGTH))
                .ignoreAll(Arrays.asList(NoSuchElementException.class,
                                         NoSuchFrameException.class,
                                         StaleElementReferenceException.class));
    }

    @Override
    public WebElementFacade waitUntilNotVisible() {
        if (driverIsDisabled()) {
            return this;
        }

        if (!isCurrentlyVisible()) {
            return this;
        }

        try {
            waitForCondition().until(elementIsNotDisplayed(this));
        } catch (TimeoutException timeout) {
            throwShouldBeInvisibleErrorWithCauseIfPresent(timeout, "Expected hidden element was displayed");
        }
        return this;
    }

    @Override
    public String getValue() {
        checkPresenceOfWebElement();
        return getElement().getAttribute("value");
    }

    @Override
    public boolean isSelected() {
        return getElement().isSelected();
    }

    @Override
    public String getText() {
        if (driverIsDisabled()) {
            return "";
        }

        if (LEGACY_WAIT_FOR_TEXT.booleanFrom(environmentVariables, false)) {
            checkPresenceOfWebElement();
        }
        return getElement().getText();
    }

    @Override
    public String getTextContent() {
        if (driverIsDisabled()) {
            return "";
        }

        return getElement().getAttribute("textContent");
    }

    @Override
    public String getAriaLabel() {
        return getAttribute("aria-label");
    }

    @Override
    public String getRole() {
        return getAttribute("role");
    }

    @Override
    public boolean isDisabled() {
        if (driverIsDisabled()) {
            return true;
        }
        return !getElement().isEnabled();
    }

    private boolean driverIsActive() {
        return ((driver != null) && (!driverIsDisabled()));
    }

    @Override
    public WebElementFacade waitUntilEnabled() {
        try {
            if (driverIsActive()) {
                waitForCondition().until(elementIsEnabled(this));
            }
        } catch (TimeoutException timeout) {
            throw new ElementShouldBeEnabledException("Expected enabled element was not enabled", timeout);
        }
        return this;
    }

    @Override
    public WebElementFacade waitUntilClickable() {
        try {
            if (driverIsActive()) {
                waitForCondition().until(elementIsClickable(this));
            }
        } catch (TimeoutException timeout) {
            throw new ElementShouldBeEnabledException("Expected enabled element was not enabled", timeout);
        }
        return this;
    }

    @Override
    public WebElementFacade waitUntilDisabled() {
        try {
            if (driverIsActive()) {
                waitForCondition().until(elementIsNotEnabled(this));
            }
        } catch (TimeoutException timeout) {
            throw new ElementShouldBeDisabledException("Expected disabled element was not disabled", timeout);
        }
        return this;
    }

    @Override
    public String getTextValue() {
        if (driverIsDisabled()) {
            return "";
        }

        waitUntilPresent();

        if (!isVisible()) {
            return "";
        }

        if (valueAttributeSupportedAndDefinedIn(getElement())) {
            return getValue();
        }

        if (!StringUtils.isEmpty(getElement().getText())) {
            return getElement().getText();
        }
        return "";
    }

    @Override
    public WebElementState expect(String errorMessage) {
        return copy().expectingErrorMessage(errorMessage);
    }

    private Optional expectedErrorMessage = Optional.empty();

    protected WebElementState expectingErrorMessage(String errorMessage) {
        this.expectedErrorMessage = Optional.of(errorMessage);
        return this;
    }

    protected String getErrorMessage(String defaultErrorMessage) {
        return expectedErrorMessage.orElse(defaultErrorMessage);
    }

    private boolean valueAttributeSupportedAndDefinedIn(final WebElement element) {
        return hasValueAttribute(element) && StringUtils.isNotEmpty(getValue());
    }

    /**
     * Click on an element, with or without waiting for it to be visible and enabled
     */
    @Override
    public void click(ClickStrategy clickStrategy) {
        if (driverIsDisabled()) {
            return;
        }

        switch (clickStrategy) {
            case WAIT_UNTIL_ENABLED:
                waitUntilElementAvailable();
                break;
            case WAIT_UNTIL_PRESENT:
                waitUntilElementPresent();
                break;
            case IMMEDIATE:
                break;
        }

        WithRetries.on(this).perform(elementFacade -> elementFacade.getElement().click(), 12);

        logClick();
        notifyScreenChange();
    }

    /**
     * Wait for an element to be visible and enabled, and then click on it.
     */
    @Override
    public void click() {
        click(ClickStrategy.WAIT_UNTIL_ENABLED);
    }

    public void doubleClick() {

        Actions actions = new Actions(driver);
        WithRetries.on(this).perform(actions::doubleClick, 12);
    }

    public void contextClick() {
        Actions actions = new Actions(driver);
        WithRetries.on(this).perform(actions::contextClick, 12);
    }

    private void logClick() {
        logIfVerbose("click");
    }

    private void logIfVerbose(String logMessage) {
        if (useVerboseLogging()) {
            LOGGER.debug(logMessage + " : " + this);
        }
    }

    private boolean useVerboseLogging() {
        return ThucydidesSystemProperty.SERENITY_VERBOSE_STEPS.booleanFrom(environmentVariables);
    }

    @Override
    public void clear() {

        if (driverIsDisabled()) {
            return;
        }
        WithRetries.on(this).perform(elementFacade -> elementFacade.getElement().clear(), 12);
    }

    protected void notifyScreenChange() {
        StepEventBus.getParallelEventBus().notifyScreenChange();
    }

    @Override
    public String toString() {

        if (webElement != null && !driverIsDisabled()) {
            return webElement.toString();
        }
        if (foundBy != null) {
            return foundBy;
        }
        if (bySelector != null) {
            return bySelector.toString();
        }
        if (locator != null) {
            return locator.toString();
        }
        return "";
    }

    public void submit() {
        getElement().submit();
    }

    public void sendKeys(CharSequence... keysToSend) {
        if (!allElementsAreNull(keysToSend)) {
            WithRetries.on(this).perform(elementFacade -> {
                elementFacade.getElement().sendKeys(nonNullCharSequenceFrom(keysToSend));
            }, 12);
        }
    }

    private boolean allElementsAreNull(CharSequence[] keysToSend) {
        return Arrays.stream(keysToSend).allMatch(Objects::isNull);
    }

    public String getTagName() {
        return getElement().getTagName();
    }

    public List findElements(By by) {
        return getElement().findElements(by);
    }

    public WebElement findElement(By by) {
        return getElement().findElement(by);
    }

    /**
     * Is this element displayed or not? This method avoids the problem of having to parse an
     * element's "style" attribute.
     * This method respects the semantics of the Selenium isDisplayed() method, and will throw an
     * exception if the element is not found. To simply return true or false depending on whether an element
     * is
     *
     * @return Whether or not the element is displayed
     */
    public boolean isDisplayed() {
        return getElement().isDisplayed();
    }

    public Point getLocation() {
        return getElement().getLocation();
    }

    public Dimension getSize() {
        return getElement().getSize();
    }

    @Override
    public Rectangle getRect() {
        return getElement().getRect();
    }

    public String getCssValue(String propertyName) {
        return getElement().getCssValue(propertyName);
    }

    @Override
    public boolean containsElements(By bySelector) {
        return !findElements(bySelector).isEmpty();
    }

    @Override
    public boolean containsElements(String xpathOrCssSelector) {
        return !thenFindAll(xpathOrCssSelector).isEmpty();
    }

    @Override
    public WebElementState shouldContainElements(By bySelector) {
        if (!containsElements(bySelector)) {
            String errorMessage = String.format("Could not find contained elements %s in %s", bySelector, getElement().toString());
            failWithMessage(errorMessage);
        }
        return this;
    }

    @Override
    public WebElementState shouldContainElements(String xpathOrCssSelector) {
        if (!containsElements(xpathOrCssSelector)) {
            String errorMessage = String.format("Could not find contained elements %s in %s", xpathOrCssSelector, getElement().toString());
            failWithMessage(errorMessage);
        }
        return this;
    }

    @Override
    public boolean hasClass(String cssClassName) {
        String cssClassValue = getAttribute("class").toLowerCase();
        List cssClasses = Splitter.on(" ").omitEmptyStrings().trimResults().splitToList(cssClassValue);
        return cssClasses.contains(cssClassName.toLowerCase());
    }

    public WebElementFacade foundBy(String foundBy) {
        this.foundBy = foundBy;
        return this;
    }

    @Override
    public Coordinates getCoordinates() {
        return ((Locatable) getElement()).getCoordinates();
    }

    @Override
    public WebElement getWrappedElement() {
        return getResolvedElement();
    }

    @Override
    public String getDomProperty(String name) {
        return getResolvedElement().getDomProperty(name);
    }

    @Override
    public String getDomAttribute(String name) {
        return getResolvedElement().getDomAttribute(name);
    }

    @Override
    public String getAriaRole() {
        return getResolvedElement().getAriaRole();
    }

    @Override
    public String getAccessibleName() {
        return getResolvedElement().getAccessibleName();
    }

    @Override
    public SearchContext getShadowRoot() {
        return getResolvedElement().getShadowRoot();
    }

    @Override
    public ListOfWebElementFacades findNestedElementsMatching(ResolvableElement nestedElement) {
        return nestedElement.resolveAllFor(this);
    }

    public static ListOfWebElementFacades fromWebElements(List elements) {
        List facades = elements.stream().map(element -> WebElementFacadeImpl.wrapWebElement(Serenity.getDriver(), element)).collect(Collectors.toList());
        return new ListOfWebElementFacades(facades);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy