diff --git a/src/main/java/aquality/selenium/core/elements/CachedElementStateProvider.java b/src/main/java/aquality/selenium/core/elements/CachedElementStateProvider.java new file mode 100644 index 0000000..db0c56c --- /dev/null +++ b/src/main/java/aquality/selenium/core/elements/CachedElementStateProvider.java @@ -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> getHandledExceptions() { + return Arrays.asList(StaleElementReferenceException.class, NoSuchElementException.class); + } + + protected boolean tryInvokeFunction(Predicate predicate) { + return tryInvokeFunction(predicate, getHandledExceptions()); + } + + protected boolean tryInvokeFunction(Predicate predicate, List> 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); + } +} diff --git a/src/main/java/aquality/selenium/core/elements/DefaultElementStateProvider.java b/src/main/java/aquality/selenium/core/elements/DefaultElementStateProvider.java new file mode 100644 index 0000000..f0cc8f6 --- /dev/null +++ b/src/main/java/aquality/selenium/core/elements/DefaultElementStateProvider.java @@ -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); + } +} diff --git a/src/main/java/aquality/selenium/core/elements/ElementCacheHandler.java b/src/main/java/aquality/selenium/core/elements/ElementCacheHandler.java new file mode 100644 index 0000000..caadf0b --- /dev/null +++ b/src/main/java/aquality/selenium/core/elements/ElementCacheHandler.java @@ -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; + } +} diff --git a/src/main/java/aquality/selenium/core/elements/ElementFinder.java b/src/main/java/aquality/selenium/core/elements/ElementFinder.java index 73f7455..89455d0 100644 --- a/src/main/java/aquality/selenium/core/elements/ElementFinder.java +++ b/src/main/java/aquality/selenium/core/elements/ElementFinder.java @@ -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); } diff --git a/src/main/java/aquality/selenium/core/elements/ElementStateProvider.java b/src/main/java/aquality/selenium/core/elements/ElementStateProvider.java new file mode 100644 index 0000000..d0976fe --- /dev/null +++ b/src/main/java/aquality/selenium/core/elements/ElementStateProvider.java @@ -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"); + } +} diff --git a/src/main/java/aquality/selenium/core/elements/interfaces/IElementCacheHandler.java b/src/main/java/aquality/selenium/core/elements/interfaces/IElementCacheHandler.java new file mode 100644 index 0000000..0239153 --- /dev/null +++ b/src/main/java/aquality/selenium/core/elements/interfaces/IElementCacheHandler.java @@ -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); + } +} diff --git a/src/main/java/aquality/selenium/core/elements/interfaces/IElementStateProvider.java b/src/main/java/aquality/selenium/core/elements/interfaces/IElementStateProvider.java new file mode 100644 index 0000000..980a1ff --- /dev/null +++ b/src/main/java/aquality/selenium/core/elements/interfaces/IElementStateProvider.java @@ -0,0 +1,169 @@ +package aquality.selenium.core.elements.interfaces; + + +/** + * Provides ability to define of element's state (whether it is displayed, exist or not) + * Also provides respective positive and negative waiting functions + */ +public interface IElementStateProvider { + + /** + * Is an element clickable on the form. + * + * @return true if element clickable, false otherwise + */ + boolean isClickable(); + + /** + * Waits for is element clickable on the form. + * + * @param timeout Timeout for waiting + * @throws org.openqa.selenium.TimeoutException when timeout exceeded and element is not clickable. + */ + void waitForClickable(Long timeout); + + /** + * Waits for is element clickable on the form. + * Uses condition timeout from settings file for waiting + * + * @throws org.openqa.selenium.TimeoutException when timeout exceeded and element is not clickable. + */ + default void waitForClickable() { + waitForClickable(null); + } + + /** + * Is an element displayed on the form. + * + * @return true if element displayed, false otherwise + */ + boolean isDisplayed(); + + /** + * Waits for is element displayed on the form. + * + * @param timeout Timeout for waiting + * @return true if element displayed after waiting, false otherwise + */ + boolean waitForDisplayed(Long timeout); + + /** + * Waits for is element displayed on the form. + * Uses condition timeout from settings file for waiting + * + * @return true if element displayed after waiting, false otherwise + */ + default boolean waitForDisplayed() { + return waitForDisplayed(null); + } + + /** + * Waits for is element displayed on the form. + * + * @param timeout Timeout for waiting + * @return true if element displayed after waiting, false otherwise + */ + boolean waitForNotDisplayed(Long timeout); + + + /** + * Waits for is element displayed on the form. + * Uses condition timeout from settings file for waiting + * + * @return true if element displayed after waiting, false otherwise + */ + default boolean waitForNotDisplayed() { + return waitForNotDisplayed(null); + } + + /** + * Is an element exist in DOM (without visibility check) + * + * @return true if element exist, false otherwise + */ + boolean isExist(); + + /** + * Waits until element is exist in DOM (without visibility check). + * + * @param timeout Timeout for waiting + * @return true if element exist after waiting, false otherwise + */ + boolean waitForExist(Long timeout); + + + /** + * Waits until element is exist in DOM (without visibility check). + * Uses condition timeout from settings file for waiting + * + * @return true if element exist after waiting, false otherwise + */ + default boolean waitForExist() { + return waitForExist(null); + } + + /** + * Waits until element does not exist in DOM (without visibility check). + * + * @return true if element does not exist after waiting, false otherwise + */ + boolean waitForNotExist(Long timeout); + + /** + * Waits until element does not exist in DOM (without visibility check). + * Uses condition timeout from settings file for waiting + * + * @return true if element does not exist after waiting, false otherwise + */ + default boolean waitForNotExist() { + return waitForNotExist(null); + } + + /** + * Check that the element is enabled + * + * @return true if enabled + * @throws org.openqa.selenium.NoSuchElementException when timeout exceeded and element not found. + */ + boolean isEnabled(); + + /** + * Check that the element is enabled + * + * @param timeout Timeout for waiting + * @return true if enabled + * @throws org.openqa.selenium.NoSuchElementException when timeout exceeded and element not found. + */ + boolean waitForEnabled(Long timeout); + + + /** + * Check that the element is enabled (performed by a class member) + * Uses condition timeout from settings file for waiting + * + * @return true if enabled + * @throws org.openqa.selenium.NoSuchElementException when timeout exceeded and element not found. + */ + default boolean waitForEnabled() { + return waitForEnabled(null); + } + + /** + * Waits until element does not enabled in DOM + * + * @return true if element does not enabled after waiting, false otherwise + * @throws org.openqa.selenium.NoSuchElementException when timeout exceeded and element not found. + */ + boolean waitForNotEnabled(Long timeout); + + /** + * Waits until element does not enabled in DOM + * Uses condition timeout from settings file for waiting + * + * @return true if element does not enabled after waiting, false otherwise + * @throws org.openqa.selenium.NoSuchElementException when timeout exceeded and element not found. + */ + default boolean waitForNotEnabled() { + return waitForNotEnabled(null); + } +} \ No newline at end of file diff --git a/src/main/java/aquality/selenium/core/waitings/ConditionalWait.java b/src/main/java/aquality/selenium/core/waitings/ConditionalWait.java index e5a2f0c..79cc46b 100644 --- a/src/main/java/aquality/selenium/core/waitings/ConditionalWait.java +++ b/src/main/java/aquality/selenium/core/waitings/ConditionalWait.java @@ -99,7 +99,7 @@ private boolean isConditionSatisfied(BooleanSupplier condition, Collection T get(Class type) { + return getServiceProvider().getInstance(type); + } } diff --git a/src/test/java/tests/application/browser/CachedLabel.java b/src/test/java/tests/application/browser/CachedLabel.java new file mode 100644 index 0000000..a53bae3 --- /dev/null +++ b/src/test/java/tests/application/browser/CachedLabel.java @@ -0,0 +1,55 @@ +package tests.application.browser; + +import aquality.selenium.core.elements.ElementState; +import aquality.selenium.core.elements.interfaces.IElementCacheHandler; +import aquality.selenium.core.elements.interfaces.IElementFinder; +import aquality.selenium.core.localization.ILocalizedLogger; +import aquality.selenium.core.waitings.IConditionalWait; +import org.openqa.selenium.By; +import tests.application.ICachedElement; + +public class CachedLabel implements ICachedElement { + private final By locator; + private final ElementState elementState; + private IElementCacheHandler elementCacheHandler; + + public CachedLabel(By locator, ElementState state) { + this.locator = locator; + this.elementState = state; + } + + @Override + public By getLocator() { + return locator; + } + + @Override + public ElementState getElementState() { + return elementState; + } + + @Override + public IElementCacheHandler getElementCacheHandler() { + return elementCacheHandler; + } + + @Override + public void setElementCacheHandler(IElementCacheHandler elementCacheHandler) { + this.elementCacheHandler = elementCacheHandler; + } + + @Override + public IElementFinder getElementFinder() { + return AqualityServices.get(IElementFinder.class); + } + + @Override + public IConditionalWait getConditionalWait() { + return AqualityServices.get(IConditionalWait.class); + } + + @Override + public ILocalizedLogger getLocalizedLogger() { + return AqualityServices.get(ILocalizedLogger.class); + } +} diff --git a/src/test/java/tests/application/browser/ITheInternetPageTest.java b/src/test/java/tests/application/browser/ITheInternetPageTest.java new file mode 100644 index 0000000..97f7447 --- /dev/null +++ b/src/test/java/tests/application/browser/ITheInternetPageTest.java @@ -0,0 +1,36 @@ +package tests.application.browser; + +import aquality.selenium.core.applications.IApplication; +import org.openqa.selenium.Dimension; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import theinternet.TheInternetPage; + +public interface ITheInternetPageTest { + Dimension DEFAULT_SIZE = new Dimension(1024, 768); + + default IApplication getBrowser() { + return AqualityServices.getApplication(); + } + + default boolean isApplicationStarted() { + return AqualityServices.isApplicationStarted(); + } + + default void navigate(TheInternetPage page) { + getBrowser().getDriver().navigate().to(page.getAddress()); + } + + @BeforeMethod + default void beforeMethod() { + navigate(TheInternetPage.DYNAMIC_CONTROLS); + getBrowser().getDriver().manage().window().setSize(DEFAULT_SIZE); + } + + @AfterMethod + default void cleanUp () { + if (isApplicationStarted()) { + getBrowser().getDriver().quit(); + } + } +} diff --git a/src/test/java/tests/application/windowsApp/CachedButton.java b/src/test/java/tests/application/windowsApp/CachedButton.java new file mode 100644 index 0000000..668d6cd --- /dev/null +++ b/src/test/java/tests/application/windowsApp/CachedButton.java @@ -0,0 +1,56 @@ +package tests.application.windowsApp; + +import aquality.selenium.core.elements.ElementState; +import aquality.selenium.core.elements.interfaces.IElementCacheHandler; +import aquality.selenium.core.elements.interfaces.IElementFinder; +import aquality.selenium.core.localization.ILocalizedLogger; +import aquality.selenium.core.waitings.IConditionalWait; +import org.openqa.selenium.By; +import tests.application.ICachedElement; + +public class CachedButton implements ICachedElement { + private final By locator; + private final ElementState elementState; + private IElementCacheHandler elementCacheHandler; + + public CachedButton(By locator) { + this.locator = locator; + this.elementState = ElementState.EXISTS_IN_ANY_STATE; + } + + + @Override + public By getLocator() { + return locator; + } + + @Override + public ElementState getElementState() { + return elementState; + } + + @Override + public IElementCacheHandler getElementCacheHandler() { + return elementCacheHandler; + } + + @Override + public void setElementCacheHandler(IElementCacheHandler elementCacheHandler) { + this.elementCacheHandler = elementCacheHandler; + } + + @Override + public IElementFinder getElementFinder() { + return AqualityServices.get(IElementFinder.class); + } + + @Override + public IConditionalWait getConditionalWait() { + return AqualityServices.get(IConditionalWait.class); + } + + @Override + public ILocalizedLogger getLocalizedLogger() { + return AqualityServices.get(ILocalizedLogger.class); + } +} diff --git a/src/test/java/tests/elements/CachedElementStateProviderTests.java b/src/test/java/tests/elements/CachedElementStateProviderTests.java new file mode 100644 index 0000000..dfe5622 --- /dev/null +++ b/src/test/java/tests/elements/CachedElementStateProviderTests.java @@ -0,0 +1,23 @@ +package tests.elements; + +import aquality.selenium.core.elements.CachedElementStateProvider; +import aquality.selenium.core.elements.ElementCacheHandler; +import aquality.selenium.core.elements.ElementState; +import aquality.selenium.core.elements.interfaces.IElementFinder; +import aquality.selenium.core.elements.interfaces.IElementStateProvider; +import aquality.selenium.core.localization.ILocalizedLogger; +import aquality.selenium.core.waitings.IConditionalWait; +import org.openqa.selenium.By; +import tests.application.browser.AqualityServices; + +public class CachedElementStateProviderTests implements IWebElementStateProviderTests { + + @Override + public IElementStateProvider state(By locator) { + return new CachedElementStateProvider(locator, + AqualityServices.get(IConditionalWait.class), + new ElementCacheHandler(locator, ElementState.EXISTS_IN_ANY_STATE, AqualityServices.get(IElementFinder.class)), + AqualityServices.get(ILocalizedLogger.class)); + } + +} diff --git a/src/test/java/tests/elements/CachedWebElementTests.java b/src/test/java/tests/elements/CachedWebElementTests.java new file mode 100644 index 0000000..b9eccfa --- /dev/null +++ b/src/test/java/tests/elements/CachedWebElementTests.java @@ -0,0 +1,100 @@ +package tests.elements; + +import aquality.selenium.core.elements.interfaces.IElementStateProvider; +import aquality.selenium.core.waitings.IConditionalWait; +import org.testng.Assert; +import org.testng.annotations.Test; +import tests.application.browser.AqualityServices; +import tests.application.browser.CachedLabel; +import tests.application.browser.ITheInternetPageTest; +import theinternet.DynamicControlsForm; +import theinternet.DynamicLoadingForm; +import theinternet.TheInternetPage; + +import java.util.concurrent.TimeoutException; +import java.util.function.Predicate; + +public class CachedWebElementTests implements ITheInternetPageTest, ICachedElementTests { + + private void startLoading() { + navigate(TheInternetPage.DYNAMIC_LOADING); + DynamicLoadingForm.getStartLabel().click(); + } + + private void openDynamicControls() { + navigate(TheInternetPage.DYNAMIC_CONTROLS); + } + + private void waitForLoading(CachedLabel loader) { + Assert.assertTrue(loader.state().waitForDisplayed(), "Loader should be displayed in the beginning"); + Assert.assertTrue(loader.state().waitForNotDisplayed(), "Loader should not be displayed in the end"); + } + + private IConditionalWait conditionalWait() { + return AqualityServices.get(IConditionalWait.class); + } + + @Test + public void testShouldReturnFalseAtWaitForDisplayedWhenElementIsNotDisplayed() { + CachedLabel loader = DynamicLoadingForm.getLoadingLabel(); + startLoading(); + waitForLoading(loader); + Assert.assertFalse(loader.state().waitForDisplayed(ZERO_TIMEOUT)); + } + + @Test + public void testShouldReturnTrueAtWaitForExistWhenElementIsNotDisplayed() { + CachedLabel loader = DynamicLoadingForm.getLoadingLabel(); + startLoading(); + waitForLoading(loader); + Assert.assertTrue(loader.state().waitForExist(ZERO_TIMEOUT)); + } + + @Test + public void testShouldBeStaleWhenBecameInvisible() { + startLoading(); + CachedLabel loader = DynamicLoadingForm.getLoadingLabel(); + Assert.assertTrue(loader.state().waitForDisplayed(), "Loader should be displayed in the beginning"); + Assert.assertTrue(conditionalWait().waitFor(() -> loader.cache().isStale()), + "Loader should become invisible and be treated as stale"); + Assert.assertFalse(loader.state().isDisplayed(), "Invisible loader should be not displayed"); + Assert.assertFalse(loader.state().isExist(), + "Loader that was displayed previously and become invisible should be treated as disappeared"); + Assert.assertTrue(loader.state().waitForExist(ZERO_TIMEOUT), + "When waiting for existance, we should get an actual element's state"); + } + + @Test + public void testShouldRefreshElementWhenItIsStale() { + openDynamicControls(); + CachedLabel example = DynamicControlsForm.getContentLabel(); + String exampleToString = example.getElement().getId(); + getBrowser().getDriver().navigate().refresh(); + Assert.assertTrue(example.cache().isRefreshNeeded(), "refresh should be needed when refreshed the page"); + String newToString = example.getElement().getId(); + Assert.assertNotEquals(newToString, exampleToString, "new and old element's IDs should be different"); + } + + @Test(dataProvider = "stateFunctionsFalseWhenElementStale") + public void testShouldReturnCorrectStateFalseWhenWindowIsRefreshed(Predicate stateCondition) throws TimeoutException { + assertStateConditionAfterRefresh(stateCondition, false); + } + + @Test(dataProvider = "stateFunctionsTrueWhenElementStaleWhichRetriveElement") + public void testShouldReturnCorrectStateTrueWhenWindowIsRefreshed(Predicate stateCondition) throws TimeoutException { + assertStateConditionAfterRefresh(stateCondition, true); + } + + private void assertStateConditionAfterRefresh(Predicate stateCondition, boolean expectedValue) throws TimeoutException { + openDynamicControls(); + CachedLabel testElement = DynamicControlsForm.getCheckboxLabel(); + testElement.state().waitForClickable(); + DynamicControlsForm.getRemoveLabel().click(); + conditionalWait().waitForTrue( + () -> testElement.cache().isStale(), "Element should be stale when it disappeared."); + getBrowser().getDriver().navigate().refresh(); + Assert.assertTrue(testElement.cache().isStale(), "Element should remain stale after the page refresh."); + Assert.assertEquals(stateCondition.test(testElement.state()), expectedValue, + "Element state condition is not expected after refreshing the window"); + } +} diff --git a/src/test/java/tests/elements/CachedWindowsElementTests.java b/src/test/java/tests/elements/CachedWindowsElementTests.java new file mode 100644 index 0000000..28ac2fb --- /dev/null +++ b/src/test/java/tests/elements/CachedWindowsElementTests.java @@ -0,0 +1,83 @@ +package tests.elements; + +import aquality.selenium.core.applications.IApplication; +import aquality.selenium.core.elements.interfaces.IElementStateProvider; +import org.testng.Assert; +import org.testng.annotations.Test; +import tests.ITestWithApplication; +import tests.application.windowsApp.AqualityServices; +import tests.application.windowsApp.CachedButton; +import tests.application.windowsApp.CalculatorWindow; + +import java.util.function.Predicate; + +public class CachedWindowsElementTests implements ICachedElementTests, ITestWithApplication { + + @Override + public IApplication getApplication() { + return AqualityServices.getApplication(); + } + + @Override + public boolean isApplicationStarted() { + return AqualityServices.isApplicationStarted(); + } + + @Test + public void testShouldWorkWithCalculatorWithCachedElement() { + new CachedButton(CalculatorWindow.getOneButton()).click(); + new CachedButton(CalculatorWindow.getPlusButton()).click(); + new CachedButton(CalculatorWindow.getOneButton()).click(); + new CachedButton(CalculatorWindow.getEqualsButton()).click(); + String result = new CachedButton(CalculatorWindow.getResultsLabel()).getElement().getText(); + Assert.assertTrue(result.contains("2")); + } + + @Test + public void testShouldReturnSameElementAfterInteraction() { + CachedButton oneButton = new CachedButton(CalculatorWindow.getOneButton()); + String initialElement = oneButton.getElement().getId(); + oneButton.click(); + String resultElement = oneButton.getElement().getId(); + Assert.assertEquals(resultElement, initialElement, "Element should be the same after getting interaction"); + } + + @Test + public void testShouldReturnSameElementAfterGetState() { + CachedButton oneButton = new CachedButton(CalculatorWindow.getOneButton()); + String initialElement = oneButton.getElement().getId(); + oneButton.state().waitForClickable(); + String resultElement = oneButton.getElement().getId(); + Assert.assertEquals(resultElement, initialElement, "Element should be the same after getting state"); + } + + @Test + public void testShouldReturnNewElementWhenWindowIsReopened() { + CachedButton oneButton = new CachedButton(CalculatorWindow.getOneButton()); + String initialElement = oneButton.getElement().getId(); + getApplication().getDriver().quit(); + oneButton.state().waitForClickable(); + String resultElement = oneButton.getElement().getId(); + Assert.assertNotEquals(resultElement, initialElement, "Element is still the same after reopening the window"); + } + + @Test(dataProvider = "stateFunctionsFalseWhenElementStale") + public void testShouldReturnCorrectStateWhenWindowIsClosed(Predicate stateCondition) { + assertStateConditionAfterQuit(stateCondition, false, false); + } + + @Test(dataProvider = "stateFunctionsTrueWhenElementStaleWhichRetriveElement") + public void testShouldReturnCorrectStateAndReopenApplicationWhenWindowIsClosed(Predicate stateCondition) { + assertStateConditionAfterQuit(stateCondition, true, true); + } + + private void assertStateConditionAfterQuit(Predicate stateCondition, boolean expectedValue, boolean shouldAppRestart){ + CachedButton oneButton = new CachedButton(CalculatorWindow.getOneButton()); + oneButton.getElement(); + getApplication().getDriver().quit(); + Assert.assertEquals(stateCondition.test(oneButton.state()), expectedValue, + "Element state condition is not expected after closing the window"); + Assert.assertEquals(isApplicationStarted(), shouldAppRestart, + String.format("Window was %1$s reopened when retrived the element state.", shouldAppRestart ? "not" : "")); + } +} diff --git a/src/test/java/tests/elements/DefaultElementStateProviderTests.java b/src/test/java/tests/elements/DefaultElementStateProviderTests.java new file mode 100644 index 0000000..2694c6d --- /dev/null +++ b/src/test/java/tests/elements/DefaultElementStateProviderTests.java @@ -0,0 +1,18 @@ +package tests.elements; + +import aquality.selenium.core.elements.DefaultElementStateProvider; +import aquality.selenium.core.elements.interfaces.IElementFinder; +import aquality.selenium.core.elements.interfaces.IElementStateProvider; +import aquality.selenium.core.waitings.IConditionalWait; +import org.openqa.selenium.By; +import tests.application.browser.AqualityServices; + +public class DefaultElementStateProviderTests implements IWebElementStateProviderTests { + + @Override + public IElementStateProvider state(By locator) { + return new DefaultElementStateProvider(locator, + AqualityServices.get(IConditionalWait.class), + AqualityServices.get(IElementFinder.class)); + } +} diff --git a/src/test/java/tests/elements/ICachedElementTests.java b/src/test/java/tests/elements/ICachedElementTests.java new file mode 100644 index 0000000..933cc76 --- /dev/null +++ b/src/test/java/tests/elements/ICachedElementTests.java @@ -0,0 +1,34 @@ +package tests.elements; + +import aquality.selenium.core.elements.interfaces.IElementStateProvider; +import org.testng.annotations.DataProvider; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +public interface ICachedElementTests { + long ZERO_TIMEOUT = 0L; + + @DataProvider + default Object[] stateFunctionsFalseWhenElementStale() { + List> stateFunctions = new ArrayList<>(); + stateFunctions.add(IElementStateProvider::isDisplayed); + stateFunctions.add(IElementStateProvider::isExist); + stateFunctions.add(state -> !state.waitForNotDisplayed(ZERO_TIMEOUT)); + stateFunctions.add(state -> !state.waitForNotExist(ZERO_TIMEOUT)); + return stateFunctions.toArray(); + } + + @DataProvider + default Object[] stateFunctionsTrueWhenElementStaleWhichRetriveElement() { + List> stateFunctions = new ArrayList<>(); + stateFunctions.add(IElementStateProvider::isEnabled); + stateFunctions.add(IElementStateProvider::isClickable); + stateFunctions.add(state -> state.waitForDisplayed(ZERO_TIMEOUT)); + stateFunctions.add(state -> state.waitForExist(ZERO_TIMEOUT)); + stateFunctions.add(state -> state.waitForEnabled(ZERO_TIMEOUT)); + stateFunctions.add(state -> !state.waitForNotEnabled(ZERO_TIMEOUT)); + return stateFunctions.toArray(); + } +} diff --git a/src/test/java/tests/elements/IWebElementStateProviderTests.java b/src/test/java/tests/elements/IWebElementStateProviderTests.java new file mode 100644 index 0000000..cf2b078 --- /dev/null +++ b/src/test/java/tests/elements/IWebElementStateProviderTests.java @@ -0,0 +1,141 @@ +package tests.elements; + +import aquality.selenium.core.configurations.ITimeoutConfiguration; +import aquality.selenium.core.elements.interfaces.IElementStateProvider; +import org.openqa.selenium.By; +import org.openqa.selenium.NoSuchElementException; +import org.testng.Assert; +import org.testng.annotations.Test; +import tests.application.browser.AqualityServices; +import tests.application.browser.ITheInternetPageTest; +import theinternet.DynamicControlsForm; +import theinternet.DynamicLoadingForm; +import theinternet.TheInternetPage; + +import java.util.function.Predicate; + +import static utils.TimeUtil.calculateDuration; +import static utils.TimeUtil.getCurrentTime; + +public interface IWebElementStateProviderTests extends ITheInternetPageTest { + double OPERATION_TIME = 5; + long CUSTOM_WAIT_TIME = 2L; + By ABSENT_ELEMENT_LOCATOR = By.xpath("//div[@class='not exist element']"); + + default DynamicControlsForm getDynamicControlsForm() { + return new DynamicControlsForm(this::getBrowser, this::state); + } + + default DynamicLoadingForm getDynamicLoadingForm() { + return new DynamicLoadingForm(this::getBrowser, this::state); + } + + IElementStateProvider state(By locator); + + default long getConditionTimeout() { + return AqualityServices.get(ITimeoutConfiguration.class).getCondition(); + } + + default void checkWaitingTimeForInputState(Long waitTime, Predicate notExpectedCondition) { + long startTime = getCurrentTime(); + boolean isConditionSatisfied = notExpectedCondition.test(getDynamicControlsForm().inputState()); + double duration = calculateDuration(startTime); + + Assert.assertFalse(isConditionSatisfied); + Assert.assertTrue(duration >= waitTime && duration <= (waitTime + OPERATION_TIME)); + } + + @Test + default void testElementShouldWaitForEnabledWithCustomTimeout() { + long waitTime = CUSTOM_WAIT_TIME; + checkWaitingTimeForInputState(waitTime, state -> state.waitForEnabled(waitTime)); + } + + @Test + default void testElementShouldWaitForEnabledWithDefaultTimeout() { + long waitTime = getConditionTimeout(); + checkWaitingTimeForInputState(waitTime, IElementStateProvider::waitForEnabled); + } + + @Test + default void testElementShouldWaitForNotEnabledWithCustomTimeout() { + long waitTime = CUSTOM_WAIT_TIME; + getDynamicControlsForm().clickEnable(); + getDynamicControlsForm().inputState().waitForEnabled(); + + checkWaitingTimeForInputState(waitTime, state -> state.waitForNotEnabled(waitTime)); + } + + @Test(expectedExceptions = NoSuchElementException.class) + default void testNoSuchShouldBeThrownForWaitEnabledIfElementNotFound() { + state(ABSENT_ELEMENT_LOCATOR).waitForEnabled(CUSTOM_WAIT_TIME); + } + + @Test(expectedExceptions = NoSuchElementException.class) + default void testNoSuchShouldBeThrownForWaitNotEnabledIfElementNotFound() { + state(ABSENT_ELEMENT_LOCATOR).waitForNotEnabled(CUSTOM_WAIT_TIME); + } + + @Test + default void testElementShouldWaitForNotEnabledWithDefaultTimeout() { + long waitTime = getConditionTimeout(); + + getDynamicControlsForm().clickEnable(); + getDynamicControlsForm().inputState().waitForEnabled(); + + checkWaitingTimeForInputState(waitTime, IElementStateProvider::waitForNotEnabled); + } + + @Test + default void testTextBoxEnabled() { + getDynamicControlsForm().clickEnable(); + getDynamicControlsForm().inputState().waitForEnabled(); + Assert.assertTrue(getDynamicControlsForm().inputState().isEnabled()); + } + + @Test + default void testWaitInvisibility() { + navigate(TheInternetPage.DYNAMIC_LOADING); + + getDynamicLoadingForm().startButtonState().waitForClickable(); + getDynamicLoadingForm().clickStart(); + + getDynamicLoadingForm().loaderState().waitForDisplayed(getDynamicLoadingForm().getSmallTimeout()); + + boolean status = getDynamicLoadingForm().loaderState().waitForNotDisplayed(getDynamicLoadingForm().getSmallTimeout()); + Assert.assertFalse(status); + + status = getDynamicLoadingForm().loaderState().waitForNotDisplayed(); + Assert.assertTrue(status); + } + + @Test + default void testWaitForExist() { + navigate(TheInternetPage.DYNAMIC_LOADING); + boolean status = getDynamicLoadingForm().loaderState().waitForExist(getDynamicLoadingForm().getTimeout()); + + Assert.assertFalse(status); + Assert.assertTrue(getDynamicLoadingForm().startButtonState().waitForExist()); + } + + @Test + default void testShouldBePossibleToWaitElementNotExistsCustomTime() { + long waitTime = CUSTOM_WAIT_TIME; + getDynamicControlsForm().clickRemove(); + + checkWaitingTimeForInputState(waitTime, inputState -> getDynamicControlsForm().checkboxState().waitForNotExist(waitTime)); + } + + @Test + default void testShouldBePossibleToWaitElementNotExists() { + long waitTime = getConditionTimeout(); + getDynamicControlsForm().clickRemove(); + + long startTime = getCurrentTime(); + boolean isMissed = getDynamicControlsForm().checkboxState().waitForNotExist(); + double duration = calculateDuration(startTime); + + Assert.assertTrue(isMissed); + Assert.assertTrue(duration < waitTime); + } +} diff --git a/src/test/java/tests/localization/LocalizationManagerTests.java b/src/test/java/tests/localization/LocalizationManagerTests.java index a8e0d13..6c3d203 100644 --- a/src/test/java/tests/localization/LocalizationManagerTests.java +++ b/src/test/java/tests/localization/LocalizationManagerTests.java @@ -27,7 +27,8 @@ private Object[] keysWithParams() { "loc.no.elements.found.by.locator", "loc.elements.were.found.but.not.in.state", "loc.elements.found.but.should.not", - "loc.search.of.elements.failed"}; + "loc.search.of.elements.failed", + "loc.element.not.in.state"}; } @DataProvider diff --git a/src/test/java/tests/utilities/ElementActionRetrierTests.java b/src/test/java/tests/utilities/ElementActionRetrierTests.java index 8ee0010..8f29c3e 100644 --- a/src/test/java/tests/utilities/ElementActionRetrierTests.java +++ b/src/test/java/tests/utilities/ElementActionRetrierTests.java @@ -82,7 +82,7 @@ private void checkRetrierShouldWaitPollingTimeBetweenMethodsCall(Runnable retryF retryFunction.run(); long duration = new Date().getTime() - startTime.getTime(); assertTrue(duration >= POLLING_INTERVAL, String.format("duration '%s' should be more than polling interval '%s'", duration, POLLING_INTERVAL)); - assertTrue(duration <= 2 * POLLING_INTERVAL, String.format("duration '%s' less than doubled polling interval '%s'", duration, POLLING_INTERVAL)); + assertTrue(duration <= 2 * POLLING_INTERVAL, String.format("duration '%s' should be less than doubled polling interval '%s'", duration, POLLING_INTERVAL)); } @Test(expectedExceptions = InvalidArgumentException.class) diff --git a/src/test/java/theinternet/BaseForm.java b/src/test/java/theinternet/BaseForm.java new file mode 100644 index 0000000..4fc6983 --- /dev/null +++ b/src/test/java/theinternet/BaseForm.java @@ -0,0 +1,26 @@ +package theinternet; + +import aquality.selenium.core.applications.IApplication; +import aquality.selenium.core.elements.interfaces.IElementStateProvider; +import org.openqa.selenium.By; +import java.util.function.Function; +import java.util.function.Supplier; + +abstract class BaseForm { + + private final Supplier appSupplier; + private final Function stateProviderFunction; + + BaseForm(Supplier appSupplier, Function stateProviderFunction) { + this.appSupplier = appSupplier; + this.stateProviderFunction = stateProviderFunction; + } + + void click(By locator) { + locator.findElement(appSupplier.get().getDriver()).click(); + } + + IElementStateProvider state(By locator) { + return stateProviderFunction.apply(locator); + } +} diff --git a/src/test/java/theinternet/DynamicControlsForm.java b/src/test/java/theinternet/DynamicControlsForm.java new file mode 100644 index 0000000..7888519 --- /dev/null +++ b/src/test/java/theinternet/DynamicControlsForm.java @@ -0,0 +1,50 @@ +package theinternet; + +import aquality.selenium.core.applications.IApplication; +import aquality.selenium.core.elements.ElementState; +import aquality.selenium.core.elements.interfaces.IElementStateProvider; +import org.openqa.selenium.By; +import tests.application.browser.CachedLabel; + +import java.util.function.Function; +import java.util.function.Supplier; + +public class DynamicControlsForm extends BaseForm { + private static final By ENABLE_BUTTON_LOCATOR = By.xpath("//button[contains(@onclick, 'swapInput()')][contains(.,'Enable')]"); + private static final By REMOVE_BUTTON_LOCATOR = By.xpath("//button[contains(@onclick, 'swapCheckbox()')]"); + private static final By INPUT_TEXTBOX_LOCATOR = By.xpath("//input[@type='text']"); + private static final By CHECKBOX_LOCATOR = By.xpath("//div[@id='checkbox']"); + private static final By CONTENT_LOCATOR = By.id("content"); + + public DynamicControlsForm(Supplier appSupplier, Function stateProviderFunction) { + super(appSupplier, stateProviderFunction); + } + + public void clickEnable() { + click(ENABLE_BUTTON_LOCATOR); + } + + public void clickRemove() { + click(REMOVE_BUTTON_LOCATOR); + } + + public IElementStateProvider inputState() { + return state(INPUT_TEXTBOX_LOCATOR); + } + + public IElementStateProvider checkboxState() { + return state(CHECKBOX_LOCATOR); + } + + public static CachedLabel getContentLabel() { + return new CachedLabel(CONTENT_LOCATOR, ElementState.DISPLAYED); + } + + public static CachedLabel getCheckboxLabel() { + return new CachedLabel(CHECKBOX_LOCATOR, ElementState.EXISTS_IN_ANY_STATE); + } + + public static CachedLabel getRemoveLabel() { + return new CachedLabel(REMOVE_BUTTON_LOCATOR, ElementState.EXISTS_IN_ANY_STATE); + } +} diff --git a/src/test/java/theinternet/DynamicLoadingForm.java b/src/test/java/theinternet/DynamicLoadingForm.java new file mode 100644 index 0000000..8de7e90 --- /dev/null +++ b/src/test/java/theinternet/DynamicLoadingForm.java @@ -0,0 +1,50 @@ +package theinternet; + +import aquality.selenium.core.applications.IApplication; +import aquality.selenium.core.elements.ElementState; +import aquality.selenium.core.elements.interfaces.IElementStateProvider; +import org.openqa.selenium.By; +import tests.application.browser.CachedLabel; + +import java.util.function.Function; +import java.util.function.Supplier; + +public class DynamicLoadingForm extends BaseForm { + + private static final long DEFAULT_LOADING_TIMEOUT = 5L; + private static final long SMALL_LOADING_TIMEOUT = 2L; + private static final By LOADING_LABEL_LOCATOR = By.id("loading"); + private static final By START_BUTTON_LOCATOR = By.xpath("//div[@id='start']/button"); + + public DynamicLoadingForm(Supplier appSupplier, Function stateProviderFunction) { + super(appSupplier, stateProviderFunction); + } + + public static CachedLabel getLoadingLabel() { + return new CachedLabel(LOADING_LABEL_LOCATOR, ElementState.DISPLAYED); + } + + public static CachedLabel getStartLabel() { + return new CachedLabel(START_BUTTON_LOCATOR, ElementState.DISPLAYED); + } + + public long getTimeout() { + return DEFAULT_LOADING_TIMEOUT; + } + + public long getSmallTimeout() { + return SMALL_LOADING_TIMEOUT; + } + + public void clickStart() { + click(START_BUTTON_LOCATOR); + } + + public IElementStateProvider loaderState() { + return state(LOADING_LABEL_LOCATOR); + } + + public IElementStateProvider startButtonState() { + return state(START_BUTTON_LOCATOR); + } +} diff --git a/src/test/java/theinternet/TheInternetPage.java b/src/test/java/theinternet/TheInternetPage.java new file mode 100644 index 0000000..b3080d4 --- /dev/null +++ b/src/test/java/theinternet/TheInternetPage.java @@ -0,0 +1,22 @@ +package theinternet; + +public enum TheInternetPage { + DYNAMIC_CONTROLS, + DYNAMIC_LOADING("dynamic_loading/1"); + + private static final String BASE_URL = "http://the-internet.herokuapp.com/"; + + private final String postfix; + + TheInternetPage() { + this.postfix = name().toLowerCase(); + } + + TheInternetPage(String postfix) { + this.postfix = postfix; + } + + public String getAddress() { + return BASE_URL.concat(postfix); + } +} diff --git a/src/test/java/utils/TimeUtil.java b/src/test/java/utils/TimeUtil.java new file mode 100644 index 0000000..825adea --- /dev/null +++ b/src/test/java/utils/TimeUtil.java @@ -0,0 +1,16 @@ +package utils; + +public class TimeUtil { + + public static long getCurrentTime(){ + return System.nanoTime(); + } + + public static double getCurrentTimeInSeconds(){ + return System.nanoTime()/Math.pow(10,9); + } + + public static double calculateDuration(long startTimeNanoSec){ + return (getCurrentTime() - startTimeNanoSec)/Math.pow(10,9); + } +} diff --git a/src/test/resources/TestSuite.xml b/src/test/resources/TestSuite.xml index cacebc9..86d244e 100644 --- a/src/test/resources/TestSuite.xml +++ b/src/test/resources/TestSuite.xml @@ -13,6 +13,9 @@ + + + @@ -24,6 +27,7 @@ +