001/* 002 * (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl-2.1.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * <a href="mailto:grenard@nuxeo.com">Guillaume</a> 016 */ 017package org.nuxeo.functionaltests; 018 019import java.util.Arrays; 020import java.util.List; 021import java.util.concurrent.TimeUnit; 022 023import org.apache.commons.logging.Log; 024import org.apache.commons.logging.LogFactory; 025import org.openqa.selenium.By; 026import org.openqa.selenium.InvalidSelectorException; 027import org.openqa.selenium.NoSuchElementException; 028import org.openqa.selenium.NotFoundException; 029import org.openqa.selenium.StaleElementReferenceException; 030import org.openqa.selenium.TimeoutException; 031import org.openqa.selenium.WebDriver; 032import org.openqa.selenium.WebElement; 033import org.openqa.selenium.support.ui.ExpectedCondition; 034import org.openqa.selenium.support.ui.ExpectedConditions; 035import org.openqa.selenium.support.ui.FluentWait; 036import org.openqa.selenium.support.ui.Wait; 037import org.openqa.selenium.support.ui.WebDriverWait; 038 039import com.google.common.base.Function; 040 041/** 042 * Helper class providing find and wait methods with or without timeout. When requiring timeout, the polling frequency 043 * is every 100 milliseconds if not specified. 044 * 045 * @since 5.9.2 046 */ 047public class Locator { 048 049 private static final Log log = LogFactory.getLog(Locator.class); 050 051 // Timeout for waitUntilURLDifferentFrom in seconds 052 public static int URLCHANGE_MAX_WAIT = 30; 053 054 public static WebElement findElement(By by) { 055 return AbstractTest.driver.findElement(by); 056 } 057 058 /** 059 * Finds the first {@link WebElement} using the given method, with the default timeout. Then waits until the element 060 * is enabled, with the default timeout. 061 * 062 * @param by the locating mechanism 063 * @return the first matching element on the current page, if found 064 * @throws NotFoundException if the element is not found or not enabled 065 */ 066 public static WebElement findElementAndWaitUntilEnabled(By by) throws NotFoundException { 067 return findElementAndWaitUntilEnabled(by, AbstractTest.LOAD_TIMEOUT_SECONDS * 1000, 068 AbstractTest.AJAX_TIMEOUT_SECONDS * 1000); 069 } 070 071 /** 072 * Finds the first {@link WebElement} using the given method, with a {@code findElementTimeout}. Then waits until 073 * the element is enabled, with a {@code waitUntilEnabledTimeout}. 074 * 075 * @param by the locating mechanism 076 * @param findElementTimeout the find element timeout in milliseconds 077 * @param waitUntilEnabledTimeout the wait until enabled timeout in milliseconds 078 * @return the first matching element on the current page, if found 079 * @throws NotFoundException if the element is not found or not enabled 080 */ 081 public static WebElement findElementAndWaitUntilEnabled(final By by, final int findElementTimeout, 082 final int waitUntilEnabledTimeout) throws NotFoundException { 083 Wait<WebDriver> wait = new FluentWait<WebDriver>(AbstractTest.driver).withTimeout( 084 AbstractTest.LOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS).pollingEvery( 085 AbstractTest.POLLING_FREQUENCY_MILLISECONDS, TimeUnit.MILLISECONDS); 086 Function<WebDriver, WebElement> function = new Function<WebDriver, WebElement>() { 087 @Override 088 public WebElement apply(WebDriver driver) { 089 WebElement element = null; 090 try { 091 // Find the element. 092 element = findElementWithTimeout(by, findElementTimeout); 093 094 // Try to wait until the element is enabled. 095 waitUntilEnabled(element, waitUntilEnabledTimeout); 096 } catch (StaleElementReferenceException sere) { 097 AbstractTest.log.debug("StaleElementReferenceException: " + sere.getMessage()); 098 return null; 099 } 100 return element; 101 } 102 }; 103 104 return wait.until(function); 105 106 } 107 108 public static List<WebElement> findElementsWithTimeout(final By by) throws NoSuchElementException { 109 Wait<WebDriver> wait = new FluentWait<WebDriver>(AbstractTest.driver).withTimeout( 110 AbstractTest.LOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS).pollingEvery( 111 AbstractTest.POLLING_FREQUENCY_MILLISECONDS, TimeUnit.MILLISECONDS).ignoring( 112 NoSuchElementException.class); 113 return wait.until(new Function<WebDriver, List<WebElement>>() { 114 @Override 115 public List<WebElement> apply(WebDriver driver) { 116 List<WebElement> elements = driver.findElements(by); 117 return elements.isEmpty() ? null : elements; 118 } 119 }); 120 } 121 122 /** 123 * Finds the first {@link WebElement} using the given method, with the default timeout. Then waits until the element 124 * is enabled, with the default timeout. Then clicks on the element. 125 * 126 * @param by the locating mechanism 127 * @throws NotFoundException if the element is not found or not enabled 128 */ 129 public static void findElementWaitUntilEnabledAndClick(By by) throws NotFoundException { 130 waitUntilElementEnabledAndClick(by, AbstractTest.LOAD_TIMEOUT_SECONDS * 1000, 131 AbstractTest.AJAX_TIMEOUT_SECONDS * 1000); 132 } 133 134 /** 135 * Finds the first {@link WebElement} using the given method, with a timeout. 136 * 137 * @param by the locating mechanism 138 * @param timeout the timeout in milliseconds 139 * @return the first matching element on the current page, if found 140 * @throws NoSuchElementException when not found 141 */ 142 public static WebElement findElementWithTimeout(By by) throws NoSuchElementException { 143 return findElementWithTimeout(by, AbstractTest.LOAD_TIMEOUT_SECONDS * 1000); 144 } 145 146 /** 147 * Finds the first {@link WebElement} using the given method, with a timeout. 148 * 149 * @param by the locating mechanism 150 * @param timeout the timeout in milliseconds 151 * @return the first matching element on the current page, if found 152 * @throws NoSuchElementException when not found 153 */ 154 public static WebElement findElementWithTimeout(By by, int timeout) throws NoSuchElementException { 155 return findElementWithTimeout(by, timeout, null); 156 } 157 158 /** 159 * Finds the first {@link WebElement} using the given method, with a timeout. 160 * 161 * @param by the locating mechanism 162 * @param timeout the timeout in milliseconds 163 * @param parentElement find from the element 164 * @return the first matching element on the current page, if found 165 * @throws NoSuchElementException when not found 166 */ 167 public static WebElement findElementWithTimeout(final By by, int timeout, final WebElement parentElement) 168 throws NoSuchElementException { 169 Wait<WebDriver> wait = new FluentWait<WebDriver>(AbstractTest.driver).withTimeout(timeout, 170 TimeUnit.MILLISECONDS).pollingEvery(AbstractTest.POLLING_FREQUENCY_MILLISECONDS, TimeUnit.MILLISECONDS).ignoring( 171 StaleElementReferenceException.class); 172 try { 173 return wait.until(new Function<WebDriver, WebElement>() { 174 @Override 175 public WebElement apply(WebDriver driver) { 176 try { 177 if (parentElement == null) { 178 return driver.findElement(by); 179 } else { 180 return parentElement.findElement(by); 181 } 182 } catch (NoSuchElementException e) { 183 return null; 184 } 185 } 186 }); 187 } catch (TimeoutException e) { 188 throw new NoSuchElementException(String.format("Couldn't find element '%s' after timeout", by)); 189 } 190 } 191 192 /** 193 * Finds the first {@link WebElement} using the given method, with a timeout. 194 * 195 * @param by the locating mechanism 196 * @param timeout the timeout in milliseconds 197 * @param parentElement find from the element 198 * @return the first matching element on the current page, if found 199 * @throws NoSuchElementException when not found 200 */ 201 public static WebElement findElementWithTimeout(By by, WebElement parentElement) throws NoSuchElementException { 202 return findElementWithTimeout(by, AbstractTest.LOAD_TIMEOUT_SECONDS * 1000, parentElement); 203 } 204 205 /** 206 * Fluent wait for text to be not present in the given element. 207 * 208 * @since 5.7.3 209 */ 210 public static void waitForTextNotPresent(final WebElement element, final String text) { 211 Wait<WebDriver> wait = new FluentWait<WebDriver>(AbstractTest.driver).withTimeout( 212 AbstractTest.LOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS).pollingEvery( 213 AbstractTest.POLLING_FREQUENCY_MILLISECONDS, TimeUnit.MILLISECONDS); 214 wait.until((new Function<WebDriver, Boolean>() { 215 @Override 216 public Boolean apply(WebDriver driver) { 217 try { 218 return !element.getText().contains(text); 219 } catch (StaleElementReferenceException e) { 220 return null; 221 } 222 } 223 })); 224 } 225 226 /** 227 * Fluent wait for text to be present in the element retrieved with the given method. 228 * 229 * @since 5.7.3 230 */ 231 public static void waitForTextPresent(By locator, String text) { 232 Wait<WebDriver> wait = new FluentWait<WebDriver>(AbstractTest.driver).withTimeout( 233 AbstractTest.LOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS).pollingEvery( 234 AbstractTest.POLLING_FREQUENCY_MILLISECONDS, TimeUnit.MILLISECONDS); 235 wait.until(ExpectedConditions.textToBePresentInElement(locator, text)); 236 } 237 238 /** 239 * Fluent wait for text to be present in the given element. 240 * 241 * @since 5.7.3 242 */ 243 public static void waitForTextPresent(final WebElement element, final String text) { 244 Wait<WebDriver> wait = new FluentWait<WebDriver>(AbstractTest.driver).withTimeout( 245 AbstractTest.LOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS).pollingEvery( 246 AbstractTest.POLLING_FREQUENCY_MILLISECONDS, TimeUnit.MILLISECONDS); 247 wait.until((new Function<WebDriver, Boolean>() { 248 @Override 249 public Boolean apply(WebDriver driver) { 250 try { 251 return element.getText().contains(text); 252 } catch (StaleElementReferenceException e) { 253 return null; 254 } 255 } 256 })); 257 } 258 259 /** 260 * Finds the first {@link WebElement} using the given method, with a {@code findElementTimeout}. Then waits until 261 * the element is enabled, with a {@code waitUntilEnabledTimeout}. Then clicks on the element. 262 * 263 * @param by the locating mechanism 264 * @param findElementTimeout the find element timeout in milliseconds 265 * @param waitUntilEnabledTimeout the wait until enabled timeout in milliseconds 266 * @throws NotFoundException if the element is not found or not enabled 267 */ 268 public static void waitUntilElementEnabledAndClick(final By by, final int findElementTimeout, 269 final int waitUntilEnabledTimeout) throws NotFoundException { 270 271 waitUntilGivenFunctionIgnoring(new Function<WebDriver, Boolean>() { 272 @Override 273 public Boolean apply(WebDriver driver) { 274 // Find the element. 275 WebElement element = findElementAndWaitUntilEnabled(by, findElementTimeout, waitUntilEnabledTimeout); 276 277 element.click(); 278 return true; 279 } 280 }, StaleElementReferenceException.class); 281 } 282 283 /** 284 * Finds the first {@link WebElement} using the given method, with a {@code findElementTimeout}. Then clicks on the 285 * element. 286 * 287 * @param by the locating mechanism 288 * @throws NotFoundException if the element is not found or not enabled 289 * @since 5.9.4 290 */ 291 public static void findElementWithTimeoutAndClick(final By by) throws NotFoundException { 292 293 waitUntilGivenFunctionIgnoring(new Function<WebDriver, Boolean>() { 294 @Override 295 public Boolean apply(WebDriver driver) { 296 // Find the element. 297 WebElement element = findElementWithTimeout(by); 298 299 element.click(); 300 return true; 301 } 302 }, StaleElementReferenceException.class); 303 } 304 305 /** 306 * Fluent wait for an element not to be present, checking every 100 ms. 307 * 308 * @since 5.7.2 309 */ 310 public static void waitUntilElementNotPresent(final By locator) { 311 Wait<WebDriver> wait = new FluentWait<WebDriver>(AbstractTest.driver).withTimeout( 312 AbstractTest.LOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS).pollingEvery( 313 AbstractTest.POLLING_FREQUENCY_MILLISECONDS, TimeUnit.MILLISECONDS); 314 wait.until((new Function<WebDriver, By>() { 315 @Override 316 public By apply(WebDriver driver) { 317 try { 318 driver.findElement(locator); 319 } catch (NoSuchElementException ex) { 320 // ok 321 return locator; 322 } 323 return null; 324 } 325 })); 326 } 327 328 /** 329 * Fluent wait for an element to be present, checking every 100 ms. 330 * 331 * @since 5.7.2 332 */ 333 public static void waitUntilElementPresent(final By locator) { 334 Wait<WebDriver> wait = new FluentWait<WebDriver>(AbstractTest.driver).withTimeout( 335 AbstractTest.LOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS).pollingEvery( 336 AbstractTest.POLLING_FREQUENCY_MILLISECONDS, TimeUnit.MILLISECONDS).ignoring( 337 NoSuchElementException.class); 338 wait.until(new Function<WebDriver, WebElement>() { 339 @Override 340 public WebElement apply(WebDriver driver) { 341 return driver.findElement(locator); 342 } 343 }); 344 } 345 346 /** 347 * Waits until an element is enabled, with a timeout. 348 * 349 * @param element the element 350 */ 351 public static void waitUntilEnabled(WebElement element) throws NotFoundException { 352 waitUntilEnabled(element, AbstractTest.AJAX_TIMEOUT_SECONDS * 1000); 353 } 354 355 /** 356 * Waits until an element is enabled, with a timeout. 357 * 358 * @param element the element 359 * @param timeout the timeout in milliseconds 360 */ 361 public static void waitUntilEnabled(final WebElement element, int timeout) throws NotFoundException { 362 Wait<WebDriver> wait = new FluentWait<WebDriver>(AbstractTest.driver).withTimeout(timeout, 363 TimeUnit.MILLISECONDS).pollingEvery(AbstractTest.POLLING_FREQUENCY_MILLISECONDS, TimeUnit.MILLISECONDS); 364 Function<WebDriver, Boolean> function = new Function<WebDriver, Boolean>() { 365 @Override 366 public Boolean apply(WebDriver driver) { 367 return element.isEnabled(); 368 } 369 }; 370 try { 371 wait.until(function); 372 } catch (TimeoutException e) { 373 throw new NotFoundException("Element not enabled after timeout: " + element); 374 } 375 } 376 377 /** 378 * Fluent wait on a the given function, checking every 100 ms. 379 * 380 * @param function 381 * @since 5.9.2 382 */ 383 public static void waitUntilGivenFunction(Function<WebDriver, Boolean> function) { 384 waitUntilGivenFunctionIgnoring(function, null); 385 } 386 387 /** 388 * Fluent wait on a the given function, checking every 100 ms. 389 * 390 * @param function 391 * @param ignoredExceptions the types of exceptions to ignore. 392 * @since 5.9.2 393 */ 394 public static <K extends java.lang.Throwable> void waitUntilGivenFunctionIgnoreAll( 395 Function<WebDriver, Boolean> function, java.lang.Class<? extends K>... ignoredExceptions) { 396 FluentWait<WebDriver> wait = new FluentWait<WebDriver>(AbstractTest.driver).withTimeout( 397 AbstractTest.LOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS).pollingEvery( 398 AbstractTest.POLLING_FREQUENCY_MILLISECONDS, TimeUnit.MILLISECONDS); 399 if (ignoredExceptions != null) { 400 if (ignoredExceptions.length == 1) { 401 wait.ignoring(ignoredExceptions[0]); 402 } else { 403 wait.ignoreAll(Arrays.asList(ignoredExceptions)); 404 } 405 406 } 407 wait.until(function); 408 } 409 410 /** 411 * Fluent wait on a the given function, checking every 100 ms. 412 * 413 * @param function 414 * @param ignoredException the type of exception to ignore. 415 * @since 5.9.2 416 */ 417 public static <K extends java.lang.Throwable> void waitUntilGivenFunctionIgnoring( 418 Function<WebDriver, Boolean> function, java.lang.Class<? extends K> ignoredException) { 419 FluentWait<WebDriver> wait = new FluentWait<WebDriver>(AbstractTest.driver).withTimeout( 420 AbstractTest.LOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS).pollingEvery( 421 AbstractTest.POLLING_FREQUENCY_MILLISECONDS, TimeUnit.MILLISECONDS); 422 if (ignoredException != null) { 423 wait.ignoring(ignoredException); 424 } 425 wait.until(function); 426 } 427 428 /** 429 * Waits until the URL contains the string given in parameter, with a timeout. 430 * 431 * @param string the string that is to be contained 432 * @since 5.9.2 433 */ 434 public static void waitUntilURLContains(String string) { 435 waitUntilURLContainsOrNot(string, true); 436 } 437 438 /** 439 * @since 5.9.2 440 */ 441 private static void waitUntilURLContainsOrNot(String string, final boolean contain) { 442 final String refurl = string; 443 ExpectedCondition<Boolean> condition = new ExpectedCondition<Boolean>() { 444 @Override 445 public Boolean apply(WebDriver d) { 446 String currentUrl = d.getCurrentUrl(); 447 boolean result = !(currentUrl.contains(refurl) ^ contain); 448 if (!result) { 449 AbstractTest.log.debug("currentUrl is : " + currentUrl); 450 AbstractTest.log.debug((contain ? "It should contains : " : "It should not contains : ") + refurl); 451 } 452 return result; 453 } 454 }; 455 WebDriverWait wait = new WebDriverWait(AbstractTest.driver, URLCHANGE_MAX_WAIT); 456 wait.until(condition); 457 } 458 459 /** 460 * Waits until the URL is different from the one given in parameter, with a timeout. 461 * 462 * @param url the URL to compare to 463 */ 464 public static void waitUntilURLDifferentFrom(String url) { 465 final String refurl = url; 466 ExpectedCondition<Boolean> urlchanged = new ExpectedCondition<Boolean>() { 467 @Override 468 public Boolean apply(WebDriver d) { 469 String currentUrl = d.getCurrentUrl(); 470 AbstractTest.log.debug("currentUrl is still: " + currentUrl); 471 return !currentUrl.equals(refurl); 472 } 473 }; 474 WebDriverWait wait = new WebDriverWait(AbstractTest.driver, URLCHANGE_MAX_WAIT); 475 wait.until(urlchanged); 476 if (AbstractTest.driver.getCurrentUrl().equals(refurl)) { 477 log.warn("Page change failed"); 478 } 479 } 480 481 /** 482 * Waits until the URL does not contain the string given in parameter, with a timeout. 483 * 484 * @param string the string that is not to be contained 485 * @since 5.9.2 486 */ 487 public static void waitUntilURLNotContain(String string) { 488 waitUntilURLContainsOrNot(string, false); 489 } 490 491 /** 492 * Return parent element with given tag name. 493 * <p> 494 * Throws a {@link NoSuchElementException} error if no element found. 495 * 496 * @since 7.3 497 */ 498 public static WebElement findParentTag(WebElement elt, String tagName) { 499 try { 500 By parentBy = By.xpath(".."); 501 WebElement p = elt.findElement(parentBy); 502 while (p != null) { 503 if (tagName.equals(p.getTagName())) { 504 return p; 505 } 506 p = p.findElement(parentBy); 507 } 508 } catch (InvalidSelectorException e) { 509 } 510 throw new NoSuchElementException(String.format("No parent element found with tag %s.", tagName)); 511 } 512}