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