diff --git a/bb-core/src/main/java/com/cognifide/qa/bb/constants/ConfigKeys.java b/bb-core/src/main/java/com/cognifide/qa/bb/constants/ConfigKeys.java index 5008ffc1..e35a6228 100644 --- a/bb-core/src/main/java/com/cognifide/qa/bb/constants/ConfigKeys.java +++ b/bb-core/src/main/java/com/cognifide/qa/bb/constants/ConfigKeys.java @@ -36,6 +36,10 @@ public final class ConfigKeys { public static final String WEBDRIVER_CAP_PLATFORM_NAME = "webdriver.cap.platformName"; + /** + * @deprecated since 1.6.0; use {@code timings.*} properties + */ + @Deprecated public static final String WEBDRIVER_DEFAULT_TIMEOUT = "webdriver.defaultTimeout"; public static final String WEBDRIVER_FIREFOX_BIN = "webdriver.firefox.bin"; @@ -52,10 +56,13 @@ public final class ConfigKeys { public static final String WEBDRIVER_URL = "webdriver.url"; + @Deprecated public static final String CONFIGURATION_PATHS = "configuration.paths"; + @Deprecated public static final String DEFAULT_PROPERTIES_NAME = "default.properties"; + @Deprecated public static final String JUNIT_RERUNS = "junit.reruns"; public static final String CONFIG_STRATEGY = "bobcat.config"; @@ -64,6 +71,12 @@ public final class ConfigKeys { public static final String COOKIES_LOAD_AUTOMATICALLY = "cookies.loadAutomatically"; public static final String COOKIES_FILE = "cookies.file"; + public static final String MODIFIERS_IMPLICIT_TIMEOUT = "modifiers.implicitTimeout"; + + public static final String TIMINGS_EXPLICIT_TIMEOUT = "timings.explicitTimeout"; + public static final String TIMINGS_IMPLICIT_TIMEOUT = "timings.implicitTimeout"; + public static final String TIMINGS_POLLING_INTERVAL = "timings.pollingInterval"; + private ConfigKeys() { } } diff --git a/bb-core/src/main/java/com/cognifide/qa/bb/expectedconditions/CommonExpectedConditions.java b/bb-core/src/main/java/com/cognifide/qa/bb/expectedconditions/CommonExpectedConditions.java index 370a962a..bff4522d 100644 --- a/bb-core/src/main/java/com/cognifide/qa/bb/expectedconditions/CommonExpectedConditions.java +++ b/bb-core/src/main/java/com/cognifide/qa/bb/expectedconditions/CommonExpectedConditions.java @@ -34,7 +34,10 @@ /** * Class contains custom ExpectedConditions for explicit waiting (provided by BobcatWait) + * + * @deprecated to be removed in 2.0; use methods from {@link org.openqa.selenium.support.ui.ExpectedConditions} or {@link com.cognifide.qa.bb.webelement.WebElementConditions} */ +@Deprecated public final class CommonExpectedConditions { private static final Logger LOG = LoggerFactory.getLogger(CommonExpectedConditions.class); @@ -56,9 +59,9 @@ public static ExpectedCondition elementNotPresent(final By bySelector) /** * Check if element has attribute with provided value * - * @param attributeName name of the attribute + * @param attributeName name of the attribute * @param attributeValue value of the attribute - * @param element WebElement to check + * @param element WebElement to check * @return true if element has attribute with provided value */ public static ExpectedCondition elementHasAttributeWithValue(final WebElement element, @@ -71,7 +74,7 @@ public static ExpectedCondition elementHasAttributeWithValue(final WebE * Check if element located by specified By locator exists in DOM in an * element's context * - * @param scope scope in which element will be searched for + * @param scope scope in which element will be searched for * @param locator {@link By} locator of the searched element * @return false if element does not exist or WebDriver is null */ @@ -102,7 +105,7 @@ public static ExpectedCondition elementNotPresentOrVisible(final By byS /** * Check if provided element height is greater than expected height * - * @param element - WebElement to check + * @param element - WebElement to check * @param expectedHeight - expected height of an element * @return true if element height is greater than expected */ @@ -115,7 +118,7 @@ public static ExpectedCondition heightOfElementGreaterThan(final WebEle * List of WebElements found in provided scope using provided locator is * constant * - * @param element WebElement to set scope for elements finder + * @param element WebElement to set scope for elements finder * @param byElement By selector * @return true if list of WebElements is the same after one second */ diff --git a/bb-core/src/main/java/com/cognifide/qa/bb/expectedconditions/UrlExpectedConditions.java b/bb-core/src/main/java/com/cognifide/qa/bb/expectedconditions/UrlExpectedConditions.java index 36523e46..ba7d138c 100644 --- a/bb-core/src/main/java/com/cognifide/qa/bb/expectedconditions/UrlExpectedConditions.java +++ b/bb-core/src/main/java/com/cognifide/qa/bb/expectedconditions/UrlExpectedConditions.java @@ -21,10 +21,14 @@ import org.apache.commons.lang3.StringUtils; import org.openqa.selenium.support.ui.ExpectedCondition; +import org.openqa.selenium.support.ui.ExpectedConditions; /** * Class contains custom ExpectedConditions for testing current URL + * + * @deprecated use methods from {@link ExpectedConditions} */ +@Deprecated public final class UrlExpectedConditions { private UrlExpectedConditions() { diff --git a/bb-core/src/main/java/com/cognifide/qa/bb/provider/selenium/BobcatWait.java b/bb-core/src/main/java/com/cognifide/qa/bb/provider/selenium/BobcatWait.java index bbab730f..308e8161 100644 --- a/bb-core/src/main/java/com/cognifide/qa/bb/provider/selenium/BobcatWait.java +++ b/bb-core/src/main/java/com/cognifide/qa/bb/provider/selenium/BobcatWait.java @@ -33,11 +33,13 @@ /** * This is an utility class that extends Selenium's wait functionality. If you need wait/until replacement, - * inject instance of this class into your PageObject. If you only need "sleep" function, call it directly - * from BobcatWait statically, because "sleep" is static. + * inject instance of this class into your PageObject. *

* This class serves as a factory for BobcatWebDriverWait instances. + * + * @deprecated moved under {@link com.cognifide.qa.bb.wait.BobcatWait} */ +@Deprecated public class BobcatWait { private static final Logger LOG = LoggerFactory.getLogger(BobcatWait.class); @@ -52,18 +54,9 @@ public class BobcatWait { private WebDriverProvider webDriverProvider; /** - * This is a wrapper method for Thread's sleep method. - *

- * It catches and logs InterruptedException thrown from original sleep method, - * so the user doesn't need to bother with writing a try-catch block. - *

- * Use this method only if you can't use "withTimeout" (which is more flexible). - *

- * If you don't use "withTimeout", call "sleep" directly from BobcatWait, - * without creating an instance. - * - * @param durationInSec Thread will sleep for durationInSec seconds. + * @deprecated it's 2018, don't use sleeps in your tests :) */ + @Deprecated public static void sleep(double durationInSec) { try { TimeUnit.MILLISECONDS.sleep((long) (durationInSec * 1000)); diff --git a/bb-core/src/main/java/com/cognifide/qa/bb/provider/selenium/BobcatWebDriverWait.java b/bb-core/src/main/java/com/cognifide/qa/bb/provider/selenium/BobcatWebDriverWait.java index dfb7baf9..4e87db92 100644 --- a/bb-core/src/main/java/com/cognifide/qa/bb/provider/selenium/BobcatWebDriverWait.java +++ b/bb-core/src/main/java/com/cognifide/qa/bb/provider/selenium/BobcatWebDriverWait.java @@ -28,7 +28,10 @@ /** * This class performs actual wait. It should always be used with BobcatWait class which acts as a factory for * BobcatWebDriverWait. + * + * @deprecated will be removed in 2.0 */ +@Deprecated public class BobcatWebDriverWait { private static final int IMPLICITLY_WAIT_TIME = 1; @@ -88,7 +91,8 @@ public T until(ExpectedCondition condition) { */ public T until(ExpectedCondition condition, long delay) { webDriver.manage().timeouts().implicitlyWait(IMPLICITLY_WAIT_TIME, TimeUnit.SECONDS); - final T result = new WebDriverWait(webDriver, timeOutInSeconds, delay * 1000L).until(condition::apply); + final T result = + new WebDriverWait(webDriver, timeOutInSeconds, delay * 1000L).until(condition::apply); webDriver.manage().timeouts().implicitlyWait(defaultTimeout, TimeUnit.SECONDS); return result; } diff --git a/bb-core/src/main/java/com/cognifide/qa/bb/provider/selenium/webdriver/modifiers/webdriver/ImplicitTimeoutModifier.java b/bb-core/src/main/java/com/cognifide/qa/bb/provider/selenium/webdriver/modifiers/webdriver/ImplicitTimeoutModifier.java index 2cddf857..f25bfa3e 100644 --- a/bb-core/src/main/java/com/cognifide/qa/bb/provider/selenium/webdriver/modifiers/webdriver/ImplicitTimeoutModifier.java +++ b/bb-core/src/main/java/com/cognifide/qa/bb/provider/selenium/webdriver/modifiers/webdriver/ImplicitTimeoutModifier.java @@ -30,12 +30,16 @@ public class ImplicitTimeoutModifier implements WebDriverModifier { @Inject - @Named(ConfigKeys.WEBDRIVER_DEFAULT_TIMEOUT) + @Named(ConfigKeys.TIMINGS_IMPLICIT_TIMEOUT) private int defaultTimeout; + @Inject + @Named(ConfigKeys.MODIFIERS_IMPLICIT_TIMEOUT) + private boolean enabled; + @Override public boolean shouldModify() { - return true; + return enabled; } @Override diff --git a/bb-core/src/main/java/com/cognifide/qa/bb/utils/BrowserTabsHelper.java b/bb-core/src/main/java/com/cognifide/qa/bb/utils/BrowserTabsHelper.java index 59d8589a..ff4e4c3b 100644 --- a/bb-core/src/main/java/com/cognifide/qa/bb/utils/BrowserTabsHelper.java +++ b/bb-core/src/main/java/com/cognifide/qa/bb/utils/BrowserTabsHelper.java @@ -7,9 +7,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -25,6 +25,7 @@ import org.openqa.selenium.WebDriver; import com.cognifide.qa.bb.guice.ThreadScoped; +import com.cognifide.qa.bb.wait.BobcatWait; import com.google.inject.Inject; /** @@ -38,7 +39,7 @@ public final class BrowserTabsHelper { private WebDriver webDriver; @Inject - private WebElementUtils webElementUtils; + private BobcatWait bobcatWait; /** * Checks if expected tabs count is opened with timeout defined in @@ -48,7 +49,7 @@ public final class BrowserTabsHelper { * @return value indicating if expected tabs count is opened. */ public boolean isExpectedTabsCountOpened(int tabsCount) { - return webElementUtils.isConditionMet(driver -> getOpenedTabsCount() == tabsCount); + return bobcatWait.isConditionMet(driver -> getOpenedTabsCount() == tabsCount); } /** @@ -77,8 +78,8 @@ public void switchToPreviousTab() { } /** - * Switches to the browser tab with specified index offset from active tab. If resulting index - * exceeds the index of first or last tab counting continues from another end. + * Switches to the browser tab with specified index offset from active tab. If resulting index + * exceeds the index of first or last tab counting continues from another end. * * @param tabOffset tab index offset from active tab. */ diff --git a/bb-core/src/main/java/com/cognifide/qa/bb/utils/WebElementUtils.java b/bb-core/src/main/java/com/cognifide/qa/bb/utils/WebElementUtils.java index e221a42e..36913941 100644 --- a/bb-core/src/main/java/com/cognifide/qa/bb/utils/WebElementUtils.java +++ b/bb-core/src/main/java/com/cognifide/qa/bb/utils/WebElementUtils.java @@ -19,11 +19,7 @@ */ package com.cognifide.qa.bb.utils; -import static org.openqa.selenium.support.ui.ExpectedConditions.not; -import static org.openqa.selenium.support.ui.ExpectedConditions.textToBePresentInElement; -import static org.openqa.selenium.support.ui.ExpectedConditions.visibilityOf; -import static org.openqa.selenium.support.ui.ExpectedConditions.visibilityOfAllElements; -import static org.openqa.selenium.support.ui.ExpectedConditions.visibilityOfElementLocated; +import static org.openqa.selenium.support.ui.ExpectedConditions.*; import java.util.ArrayDeque; import java.util.Deque; @@ -41,13 +37,17 @@ import com.cognifide.qa.bb.constants.Timeouts; import com.cognifide.qa.bb.guice.ThreadScoped; import com.cognifide.qa.bb.provider.selenium.BobcatWait; +import com.cognifide.qa.bb.webelement.WebElementConditions; import com.google.inject.Inject; /** * This class contains utility methods for checking with waits if WebElements are meeting different * conditions. + * + * @deprecated use {@link WebElementConditions} together with {@link com.cognifide.qa.bb.wait.BobcatWait} */ @ThreadScoped +@Deprecated public final class WebElementUtils { private static final Logger LOG = LoggerFactory.getLogger(WebElementUtils.class); diff --git a/bb-core/src/main/java/com/cognifide/qa/bb/wait/BobcatWait.java b/bb-core/src/main/java/com/cognifide/qa/bb/wait/BobcatWait.java new file mode 100644 index 00000000..82f6194f --- /dev/null +++ b/bb-core/src/main/java/com/cognifide/qa/bb/wait/BobcatWait.java @@ -0,0 +1,144 @@ +/*- + * #%L + * Bobcat + * %% + * Copyright (C) 2016 Cognifide Ltd. + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package com.cognifide.qa.bb.wait; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; + +import org.openqa.selenium.StaleElementReferenceException; +import org.openqa.selenium.TimeoutException; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.support.ui.ExpectedCondition; +import org.openqa.selenium.support.ui.WebDriverWait; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.cognifide.qa.bb.guice.ThreadScoped; +import com.google.inject.Inject; + +/** + * This is the go-to solution for handling dynamic elements with Bobcat. + * It is based on the {@link org.openqa.selenium.support.ui.WebDriverWait} mechanism. + */ +@ThreadScoped +public class BobcatWait { + + private static final Logger LOG = LoggerFactory.getLogger(BobcatWait.class); + + private Timings timings = new TimingsBuilder().build(); + + private List> ignoredExceptions = new ArrayList<>(); + + private WebDriver webDriver; + + @Inject + public BobcatWait(WebDriver webDriver) { + this.webDriver = webDriver; + } + + /** + * Allows to customize the timings (explicit & implicit timeout + polling time). + * + * @param timings a {@link Timings} instance - use the {@link TimingsBuilder} to create one + * @return a self reference + * @see Timings + */ + public BobcatWait tweak(Timings timings) { + this.timings = timings; + return this; + } + + /** + * Adds a list of exception to be ignored during condition evaluation + * + * @param exceptions list of exceptions to be ignored + * @return a self reference + * @see org.openqa.selenium.support.ui.FluentWait#ignoreAll(Collection) + */ + public BobcatWait ignoring(List> exceptions) { + ignoredExceptions.addAll(exceptions); + return this; + } + + /** + * This method enhances Selenium's "until" method. + * First it reduces implicit timeout to a near-zero value. + * Then a timer is started and the condition is checked cyclically until it is fulfilled + * or the timer times out. Finally, implicit timeout is set back to the value + * defined in the property file (under {@value com.cognifide.qa.bb.constants.ConfigKeys#TIMINGS_IMPLICIT_TIMEOUT} + * + * @param condition Selenium's condition object that is queried cyclically inside the wait loop. + * @param The function's expected return type. + * @return The ExpectedCondition's return value if the function returned something different from null or false before the timeout expired. + * @see WebDriverWait#until(Function) + */ + public T until(ExpectedCondition condition) { + try { + setImplicitTimeoutToNearZero(); + return getWebDriverWait() + .ignoreAll(ignoredExceptions) + .until(condition); + } finally { + ignoredExceptions = new ArrayList<>(); + timings = new TimingsBuilder().build(); + restoreImplicitTimeout(); + } + } + + /** + * Checks if the provided condition is met. + * + * @param condition condition to be checked + * @return boolean indicating if condition is met + */ + public boolean isConditionMet(final ExpectedCondition condition) { + try { + until(condition); + } catch (TimeoutException | StaleElementReferenceException e) { + LOG.debug("{} condition has not been met before timeout ", condition, e); + return false; + } + return true; + } + + /** + * Sets implicit timeout to {@value Timings#NEAR_ZERO} milliseconds. + */ + protected void setImplicitTimeoutToNearZero() { + webDriver.manage().timeouts().implicitlyWait(Timings.NEAR_ZERO, TimeUnit.SECONDS); + } + + /** + * Restores implicit timeout to the value defined in the {@link Timings} instance. + */ + protected void restoreImplicitTimeout() { + webDriver.manage().timeouts().implicitlyWait(timings.getImplicitTimeout(), TimeUnit.SECONDS); + } + + /** + * @return an instance of {@link WebDriverWait} based on the provided {@link Timings} + */ + protected WebDriverWait getWebDriverWait() { + return new WebDriverWait(webDriver, timings.getExplicitTimeout(), timings.getPollingInterval()); + } +} diff --git a/bb-core/src/main/java/com/cognifide/qa/bb/wait/Timings.java b/bb-core/src/main/java/com/cognifide/qa/bb/wait/Timings.java new file mode 100644 index 00000000..3da37bd0 --- /dev/null +++ b/bb-core/src/main/java/com/cognifide/qa/bb/wait/Timings.java @@ -0,0 +1,59 @@ +/*- + * #%L + * Bobcat + * %% + * Copyright (C) 2018 Cognifide Ltd. + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package com.cognifide.qa.bb.wait; + +/** + * A value object defining the set of timings used in {@link BobcatWait}. + * Use {@link TimingsBuilder} to create instances of this class. + *

+ * Contains info about: + *

    + *
  • explicit timeout (in seconds)
  • + *
  • implicit timeout (in seconds)
  • + *
  • polling interval (in milliseconds)
  • + *
+ */ +public class Timings { + public static final int NEAR_ZERO = 1; + public static final int DEFAULT_POLLING_INTERVAL = 500; + public static final int DEFAULT_EXPLICIT_TIMEOUT = 10; + public static final int DEFAULT_IMPLICIT_TIMEOUT = 1; + private long explicitTimeout; + private long pollingInterval; + private long implicitTimeout; + + public Timings(long explicitTimeout, long pollingInterval, long implicitTimeout) { + this.explicitTimeout = explicitTimeout; + this.pollingInterval = pollingInterval; + this.implicitTimeout = implicitTimeout; + } + + public long getExplicitTimeout() { + return explicitTimeout; + } + + public long getPollingInterval() { + return pollingInterval; + } + + public long getImplicitTimeout() { + return implicitTimeout; + } +} diff --git a/bb-core/src/main/java/com/cognifide/qa/bb/wait/TimingsBuilder.java b/bb-core/src/main/java/com/cognifide/qa/bb/wait/TimingsBuilder.java new file mode 100644 index 00000000..2a4cd9a1 --- /dev/null +++ b/bb-core/src/main/java/com/cognifide/qa/bb/wait/TimingsBuilder.java @@ -0,0 +1,89 @@ +/*- + * #%L + * Bobcat + * %% + * Copyright (C) 2018 Cognifide Ltd. + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package com.cognifide.qa.bb.wait; + +import com.cognifide.qa.bb.constants.ConfigKeys; + +/** + * Builder for {@link Timings} instances. + *

+ * Default values are taken from System properties: + *

    + *
  • for explicit timeout - {@value ConfigKeys#TIMINGS_EXPLICIT_TIMEOUT}
  • + *
  • for implicit timeout - {@value ConfigKeys#TIMINGS_IMPLICIT_TIMEOUT}
  • + *
  • for polling interval - {@value ConfigKeys#TIMINGS_POLLING_INTERVAL}
  • + *
+ */ +public class TimingsBuilder { + + private long pollingInterval = + Long.valueOf(System.getProperty(ConfigKeys.TIMINGS_POLLING_INTERVAL, + String.valueOf(Timings.DEFAULT_POLLING_INTERVAL))); + + private long explicitTimeout = + Long.valueOf(System.getProperty(ConfigKeys.TIMINGS_EXPLICIT_TIMEOUT, + String.valueOf(Timings.DEFAULT_EXPLICIT_TIMEOUT))); + + private long implicitTimeout = + Long.valueOf(System.getProperty(ConfigKeys.TIMINGS_IMPLICIT_TIMEOUT, + String.valueOf(Timings.DEFAULT_IMPLICIT_TIMEOUT))); + + /** + * Set the explicit timeout + * + * @param timeout timeout in seconds + * @return a self reference + */ + public TimingsBuilder explicitTimeout(long timeout) { + explicitTimeout = timeout; + return this; + } + + /** + * Set the polling time. + * + * @param time in milliseconds + * @return a self reference + */ + public TimingsBuilder pollingInterval(long time) { + pollingInterval = time; + return this; + } + + /** + * Set the implicit timeout. + * + * @param timeout in miliseconds + * @return a self reference + */ + public TimingsBuilder implicitTimeout(long timeout) { + implicitTimeout = timeout; + return this; + } + + /** + * Creates an instance of {@link Timings} + * + * @return new {@link Timings} instance + */ + public Timings build() { + return new Timings(explicitTimeout, pollingInterval, implicitTimeout); + } +} diff --git a/bb-core/src/main/java/com/cognifide/qa/bb/webelement/WebElementConditions.java b/bb-core/src/main/java/com/cognifide/qa/bb/webelement/WebElementConditions.java new file mode 100644 index 00000000..3c697656 --- /dev/null +++ b/bb-core/src/main/java/com/cognifide/qa/bb/webelement/WebElementConditions.java @@ -0,0 +1,89 @@ +/*- + * #%L + * Bobcat + * %% + * Copyright (C) 2016 Cognifide Ltd. + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package com.cognifide.qa.bb.webelement; + +import java.util.ArrayDeque; +import java.util.Deque; + +import org.openqa.selenium.NoSuchElementException; +import org.openqa.selenium.Point; +import org.openqa.selenium.StaleElementReferenceException; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.ui.ExpectedCondition; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class contains utility methods for checking with waits if WebElements are meeting different + * conditions. + */ +public final class WebElementConditions { + + private static final Logger LOG = LoggerFactory.getLogger(WebElementConditions.class); + + /** + * Condition that checks if animation of provided WebElement finished. + * + * @param element WebElement to be checked + * @return ExpectedCondition representing the above check + */ + public ExpectedCondition hasAnimationFinished(final WebElement element) { + final Deque locations = new ArrayDeque<>(); + return webDriver -> { + Point currentLocation = element.getLocation(); + boolean animationStopped = false; + if (!locations.isEmpty()) { + animationStopped = locations.peekFirst().equals(currentLocation); + } + + locations.addFirst(currentLocation); + return animationStopped ? element : null; + }; + } + + /** + * Condition that checks if provided WebElement is 'ready' to be operated on, ie. is visible and not stale. + * + * @param element list of WebElements within which the specified WebElement is searched + * @return ExpectedCondition representing the above check + */ + public static ExpectedCondition elementIsReady(final WebElement element) { + return webDriver -> { + try { + return element.isDisplayed() ? element : null; + } catch (NoSuchElementException | StaleElementReferenceException e) { + LOG.debug("Element {} not present: {}", element, e); + return null; + } + }; + } + + /** + * Check if provided element height is greater than expected height + * + * @param element - WebElement to check + * @param expectedHeight - expected height of an element + * @return ExpectedCondition representing the above check + */ + public static ExpectedCondition heightOfElementGreaterThan(final WebElement element, + final int expectedHeight) { + return driver -> element.getSize().getHeight() > expectedHeight ? element : null; + } +} diff --git a/bb-core/src/main/resources/default.properties b/bb-core/src/main/resources/default.properties index 61a1e683..188e6c8b 100644 --- a/bb-core/src/main/resources/default.properties +++ b/bb-core/src/main/resources/default.properties @@ -1,17 +1,20 @@ webdriver.type=firefox +#deprecated, see ConfigKeys webdriver.defaultTimeout=10 webdriver.mobile=false webdriver.maximize=true webdriver.reusable=false -timeouts.big=30 -timeouts.medium=15 -timeouts.small=5 -timeouts.minimal=1 - proxy.enabled=false proxy.ip=127.0.0.1 proxy.port=9000 analytics.uri.prefix = /b/ss analytics.call.timeout.ms = 30000 + +cookies.loadAutomatically = true + +modifiers.implicitTimeout=true +timings.explicitTimeout=10 +timings.implicitTimeout=1 +timings.pollingInterval=500 diff --git a/bb-core/src/main/resources/default.yaml b/bb-core/src/main/resources/default.yaml index eaac8cc7..80469eca 100644 --- a/bb-core/src/main/resources/default.yaml +++ b/bb-core/src/main/resources/default.yaml @@ -11,11 +11,6 @@ default: bobcat.report.path: ./target/report bobcat.report.reporters: html,json,simple,stdout - timeouts.big: 30 - timeouts.medium: 15 - timeouts.small: 5 - timeouts.minimal: 1 - analytics.uri.prefix: /b/ss analytics.call.timeout.ms: 30000 @@ -24,6 +19,11 @@ default: webdriver.mobile: false webdriver.maximize: true webdriver.reusable: false - webdriver.defaultTimeout: 10 + webdriver.defaultTimeout: 10 #deprecated, see ConfigKeys cookies.loadAutomatically: true + + modifiers.implicitTimeout: true + timings.explicitTimeout: 10 + timings.implicitTimeout: 1 + timings.pollingInterval: 500 diff --git a/bb-core/src/test/java/com/cognifide/qa/bb/expectedconditions/UrlExpectedConditionsPageUrlContainsTest.java b/bb-core/src/test/java/com/cognifide/qa/bb/expectedconditions/UrlExpectedConditionsPageUrlContainsTest.java index c8d45c25..9be3b985 100644 --- a/bb-core/src/test/java/com/cognifide/qa/bb/expectedconditions/UrlExpectedConditionsPageUrlContainsTest.java +++ b/bb-core/src/test/java/com/cognifide/qa/bb/expectedconditions/UrlExpectedConditionsPageUrlContainsTest.java @@ -35,6 +35,7 @@ import org.mockito.junit.MockitoRule; import org.openqa.selenium.WebDriver; +@Deprecated @RunWith(Parameterized.class) public class UrlExpectedConditionsPageUrlContainsTest { diff --git a/bb-core/src/test/java/com/cognifide/qa/bb/expectedconditions/UrlExpectedConditionsPageUrlIsTest.java b/bb-core/src/test/java/com/cognifide/qa/bb/expectedconditions/UrlExpectedConditionsPageUrlIsTest.java index a37ec732..e0ad9fad 100644 --- a/bb-core/src/test/java/com/cognifide/qa/bb/expectedconditions/UrlExpectedConditionsPageUrlIsTest.java +++ b/bb-core/src/test/java/com/cognifide/qa/bb/expectedconditions/UrlExpectedConditionsPageUrlIsTest.java @@ -35,6 +35,7 @@ import org.mockito.junit.MockitoRule; import org.openqa.selenium.WebDriver; +@Deprecated @RunWith(Parameterized.class) public class UrlExpectedConditionsPageUrlIsTest { diff --git a/bb-core/src/test/java/com/cognifide/qa/bb/wait/BobcatWaitTest.java b/bb-core/src/test/java/com/cognifide/qa/bb/wait/BobcatWaitTest.java new file mode 100644 index 00000000..0e617ee3 --- /dev/null +++ b/bb-core/src/test/java/com/cognifide/qa/bb/wait/BobcatWaitTest.java @@ -0,0 +1,87 @@ +/*- + * #%L + * Bobcat + * %% + * Copyright (C) 2016 Cognifide Ltd. + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package com.cognifide.qa.bb.wait; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.openqa.selenium.TimeoutException; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.support.ui.ExpectedCondition; + +@RunWith(MockitoJUnitRunner.class) +public class BobcatWaitTest { + + @Mock + private WebDriver webDriver; + + @Mock + private WebDriver.Options options; + + @Mock + private WebDriver.Timeouts timeouts; + + private BobcatWait tested; + + @Before + public void setup() { + when(webDriver.manage()).thenReturn(options); + when(options.timeouts()).thenReturn(timeouts); + when(timeouts.implicitlyWait(anyLong(), any())).thenReturn(timeouts); + tested = new BobcatWait(webDriver); + } + + @Test + public void whenUntilIsInvokedImplicitTimeoutsShouldBeReducedThenRestored() { + BobcatWait spied = spy(tested); + InOrder inOrder = inOrder(spied); + when(condition.apply(any())).thenReturn(true); + + spied.until(condition); + + inOrder.verify(spied).setImplicitTimeoutToNearZero(); + inOrder.verify(spied).getWebDriverWait(); + inOrder.verify(spied).restoreImplicitTimeout(); + } + + @Mock + private ExpectedCondition condition; + + @Test + public void isConditionMetShouldCatchTimeoutExceptionAndReturnBoolean() { + when(condition.apply(any())).thenThrow(new TimeoutException()); + + boolean result = true; + try { + result = tested.isConditionMet(condition); + } catch (TimeoutException e) { + fail("Exception should not be thrown"); + } + assertThat(result).isFalse(); + } +} diff --git a/docs/_data/navigation.yml b/docs/_data/navigation.yml index 9bb641f5..b0410580 100644 --- a/docs/_data/navigation.yml +++ b/docs/_data/navigation.yml @@ -24,6 +24,8 @@ docs: children: - title: "Overview" url: /docs/modules/core/ + - title: "Explicit and implicit waiting" + url: /docs/modules/core/waiting - title: "Setting cookies" url: /docs/modules/core/cookies - title: "Other modules" @@ -36,7 +38,9 @@ docs: url: /docs/modules/traffic/ - title: "Email" url: /docs/modules/email/ - - title: "AEM modules (new API incoming!)" + - title: "AEM modules" + children: + - title: "New API incoming in 2.0!" - title: "EOL Modules" children: - title: "AEM Common" diff --git a/docs/_docs/modules/core/waiting.md b/docs/_docs/modules/core/waiting.md new file mode 100644 index 00000000..3e7e9154 --- /dev/null +++ b/docs/_docs/modules/core/waiting.md @@ -0,0 +1,96 @@ +--- +title: "Waiting" +--- + +Available in `bb-core` since version `1.6.0`. Mechanism from previous versions: [link](https://github.com/Cognifide/bobcat/wiki/Explicit-Waits) +{: .notice--info} + +## Overview +One of the fundamental problems when automating GUI is handling dynamic elements on a website: pop-ups, carousels, menus, lazy-loaded data in various places, etc. + +Bobcat utilizes Selenium's out-of-the-box mechanisms: implicit and explicit waiting. More information about them can be found in [official docs](https://www.seleniumhq.org/docs/04_webdriver_advanced.jsp). + +## Implicit waiting + +Implicit waiting happens before any lookup of a `WebElement` is being executed during your tests. When searching for an element it waits for a specific period of time to actually report the issue in case of the element not being present on the page. +This implicit timeout can be set manually using: `webDriver.manage().timeouts().implicitlyWait(,