Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package aquality.selenium.core.elements;

import aquality.selenium.core.elements.interfaces.IElementCacheHandler;
import aquality.selenium.core.localization.ILocalizedLogger;
import aquality.selenium.core.waitings.IConditionalWait;
import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.WebElement;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeoutException;
import java.util.function.BooleanSupplier;
import java.util.function.Predicate;

/**
* Provides functions to retrive the state for cached element.
*/
public class CachedElementStateProvider extends ElementStateProvider {

private final By locator;
private final IConditionalWait conditionalWait;
private final IElementCacheHandler elementCacheHandler;
private final ILocalizedLogger localizedLogger;

public CachedElementStateProvider(By locator, IConditionalWait conditionalWait, IElementCacheHandler elementCacheHandler, ILocalizedLogger localizedLogger) {
this.locator = locator;
this.conditionalWait = conditionalWait;
this.elementCacheHandler = elementCacheHandler;
this.localizedLogger = localizedLogger;
}

protected List<Class<? extends Exception>> getHandledExceptions() {
return Arrays.asList(StaleElementReferenceException.class, NoSuchElementException.class);
}

protected boolean tryInvokeFunction(Predicate<WebElement> predicate) {
return tryInvokeFunction(predicate, getHandledExceptions());
}

protected boolean tryInvokeFunction(Predicate<WebElement> predicate, List<Class<? extends Exception>> handledExceptions) {
try {
return predicate.test(elementCacheHandler.getElement(getZeroTImeout(), ElementState.EXISTS_IN_ANY_STATE));
} catch (Exception exception) {
if (handledExceptions.contains(exception.getClass())) {
return false;
}
throw exception;
}
}

protected boolean waitForCondition(BooleanSupplier condition, String conditionName, Long timeout) {
boolean result = conditionalWait.waitFor(condition, timeout, null);
if (!result) {
String timeoutString = timeout == null ? "" : String.format("%1$s s.", timeout);
localizedLogger.warn("loc.element.not.in.state", locator, conditionName.toUpperCase(), timeoutString);
}
return result;
}

@Override
public boolean isClickable() {
return tryInvokeFunction(elementClickable().getElementStateCondition());
}

@Override
public void waitForClickable(Long timeout) {
String errorMessage = String.format("Element %1$s has not become clickable after timeout.", locator);
try {
conditionalWait.waitForTrue(this::isClickable, timeout, null, errorMessage);
} catch (TimeoutException e) {
localizedLogger.error("loc.element.not.in.state", elementClickable().getStateName(), ". ".concat(e.getMessage()));
throw new org.openqa.selenium.TimeoutException(e.getMessage(), e);
}
}

@Override
public boolean isDisplayed() {
return !elementCacheHandler.isStale() && tryInvokeFunction(WebElement::isDisplayed);
}

@Override
public boolean waitForDisplayed(Long timeout) {
return waitForCondition(() -> tryInvokeFunction(WebElement::isDisplayed), ElementState.DISPLAYED.toString(), timeout);
}

@Override
public boolean waitForNotDisplayed(Long timeout) {
return waitForCondition(() -> !isDisplayed(), "invisible or absent", timeout);
}

@Override
public boolean isExist() {
return !elementCacheHandler.isStale() && tryInvokeFunction(element -> true);
}

@Override
public boolean waitForExist(Long timeout) {
return waitForCondition(() -> tryInvokeFunction(element -> true), ElementState.EXISTS_IN_ANY_STATE.toString(), timeout);
}

@Override
public boolean waitForNotExist(Long timeout) {
return waitForCondition(() -> !isExist(), "absent", timeout);
}

@Override
public boolean isEnabled() {
return tryInvokeFunction(elementEnabled().getElementStateCondition(), Collections.singletonList(StaleElementReferenceException.class));
}

@Override
public boolean waitForEnabled(Long timeout) {
return waitForCondition(this::isEnabled, elementEnabled().getStateName(), timeout);
}

@Override
public boolean waitForNotEnabled(Long timeout) {
return waitForCondition(() -> !isEnabled(), elementNotEnabled().getStateName(), timeout);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package aquality.selenium.core.elements;

import aquality.selenium.core.elements.interfaces.IElementFinder;
import aquality.selenium.core.waitings.IConditionalWait;
import org.openqa.selenium.By;

public class DefaultElementStateProvider extends ElementStateProvider {

private final By locator;
private final IConditionalWait conditionalWait;
private final IElementFinder elementFinder;

public DefaultElementStateProvider(By locator, IConditionalWait conditionalWait, IElementFinder elementFinder) {
this.locator = locator;
this.conditionalWait = conditionalWait;
this.elementFinder = elementFinder;
}

@Override
public boolean isClickable() {
return waitForIsClickable(getZeroTImeout(), true);
}

@Override
public void waitForClickable(Long timeout) {
waitForIsClickable(timeout, false);
}

private boolean waitForIsClickable(Long timeout, boolean catchTimeoutException) {
DesiredState desiredState = elementClickable();
desiredState = catchTimeoutException ? desiredState.withCatchingTimeoutException() : desiredState;
return isElementInDesiredCondition(desiredState, timeout);
}

private boolean isElementInDesiredCondition(DesiredState elementStateCondition, Long timeout) {
return !elementFinder.findElements(locator, elementStateCondition, timeout).isEmpty();
}

@Override
public boolean isDisplayed() {
return waitForDisplayed(getZeroTImeout());
}

@Override
public boolean waitForDisplayed(Long timeout) {
return isAnyElementFound(timeout, ElementState.DISPLAYED);
}

private boolean isAnyElementFound(Long timeout, ElementState state) {
return !elementFinder.findElements(locator, state, timeout).isEmpty();
}

@Override
public boolean waitForNotDisplayed(Long timeout) {
return conditionalWait.waitFor(() -> !isDisplayed(), timeout, null);
}

@Override
public boolean isExist() {
return waitForExist(getZeroTImeout());
}

@Override
public boolean waitForExist(Long timeout) {
return isAnyElementFound(timeout, ElementState.EXISTS_IN_ANY_STATE);
}

@Override
public boolean waitForNotExist(Long timeout) {
return conditionalWait.waitFor(() -> !isExist(), timeout, null);
}

@Override
public boolean isEnabled() {
return waitForEnabled(getZeroTImeout());
}

@Override
public boolean waitForEnabled(Long timeout) {
return isElementInDesiredCondition(elementEnabled(), timeout);
}

@Override
public boolean waitForNotEnabled(Long timeout) {
return isElementInDesiredCondition(elementNotEnabled(), timeout);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package aquality.selenium.core.elements;

import aquality.selenium.core.elements.interfaces.IElementCacheHandler;
import aquality.selenium.core.elements.interfaces.IElementFinder;
import org.openqa.selenium.By;
import org.openqa.selenium.remote.RemoteWebElement;

/**
* Implementation of {@link IElementCacheHandler}.
*/
public class ElementCacheHandler implements IElementCacheHandler {

private final By locator;
private final ElementState state;
private final IElementFinder finder;

private RemoteWebElement remoteElement;

public ElementCacheHandler(By locator, ElementState state, IElementFinder finder) {
this.locator = locator;
this.state = state;
this.finder = finder;
}

@Override
public boolean isRefreshNeeded(ElementState customState) {
if (!wasCached()) {
return true;
}
try {
boolean isDisplayed = remoteElement.isDisplayed();
// refresh is needed only if the property is not match to expected element state
ElementState requiredState = customState == null ? state : customState;
return requiredState == ElementState.DISPLAYED && !isDisplayed;
} catch (Exception e) {
// refresh is needed if the property is not available
return true;
}
}

@Override
public boolean wasCached() {
return remoteElement != null;
}

@Override
public RemoteWebElement getElement(Long timeout, ElementState customState) {
ElementState requiredState = customState == null ? state : customState;
if (isRefreshNeeded(requiredState)) {
remoteElement = (RemoteWebElement) finder.findElement(locator, requiredState, timeout);
}

return remoteElement;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ protected void handleTimeoutException(TimeoutException exception, By locator, De
localizedLogger.debug("loc.elements.were.found.but.not.in.state", locator, desiredState.getStateName());
}
} else {
String combinedMessage = String.format("%1$s: %2$s", exception.getMessage(), message);
String combinedMessage = String.format("%1$s: %2$s", message, exception.getMessage());
if (desiredState.isThrowingNoSuchElementException() && !wasAnyElementFound) {
throw new NoSuchElementException(combinedMessage);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package aquality.selenium.core.elements;

import aquality.selenium.core.elements.interfaces.IElementStateProvider;
import org.openqa.selenium.WebElement;

public abstract class ElementStateProvider implements IElementStateProvider {
private static final long ZERO_TIMEOUT = 0L;

protected Long getZeroTImeout() {
return ZERO_TIMEOUT;
}

protected boolean isElementEnabled(WebElement element) {
return element.isEnabled();
}

protected DesiredState elementEnabled() {
return new DesiredState(this::isElementEnabled, "ENABLED")
.withCatchingTimeoutException()
.withThrowingNoSuchElementException();
}

protected DesiredState elementNotEnabled() {
return new DesiredState(element -> !isElementEnabled(element), "NOT ENABLED")
.withCatchingTimeoutException()
.withThrowingNoSuchElementException();
}

protected DesiredState elementClickable() {
return new DesiredState(webElement -> webElement.isDisplayed() && webElement.isEnabled(), "CLICKABLE");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package aquality.selenium.core.elements.interfaces;

import aquality.selenium.core.elements.ElementState;
import org.openqa.selenium.remote.RemoteWebElement;

/**
* Allows to use cached element.
*/
public interface IElementCacheHandler {

/**
* Determines is the cached element refresh needed.
*
* @return true if the refresh is needed (element wasn't found previously or is stale), false otherwise.
*/
default boolean isRefreshNeeded() {
return isRefreshNeeded(null);
}

/**
* Determines is the cached element refresh needed.
*
* @param customState custom element's existance state used for search.
* @return true if the refresh is needed (element wasn't found previously or is stale), false otherwise.
*/
boolean isRefreshNeeded(ElementState customState);

/**
* Determines is the element stale.
* @return true if the element was found previously and is currently stale, false otherwise.
*/
default boolean isStale() {
return wasCached() && isRefreshNeeded();
}

/**
* Determines was the element cached previously.
* @return true if the element was found and cached previously, false otherwise.
*/
boolean wasCached();

/**
* Allows to get cached element.
*
* @param timeout timeout used to retrive the element when {@see isRefreshNeeded()} is true.
* @param customState custom element's existance state used for search.
* @return cached element.
*/
RemoteWebElement getElement(Long timeout, ElementState customState);

/**
* Allows to get cached element.
*
* @param timeout timeout used to retrive the element when {@see isRefreshNeeded()} is true.
* @return cached element.
*/
default RemoteWebElement getElement(Long timeout) {
return getElement(timeout, null);
}

/**
* Allows to get cached element.
*
* @param customState custom element's existance state used for search.
* @return cached element.
*/
default RemoteWebElement getElement(ElementState customState) {
return getElement(null, customState);
}

/**
* Allows to get cached element.
*
* @return cached element.
*/
default RemoteWebElement getElement() {
return getElement(null, null);
}
}
Loading