/* * (C) Copyright 2016 Nuxeo SA (http://nuxeo.com/) and others. * * 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. * * Contributors: * <a href="mailto:grenard@nuxeo.com">Guillaume</a> * Yannis JULIENNE */ package org.nuxeo.functionaltests; import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.openqa.selenium.By; import org.openqa.selenium.InvalidSelectorException; import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.NoSuchElementException; import org.openqa.selenium.NotFoundException; import org.openqa.selenium.StaleElementReferenceException; import org.openqa.selenium.TimeoutException; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriverException; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.ExpectedCondition; import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.FluentWait; import org.openqa.selenium.support.ui.Wait; import org.openqa.selenium.support.ui.WebDriverWait; import com.google.common.base.Function; /** * Helper class providing find and wait methods with or without timeout. When requiring timeout, the polling frequency * is every 100 milliseconds if not specified. * * @since 5.9.2 */ public class Locator { private static final Log log = LogFactory.getLog(Locator.class); // Timeout for waitUntilURLDifferentFrom in seconds public static int URLCHANGE_MAX_WAIT = 30; public static WebElement findElement(By by) { return AbstractTest.driver.findElement(by); } /** * Finds the first {@link WebElement} using the given method, with the default timeout. Then waits until the element * is enabled, with the default timeout. * * @param by the locating mechanism * @return the first matching element on the current page, if found * @throws NotFoundException if the element is not found or not enabled */ public static WebElement findElementAndWaitUntilEnabled(By by) throws NotFoundException { return findElementAndWaitUntilEnabled(by, AbstractTest.LOAD_TIMEOUT_SECONDS * 1000, AbstractTest.AJAX_TIMEOUT_SECONDS * 1000); } /** * Finds the first {@link WebElement} using the given method, with a {@code findElementTimeout}. Then waits until * the element is enabled, with a {@code waitUntilEnabledTimeout}. * * @param by the locating mechanism * @param findElementTimeout the find element timeout in milliseconds * @param waitUntilEnabledTimeout the wait until enabled timeout in milliseconds * @return the first matching element on the current page, if found * @throws NotFoundException if the element is not found or not enabled */ public static WebElement findElementAndWaitUntilEnabled(final By by, final int findElementTimeout, final int waitUntilEnabledTimeout) throws NotFoundException { return findElementAndWaitUntilEnabled(null, by, findElementTimeout, waitUntilEnabledTimeout); } /** * Finds the first {@link WebElement} using the given method, with a {@code findElementTimeout}, inside an optional * {@code parentElement}. Then waits until the element is enabled, with a {@code waitUntilEnabledTimeout}. * * @param parentElement the parent element (can be null) * @param by the locating mechanism * @param findElementTimeout the find element timeout in milliseconds * @param waitUntilEnabledTimeout the wait until enabled timeout in milliseconds * @return the first matching element on the current page, if found, with optional parent element * @throws NotFoundException if the element is not found or not enabled * @since 8.3 */ public static WebElement findElementAndWaitUntilEnabled(WebElement parentElement, final By by, final int findElementTimeout, final int waitUntilEnabledTimeout) throws NotFoundException { Wait<WebDriver> wait = getFluentWait(); Function<WebDriver, WebElement> function = new Function<WebDriver, WebElement>() { @Override public WebElement apply(WebDriver driver) { WebElement element = null; try { // Find the element. element = findElementWithTimeout(by, findElementTimeout, parentElement); // Try to wait until the element is enabled. waitUntilEnabled(element, waitUntilEnabledTimeout); } catch (StaleElementReferenceException sere) { AbstractTest.log.debug("StaleElementReferenceException: " + sere.getMessage()); return null; } return element; } }; return wait.until(function); } public static List<WebElement> findElementsWithTimeout(final By by) throws NoSuchElementException { FluentWait<WebDriver> wait = getFluentWait(); wait.ignoring(NoSuchElementException.class); return wait.until(new Function<WebDriver, List<WebElement>>() { @Override public List<WebElement> apply(WebDriver driver) { List<WebElement> elements = driver.findElements(by); return elements.isEmpty() ? null : elements; } }); } /** * Finds the first {@link WebElement} using the given method, with the default timeout. Then waits until the element * is enabled, with the default timeout. Then clicks on the element. * * @param by the locating mechanism * @throws NotFoundException if the element is not found or not enabled */ public static void findElementWaitUntilEnabledAndClick(By by) throws NotFoundException { findElementWaitUntilEnabledAndClick(null, by, AbstractTest.LOAD_TIMEOUT_SECONDS * 1000, AbstractTest.AJAX_TIMEOUT_SECONDS * 1000); } /** * Finds the first {@link WebElement} using the given method, with the default timeout, inside an optional * {@code parentElement}. Then waits until the element is enabled, with the default timeout. Then clicks on the * element. * * @param parentElement the parent element (can be null) * @param by the locating mechanism * @throws NotFoundException if the element is not found or not enabled * @since 8.3 */ public static void findElementWaitUntilEnabledAndClick(WebElement parentElement, By by) throws NotFoundException { findElementWaitUntilEnabledAndClick(parentElement, by, AbstractTest.LOAD_TIMEOUT_SECONDS * 1000, AbstractTest.AJAX_TIMEOUT_SECONDS * 1000); } /** * Finds the first {@link WebElement} using the given method, with a timeout. * * @param by the locating mechanism * @param timeout the timeout in milliseconds * @return the first matching element on the current page, if found * @throws NoSuchElementException when not found */ public static WebElement findElementWithTimeout(By by) throws NoSuchElementException { return findElementWithTimeout(by, AbstractTest.LOAD_TIMEOUT_SECONDS * 1000); } /** * Finds the first {@link WebElement} using the given method, with a timeout. * * @param by the locating mechanism * @param timeout the timeout in milliseconds * @return the first matching element on the current page, if found * @throws NoSuchElementException when not found */ public static WebElement findElementWithTimeout(By by, int timeout) throws NoSuchElementException { return findElementWithTimeout(by, timeout, null); } /** * Finds the first {@link WebElement} using the given method, with a timeout. * * @param by the locating mechanism * @param timeout the timeout in milliseconds * @param parentElement find from the element * @return the first matching element on the current page, if found * @throws NoSuchElementException when not found */ public static WebElement findElementWithTimeout(final By by, int timeout, final WebElement parentElement) throws NoSuchElementException { FluentWait<WebDriver> wait = getFluentWait(); wait.withTimeout(timeout, TimeUnit.MILLISECONDS).ignoring(StaleElementReferenceException.class); try { return wait.until(new Function<WebDriver, WebElement>() { @Override public WebElement apply(WebDriver driver) { try { if (parentElement == null) { return driver.findElement(by); } else { return parentElement.findElement(by); } } catch (NoSuchElementException e) { return null; } } }); } catch (TimeoutException e) { throw new NoSuchElementException(String.format("Couldn't find element '%s' after timeout", by)); } } /** * Finds the first {@link WebElement} using the given method, with a timeout. * * @param by the locating mechanism * @param timeout the timeout in milliseconds * @param parentElement find from the element * @return the first matching element on the current page, if found * @throws NoSuchElementException when not found */ public static WebElement findElementWithTimeout(By by, WebElement parentElement) throws NoSuchElementException { return findElementWithTimeout(by, AbstractTest.LOAD_TIMEOUT_SECONDS * 1000, parentElement); } public static FluentWait<WebDriver> getFluentWait() { FluentWait<WebDriver> wait = new FluentWait<WebDriver>(AbstractTest.driver); wait.withTimeout(AbstractTest.LOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS) .pollingEvery(AbstractTest.POLLING_FREQUENCY_MILLISECONDS, TimeUnit.MILLISECONDS); return wait; } /** * Fluent wait for text to be not present in the given element. * * @since 5.7.3 */ public static void waitForTextNotPresent(final WebElement element, final String text) { Wait<WebDriver> wait = getFluentWait(); wait.until((new Function<WebDriver, Boolean>() { @Override public Boolean apply(WebDriver driver) { try { return !element.getText().contains(text); } catch (StaleElementReferenceException e) { return null; } } })); } /** * Fluent wait for text to be present in the element retrieved with the given method. * * @since 5.7.3 */ public static void waitForTextPresent(By locator, String text) { Wait<WebDriver> wait = getFluentWait(); wait.until(ExpectedConditions.textToBePresentInElementLocated(locator, text)); } /** * Fluent wait for text to be present in the given element. * * @since 5.7.3 */ public static void waitForTextPresent(final WebElement element, final String text) { Wait<WebDriver> wait = getFluentWait(); wait.until((new Function<WebDriver, Boolean>() { @Override public Boolean apply(WebDriver driver) { try { return element.getText().contains(text); } catch (StaleElementReferenceException e) { return null; } } })); } /** * Finds the first {@link WebElement} using the given method, with a {@code findElementTimeout}. Then waits until * the element is enabled, with a {@code waitUntilEnabledTimeout}. Scroll to it, then clicks on the element. * * @param by the locating mechanism * @param findElementTimeout the find element timeout in milliseconds * @param waitUntilEnabledTimeout the wait until enabled timeout in milliseconds * @throws NotFoundException if the element is not found or not enabled * @deprecated since 8.3, use {@link #findElementWaitUntilEnabledAndClick(WebElement, By)} */ @Deprecated public static void findElementWaitUntilEnabledAndClick(final By by, final int findElementTimeout, final int waitUntilEnabledTimeout) throws NotFoundException { findElementWaitUntilEnabledAndClick(null, by, findElementTimeout, waitUntilEnabledTimeout); } /** * Finds the first {@link WebElement} using the given method, with a {@code findElementTimeout}, inside an optional * {@code parentElement}. Then waits until the element is enabled, with a {@code waitUntilEnabledTimeout}. Scroll to * it, then clicks on the element. * * @param parentElement the parent element (can be null) * @param by the locating mechanism * @param findElementTimeout the find element timeout in milliseconds * @param waitUntilEnabledTimeout the wait until enabled timeout in milliseconds * @throws NotFoundException if the element is not found or not enabled * @since 8.3 */ public static void findElementWaitUntilEnabledAndClick(WebElement parentElement, final By by, final int findElementTimeout, final int waitUntilEnabledTimeout) throws NotFoundException { WebElement element = findElementAndWaitUntilEnabled(parentElement, by, findElementTimeout, waitUntilEnabledTimeout); waitUntilGivenFunctionIgnoring(new Function<WebDriver, Boolean>() { @Override public Boolean apply(WebDriver driver) { return scrollAndForceClick(element); } }, StaleElementReferenceException.class); } /** * Waits until the element is enabled, with a default timeout. Then clicks on the element. * * @param element the element * @throws NotFoundException if the element is not found or not enabled * @since 8.3 */ public static void waitUntilEnabledAndClick(final WebElement element) throws NotFoundException { waitUntilEnabledAndClick(element, AbstractTest.AJAX_TIMEOUT_SECONDS * 1000); } /** * Waits until the element is enabled, with a {@code waitUntilEnabledTimeout}. Scroll to it, then clicks on the * element. * * @param element the element * @param waitUntilEnabledTimeout the wait until enabled timeout in milliseconds * @throws NotFoundException if the element is not found or not enabled * @since 8.3 */ public static void waitUntilEnabledAndClick(final WebElement element, final int waitUntilEnabledTimeout) throws NotFoundException { waitUntilEnabled(element, waitUntilEnabledTimeout); waitUntilGivenFunctionIgnoring(new Function<WebDriver, Boolean>() { @Override public Boolean apply(WebDriver driver) { return scrollAndForceClick(element); } }, StaleElementReferenceException.class); } /** * Finds the first {@link WebElement} using the given method, with a {@code findElementTimeout}. Then clicks on the * element. * * @param by the locating mechanism * @throws NotFoundException if the element is not found or not enabled * @since 5.9.4 */ public static void findElementWithTimeoutAndClick(final By by) throws NotFoundException { waitUntilGivenFunctionIgnoring(new Function<WebDriver, Boolean>() { @Override public Boolean apply(WebDriver driver) { // Find the element. WebElement element = findElementWithTimeout(by); element.click(); return true; } }, StaleElementReferenceException.class); } /** * Fluent wait for an element not to be present, checking every 100 ms. * * @since 5.7.2 */ public static void waitUntilElementNotPresent(final By locator) { Wait<WebDriver> wait = getFluentWait(); wait.until((new Function<WebDriver, By>() { @Override public By apply(WebDriver driver) { try { driver.findElement(locator); } catch (NoSuchElementException ex) { // ok return locator; } return null; } })); } /** * Fluent wait for an element to be present, checking every 100 ms. * * @since 5.7.2 */ public static void waitUntilElementPresent(final By locator) { FluentWait<WebDriver> wait = getFluentWait(); wait.ignoring(NoSuchElementException.class); wait.until(new Function<WebDriver, WebElement>() { @Override public WebElement apply(WebDriver driver) { return driver.findElement(locator); } }); } /** * Waits until an element is enabled, with a timeout. * * @param element the element */ public static void waitUntilEnabled(WebElement element) throws NotFoundException { waitUntilEnabled(element, AbstractTest.AJAX_TIMEOUT_SECONDS * 1000); } /** * Waits until an element is enabled, with a timeout. * * @param element the element * @param timeout the timeout in milliseconds */ public static void waitUntilEnabled(final WebElement element, int timeout) throws NotFoundException { FluentWait<WebDriver> wait = getFluentWait(); wait.withTimeout(timeout, TimeUnit.MILLISECONDS); Function<WebDriver, Boolean> function = new Function<WebDriver, Boolean>() { @Override public Boolean apply(WebDriver driver) { return element.isEnabled(); } }; try { wait.until(function); } catch (TimeoutException e) { throw new NotFoundException("Element not enabled after timeout: " + element); } } /** * Fluent wait on a the given function, checking every 100 ms. * * @param function * @since 5.9.2 */ public static void waitUntilGivenFunction(Function<WebDriver, Boolean> function) { waitUntilGivenFunctionIgnoring(function, null); } /** * Fluent wait on a the given function, checking every 100 ms. * * @param function * @param ignoredExceptions the types of exceptions to ignore. * @since 5.9.2 */ @SafeVarargs public static <K extends java.lang.Throwable> void waitUntilGivenFunctionIgnoreAll( Function<WebDriver, Boolean> function, java.lang.Class<? extends K>... ignoredExceptions) { FluentWait<WebDriver> wait = getFluentWait(); if (ignoredExceptions != null) { if (ignoredExceptions.length == 1) { wait.ignoring(ignoredExceptions[0]); } else { wait.ignoreAll(Arrays.asList(ignoredExceptions)); } } wait.until(function); } /** * Fluent wait on a the given function, checking every 100 ms. * * @param function * @param ignoredException the type of exception to ignore. * @since 5.9.2 */ public static <K extends java.lang.Throwable> void waitUntilGivenFunctionIgnoring( Function<WebDriver, Boolean> function, java.lang.Class<? extends K> ignoredException) { FluentWait<WebDriver> wait = getFluentWait(); if (ignoredException != null) { wait.ignoring(ignoredException); } wait.until(function); } /** * Waits until the URL contains the string given in parameter, with a timeout. * * @param string the string that is to be contained * @since 5.9.2 */ public static void waitUntilURLContains(String string) { waitUntilURLContainsOrNot(string, true); } /** * @since 5.9.2 */ private static void waitUntilURLContainsOrNot(String string, final boolean contain) { final String refurl = string; ExpectedCondition<Boolean> condition = new ExpectedCondition<Boolean>() { @Override public Boolean apply(WebDriver d) { String currentUrl = d.getCurrentUrl(); boolean result = !(currentUrl.contains(refurl) ^ contain); if (!result) { AbstractTest.log.debug("currentUrl is : " + currentUrl); AbstractTest.log.debug((contain ? "It should contains : " : "It should not contains : ") + refurl); } return result; } }; WebDriverWait wait = new WebDriverWait(AbstractTest.driver, URLCHANGE_MAX_WAIT); wait.until(condition); } /** * Waits until the URL is different from the one given in parameter, with a timeout. * * @param url the URL to compare to */ public static void waitUntilURLDifferentFrom(String url) { final String refurl = url; ExpectedCondition<Boolean> urlchanged = new ExpectedCondition<Boolean>() { @Override public Boolean apply(WebDriver d) { String currentUrl = d.getCurrentUrl(); AbstractTest.log.debug("currentUrl is still: " + currentUrl); return !currentUrl.equals(refurl); } }; WebDriverWait wait = new WebDriverWait(AbstractTest.driver, URLCHANGE_MAX_WAIT); wait.until(urlchanged); if (AbstractTest.driver.getCurrentUrl().equals(refurl)) { log.warn("Page change failed"); } } /** * Waits until the URL does not contain the string given in parameter, with a timeout. * * @param string the string that is not to be contained * @since 5.9.2 */ public static void waitUntilURLNotContain(String string) { waitUntilURLContainsOrNot(string, false); } /** * Return parent element with given tag name. * <p> * Throws a {@link NoSuchElementException} error if no element found. * * @since 7.3 */ public static WebElement findParentTag(WebElement elt, String tagName) { try { By parentBy = By.xpath(".."); WebElement p = elt.findElement(parentBy); while (p != null) { if (tagName.equals(p.getTagName())) { return p; } p = p.findElement(parentBy); } } catch (InvalidSelectorException e) { } throw new NoSuchElementException(String.format("No parent element found with tag %s.", tagName)); } /** * Scrolls to the element in the view: allows to safely click on it afterwards. * * @param executor the javascript executor, usually {@link WebDriver} * @param element the element to scroll to * @since 8.3 */ public static final void scrollToElement(WebElement element) { ((JavascriptExecutor) AbstractTest.driver).executeScript("arguments[0].scrollIntoView(false);", element); } /** * Forces a click on an element, to workaround non-effective clicks in miscellaneous situations, after having * scrolled to it. * * @param executor the javascript executor, usually {@link WebDriver} * @param element the element to scroll to * @return true if element is clickable * @since 8.3 */ public static final boolean scrollAndForceClick(WebElement element) { JavascriptExecutor executor = (JavascriptExecutor) AbstractTest.driver; scrollToElement(element); try { // forced click to workaround non-effective clicks in miscellaneous situations executor.executeScript("arguments[0].click();", element); return true; } catch (WebDriverException e) { if (e.getMessage().contains("Element is not clickable at point")) { log.debug("Element is not clickable yet"); return false; } throw e; } } }