001/* 002 * (C) Copyright 2016 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 * Yannis JULIENNE 019 */ 020package org.nuxeo.functionaltests; 021 022import java.util.Arrays; 023import java.util.List; 024import java.util.concurrent.TimeUnit; 025 026import org.apache.commons.logging.Log; 027import org.apache.commons.logging.LogFactory; 028import org.openqa.selenium.By; 029import org.openqa.selenium.InvalidSelectorException; 030import org.openqa.selenium.JavascriptExecutor; 031import org.openqa.selenium.NoSuchElementException; 032import org.openqa.selenium.NotFoundException; 033import org.openqa.selenium.StaleElementReferenceException; 034import org.openqa.selenium.TimeoutException; 035import org.openqa.selenium.WebDriver; 036import org.openqa.selenium.WebDriverException; 037import org.openqa.selenium.WebElement; 038import org.openqa.selenium.support.ui.ExpectedCondition; 039import org.openqa.selenium.support.ui.ExpectedConditions; 040import org.openqa.selenium.support.ui.FluentWait; 041import org.openqa.selenium.support.ui.Wait; 042import org.openqa.selenium.support.ui.WebDriverWait; 043 044import com.google.common.base.Function; 045 046/** 047 * Helper class providing find and wait methods with or without timeout. When requiring timeout, the polling frequency 048 * is every 100 milliseconds if not specified. 049 * 050 * @since 5.9.2 051 */ 052public class Locator { 053 054 private static final Log log = LogFactory.getLog(Locator.class); 055 056 // Timeout for waitUntilURLDifferentFrom in seconds 057 public static int URLCHANGE_MAX_WAIT = 30; 058 059 public static WebElement findElement(By by) { 060 return AbstractTest.driver.findElement(by); 061 } 062 063 /** 064 * Finds the first {@link WebElement} using the given method, with the default timeout. Then waits until the element 065 * is enabled, with the default timeout. 066 * 067 * @param by the locating mechanism 068 * @return the first matching element on the current page, if found 069 * @throws NotFoundException if the element is not found or not enabled 070 */ 071 public static WebElement findElementAndWaitUntilEnabled(By by) throws NotFoundException { 072 return findElementAndWaitUntilEnabled(by, AbstractTest.LOAD_TIMEOUT_SECONDS * 1000, 073 AbstractTest.AJAX_TIMEOUT_SECONDS * 1000); 074 } 075 076 /** 077 * Finds the first {@link WebElement} using the given method, with a {@code findElementTimeout}. Then waits until 078 * the element is enabled, with a {@code waitUntilEnabledTimeout}. 079 * 080 * @param by the locating mechanism 081 * @param findElementTimeout the find element timeout in milliseconds 082 * @param waitUntilEnabledTimeout the wait until enabled timeout in milliseconds 083 * @return the first matching element on the current page, if found 084 * @throws NotFoundException if the element is not found or not enabled 085 */ 086 public static WebElement findElementAndWaitUntilEnabled(final By by, final int findElementTimeout, 087 final int waitUntilEnabledTimeout) throws NotFoundException { 088 return findElementAndWaitUntilEnabled(null, by, findElementTimeout, waitUntilEnabledTimeout); 089 } 090 091 /** 092 * Finds the first {@link WebElement} using the given method, with a {@code findElementTimeout}, inside an optional 093 * {@code parentElement}. Then waits until the element is enabled, with a {@code waitUntilEnabledTimeout}. 094 * 095 * @param parentElement the parent element (can be null) 096 * @param by the locating mechanism 097 * @param findElementTimeout the find element timeout in milliseconds 098 * @param waitUntilEnabledTimeout the wait until enabled timeout in milliseconds 099 * @return the first matching element on the current page, if found, with optional parent element 100 * @throws NotFoundException if the element is not found or not enabled 101 * @since 8.3 102 */ 103 public static WebElement findElementAndWaitUntilEnabled(WebElement parentElement, final By by, 104 final int findElementTimeout, final int waitUntilEnabledTimeout) throws NotFoundException { 105 Wait<WebDriver> wait = getFluentWait(); 106 Function<WebDriver, WebElement> function = new Function<WebDriver, WebElement>() { 107 @Override 108 public WebElement apply(WebDriver driver) { 109 WebElement element = null; 110 try { 111 // Find the element. 112 element = findElementWithTimeout(by, findElementTimeout, parentElement); 113 114 // Try to wait until the element is enabled. 115 waitUntilEnabled(element, waitUntilEnabledTimeout); 116 } catch (StaleElementReferenceException sere) { 117 AbstractTest.log.debug("StaleElementReferenceException: " + sere.getMessage()); 118 return null; 119 } 120 return element; 121 } 122 }; 123 124 return wait.until(function); 125 126 } 127 128 public static List<WebElement> findElementsWithTimeout(final By by) throws NoSuchElementException { 129 FluentWait<WebDriver> wait = getFluentWait(); 130 wait.ignoring(NoSuchElementException.class); 131 return wait.until(new Function<WebDriver, List<WebElement>>() { 132 @Override 133 public List<WebElement> apply(WebDriver driver) { 134 List<WebElement> elements = driver.findElements(by); 135 return elements.isEmpty() ? null : elements; 136 } 137 }); 138 } 139 140 /** 141 * Finds the first {@link WebElement} using the given method, with the default timeout. Then waits until the element 142 * is enabled, with the default timeout. Then clicks on the element. 143 * 144 * @param by the locating mechanism 145 * @throws NotFoundException if the element is not found or not enabled 146 */ 147 public static void findElementWaitUntilEnabledAndClick(By by) throws NotFoundException { 148 findElementWaitUntilEnabledAndClick(null, by, AbstractTest.LOAD_TIMEOUT_SECONDS * 1000, 149 AbstractTest.AJAX_TIMEOUT_SECONDS * 1000); 150 } 151 152 /** 153 * Finds the first {@link WebElement} using the given method, with the default timeout, inside an optional 154 * {@code parentElement}. Then waits until the element is enabled, with the default timeout. Then clicks on the 155 * element. 156 * 157 * @param parentElement the parent element (can be null) 158 * @param by the locating mechanism 159 * @throws NotFoundException if the element is not found or not enabled 160 * @since 8.3 161 */ 162 public static void findElementWaitUntilEnabledAndClick(WebElement parentElement, By by) throws NotFoundException { 163 findElementWaitUntilEnabledAndClick(parentElement, by, AbstractTest.LOAD_TIMEOUT_SECONDS * 1000, 164 AbstractTest.AJAX_TIMEOUT_SECONDS * 1000); 165 } 166 167 /** 168 * Finds the first {@link WebElement} using the given method, with a timeout. 169 * 170 * @param by the locating mechanism 171 * @param timeout the timeout in milliseconds 172 * @return the first matching element on the current page, if found 173 * @throws NoSuchElementException when not found 174 */ 175 public static WebElement findElementWithTimeout(By by) throws NoSuchElementException { 176 return findElementWithTimeout(by, AbstractTest.LOAD_TIMEOUT_SECONDS * 1000); 177 } 178 179 /** 180 * Finds the first {@link WebElement} using the given method, with a timeout. 181 * 182 * @param by the locating mechanism 183 * @param timeout the timeout in milliseconds 184 * @return the first matching element on the current page, if found 185 * @throws NoSuchElementException when not found 186 */ 187 public static WebElement findElementWithTimeout(By by, int timeout) throws NoSuchElementException { 188 return findElementWithTimeout(by, timeout, null); 189 } 190 191 /** 192 * Finds the first {@link WebElement} using the given method, with a timeout. 193 * 194 * @param by the locating mechanism 195 * @param timeout the timeout in milliseconds 196 * @param parentElement find from the element 197 * @return the first matching element on the current page, if found 198 * @throws NoSuchElementException when not found 199 */ 200 public static WebElement findElementWithTimeout(final By by, int timeout, final WebElement parentElement) 201 throws NoSuchElementException { 202 FluentWait<WebDriver> wait = getFluentWait(); 203 wait.withTimeout(timeout, TimeUnit.MILLISECONDS).ignoring(StaleElementReferenceException.class); 204 try { 205 return wait.until(new Function<WebDriver, WebElement>() { 206 @Override 207 public WebElement apply(WebDriver driver) { 208 try { 209 if (parentElement == null) { 210 return driver.findElement(by); 211 } else { 212 return parentElement.findElement(by); 213 } 214 } catch (NoSuchElementException e) { 215 return null; 216 } 217 } 218 }); 219 } catch (TimeoutException e) { 220 throw new NoSuchElementException(String.format("Couldn't find element '%s' after timeout", by)); 221 } 222 } 223 224 /** 225 * Finds the first {@link WebElement} using the given method, with a timeout. 226 * 227 * @param by the locating mechanism 228 * @param timeout the timeout in milliseconds 229 * @param parentElement find from the element 230 * @return the first matching element on the current page, if found 231 * @throws NoSuchElementException when not found 232 */ 233 public static WebElement findElementWithTimeout(By by, WebElement parentElement) throws NoSuchElementException { 234 return findElementWithTimeout(by, AbstractTest.LOAD_TIMEOUT_SECONDS * 1000, parentElement); 235 } 236 237 public static FluentWait<WebDriver> getFluentWait() { 238 FluentWait<WebDriver> wait = new FluentWait<WebDriver>(AbstractTest.driver); 239 wait.withTimeout(AbstractTest.LOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS) 240 .pollingEvery(AbstractTest.POLLING_FREQUENCY_MILLISECONDS, TimeUnit.MILLISECONDS); 241 return wait; 242 } 243 244 /** 245 * Fluent wait for text to be not present in the given element. 246 * 247 * @since 5.7.3 248 */ 249 public static void waitForTextNotPresent(final WebElement element, final String text) { 250 Wait<WebDriver> wait = getFluentWait(); 251 wait.until((new Function<WebDriver, Boolean>() { 252 @Override 253 public Boolean apply(WebDriver driver) { 254 try { 255 return !element.getText().contains(text); 256 } catch (StaleElementReferenceException e) { 257 return null; 258 } 259 } 260 })); 261 } 262 263 /** 264 * Fluent wait for text to be present in the element retrieved with the given method. 265 * 266 * @since 5.7.3 267 */ 268 public static void waitForTextPresent(By locator, String text) { 269 Wait<WebDriver> wait = getFluentWait(); 270 wait.until(ExpectedConditions.textToBePresentInElementLocated(locator, text)); 271 } 272 273 /** 274 * Fluent wait for text to be present in the given element. 275 * 276 * @since 5.7.3 277 */ 278 public static void waitForTextPresent(final WebElement element, final String text) { 279 Wait<WebDriver> wait = getFluentWait(); 280 wait.until((new Function<WebDriver, Boolean>() { 281 @Override 282 public Boolean apply(WebDriver driver) { 283 try { 284 return element.getText().contains(text); 285 } catch (StaleElementReferenceException e) { 286 return null; 287 } 288 } 289 })); 290 } 291 292 /** 293 * Finds the first {@link WebElement} using the given method, with a {@code findElementTimeout}. Then waits until 294 * the element is enabled, with a {@code waitUntilEnabledTimeout}. Scroll to it, then clicks on the element. 295 * 296 * @param by the locating mechanism 297 * @param findElementTimeout the find element timeout in milliseconds 298 * @param waitUntilEnabledTimeout the wait until enabled timeout in milliseconds 299 * @throws NotFoundException if the element is not found or not enabled 300 * @deprecated since 8.3, use {@link #findElementWaitUntilEnabledAndClick(WebElement, By)} 301 */ 302 @Deprecated 303 public static void findElementWaitUntilEnabledAndClick(final By by, final int findElementTimeout, 304 final int waitUntilEnabledTimeout) throws NotFoundException { 305 findElementWaitUntilEnabledAndClick(null, by, findElementTimeout, waitUntilEnabledTimeout); 306 } 307 308 /** 309 * Finds the first {@link WebElement} using the given method, with a {@code findElementTimeout}, inside an optional 310 * {@code parentElement}. Then waits until the element is enabled, with a {@code waitUntilEnabledTimeout}. Scroll to 311 * it, then clicks on the element. 312 * 313 * @param parentElement the parent element (can be null) 314 * @param by the locating mechanism 315 * @param findElementTimeout the find element timeout in milliseconds 316 * @param waitUntilEnabledTimeout the wait until enabled timeout in milliseconds 317 * @throws NotFoundException if the element is not found or not enabled 318 * @since 8.3 319 */ 320 public static void findElementWaitUntilEnabledAndClick(WebElement parentElement, final By by, 321 final int findElementTimeout, final int waitUntilEnabledTimeout) throws NotFoundException { 322 WebElement element = findElementAndWaitUntilEnabled(parentElement, by, findElementTimeout, 323 waitUntilEnabledTimeout); 324 waitUntilGivenFunctionIgnoring(new Function<WebDriver, Boolean>() { 325 @Override 326 public Boolean apply(WebDriver driver) { 327 return scrollAndForceClick(element); 328 } 329 }, StaleElementReferenceException.class); 330 } 331 332 /** 333 * Waits until the element is enabled, with a default timeout. Then clicks on the element. 334 * 335 * @param element the element 336 * @throws NotFoundException if the element is not found or not enabled 337 * @since 8.3 338 */ 339 public static void waitUntilEnabledAndClick(final WebElement element) throws NotFoundException { 340 waitUntilEnabledAndClick(element, AbstractTest.AJAX_TIMEOUT_SECONDS * 1000); 341 } 342 343 /** 344 * Waits until the element is enabled, with a {@code waitUntilEnabledTimeout}. Scroll to it, then clicks on the 345 * element. 346 * 347 * @param element the element 348 * @param waitUntilEnabledTimeout the wait until enabled timeout in milliseconds 349 * @throws NotFoundException if the element is not found or not enabled 350 * @since 8.3 351 */ 352 public static void waitUntilEnabledAndClick(final WebElement element, final int waitUntilEnabledTimeout) 353 throws NotFoundException { 354 waitUntilEnabled(element, waitUntilEnabledTimeout); 355 waitUntilGivenFunctionIgnoring(new Function<WebDriver, Boolean>() { 356 @Override 357 public Boolean apply(WebDriver driver) { 358 return scrollAndForceClick(element); 359 } 360 }, StaleElementReferenceException.class); 361 } 362 363 /** 364 * Finds the first {@link WebElement} using the given method, with a {@code findElementTimeout}. Then clicks on the 365 * element. 366 * 367 * @param by the locating mechanism 368 * @throws NotFoundException if the element is not found or not enabled 369 * @since 5.9.4 370 */ 371 public static void findElementWithTimeoutAndClick(final By by) throws NotFoundException { 372 373 waitUntilGivenFunctionIgnoring(new Function<WebDriver, Boolean>() { 374 @Override 375 public Boolean apply(WebDriver driver) { 376 // Find the element. 377 WebElement element = findElementWithTimeout(by); 378 379 element.click(); 380 return true; 381 } 382 }, StaleElementReferenceException.class); 383 } 384 385 /** 386 * Fluent wait for an element not to be present, checking every 100 ms. 387 * 388 * @since 5.7.2 389 */ 390 public static void waitUntilElementNotPresent(final By locator) { 391 Wait<WebDriver> wait = getFluentWait(); 392 wait.until((new Function<WebDriver, By>() { 393 @Override 394 public By apply(WebDriver driver) { 395 try { 396 driver.findElement(locator); 397 } catch (NoSuchElementException ex) { 398 // ok 399 return locator; 400 } 401 return null; 402 } 403 })); 404 } 405 406 /** 407 * Fluent wait for an element to be present, checking every 100 ms. 408 * 409 * @since 5.7.2 410 */ 411 public static void waitUntilElementPresent(final By locator) { 412 FluentWait<WebDriver> wait = getFluentWait(); 413 wait.ignoring(NoSuchElementException.class); 414 wait.until(new Function<WebDriver, WebElement>() { 415 @Override 416 public WebElement apply(WebDriver driver) { 417 return driver.findElement(locator); 418 } 419 }); 420 } 421 422 /** 423 * Waits until an element is enabled, with a timeout. 424 * 425 * @param element the element 426 */ 427 public static void waitUntilEnabled(WebElement element) throws NotFoundException { 428 waitUntilEnabled(element, AbstractTest.AJAX_TIMEOUT_SECONDS * 1000); 429 } 430 431 /** 432 * Waits until an element is enabled, with a timeout. 433 * 434 * @param element the element 435 * @param timeout the timeout in milliseconds 436 */ 437 public static void waitUntilEnabled(final WebElement element, int timeout) throws NotFoundException { 438 FluentWait<WebDriver> wait = getFluentWait(); 439 wait.withTimeout(timeout, TimeUnit.MILLISECONDS); 440 Function<WebDriver, Boolean> function = new Function<WebDriver, Boolean>() { 441 @Override 442 public Boolean apply(WebDriver driver) { 443 return element.isEnabled(); 444 } 445 }; 446 try { 447 wait.until(function); 448 } catch (TimeoutException e) { 449 throw new NotFoundException("Element not enabled after timeout: " + element); 450 } 451 } 452 453 /** 454 * Fluent wait on a the given function, checking every 100 ms. 455 * 456 * @param function 457 * @since 5.9.2 458 */ 459 public static void waitUntilGivenFunction(Function<WebDriver, Boolean> function) { 460 waitUntilGivenFunctionIgnoring(function, null); 461 } 462 463 /** 464 * Fluent wait on a the given function, checking every 100 ms. 465 * 466 * @param function 467 * @param ignoredExceptions the types of exceptions to ignore. 468 * @since 5.9.2 469 */ 470 @SafeVarargs 471 public static <K extends java.lang.Throwable> void waitUntilGivenFunctionIgnoreAll( 472 Function<WebDriver, Boolean> function, java.lang.Class<? extends K>... ignoredExceptions) { 473 FluentWait<WebDriver> wait = getFluentWait(); 474 if (ignoredExceptions != null) { 475 if (ignoredExceptions.length == 1) { 476 wait.ignoring(ignoredExceptions[0]); 477 } else { 478 wait.ignoreAll(Arrays.asList(ignoredExceptions)); 479 } 480 481 } 482 wait.until(function); 483 } 484 485 /** 486 * Fluent wait on a the given function, checking every 100 ms. 487 * 488 * @param function 489 * @param ignoredException the type of exception to ignore. 490 * @since 5.9.2 491 */ 492 public static <K extends java.lang.Throwable> void waitUntilGivenFunctionIgnoring( 493 Function<WebDriver, Boolean> function, java.lang.Class<? extends K> ignoredException) { 494 FluentWait<WebDriver> wait = getFluentWait(); 495 if (ignoredException != null) { 496 wait.ignoring(ignoredException); 497 } 498 wait.until(function); 499 } 500 501 /** 502 * Waits until the URL contains the string given in parameter, with a timeout. 503 * 504 * @param string the string that is to be contained 505 * @since 5.9.2 506 */ 507 public static void waitUntilURLContains(String string) { 508 waitUntilURLContainsOrNot(string, true); 509 } 510 511 /** 512 * @since 5.9.2 513 */ 514 private static void waitUntilURLContainsOrNot(String string, final boolean contain) { 515 final String refurl = string; 516 ExpectedCondition<Boolean> condition = new ExpectedCondition<Boolean>() { 517 @Override 518 public Boolean apply(WebDriver d) { 519 String currentUrl = d.getCurrentUrl(); 520 boolean result = !(currentUrl.contains(refurl) ^ contain); 521 if (!result) { 522 AbstractTest.log.debug("currentUrl is : " + currentUrl); 523 AbstractTest.log.debug((contain ? "It should contains : " : "It should not contains : ") + refurl); 524 } 525 return result; 526 } 527 }; 528 WebDriverWait wait = new WebDriverWait(AbstractTest.driver, URLCHANGE_MAX_WAIT); 529 wait.until(condition); 530 } 531 532 /** 533 * Waits until the URL is different from the one given in parameter, with a timeout. 534 * 535 * @param url the URL to compare to 536 */ 537 public static void waitUntilURLDifferentFrom(String url) { 538 final String refurl = url; 539 ExpectedCondition<Boolean> urlchanged = new ExpectedCondition<Boolean>() { 540 @Override 541 public Boolean apply(WebDriver d) { 542 String currentUrl = d.getCurrentUrl(); 543 AbstractTest.log.debug("currentUrl is still: " + currentUrl); 544 return !currentUrl.equals(refurl); 545 } 546 }; 547 WebDriverWait wait = new WebDriverWait(AbstractTest.driver, URLCHANGE_MAX_WAIT); 548 wait.until(urlchanged); 549 if (AbstractTest.driver.getCurrentUrl().equals(refurl)) { 550 log.warn("Page change failed"); 551 } 552 } 553 554 /** 555 * Waits until the URL does not contain the string given in parameter, with a timeout. 556 * 557 * @param string the string that is not to be contained 558 * @since 5.9.2 559 */ 560 public static void waitUntilURLNotContain(String string) { 561 waitUntilURLContainsOrNot(string, false); 562 } 563 564 /** 565 * Return parent element with given tag name. 566 * <p> 567 * Throws a {@link NoSuchElementException} error if no element found. 568 * 569 * @since 7.3 570 */ 571 public static WebElement findParentTag(WebElement elt, String tagName) { 572 try { 573 By parentBy = By.xpath(".."); 574 WebElement p = elt.findElement(parentBy); 575 while (p != null) { 576 if (tagName.equals(p.getTagName())) { 577 return p; 578 } 579 p = p.findElement(parentBy); 580 } 581 } catch (InvalidSelectorException e) { 582 } 583 throw new NoSuchElementException(String.format("No parent element found with tag %s.", tagName)); 584 } 585 586 /** 587 * Scrolls to the element in the view: allows to safely click on it afterwards. 588 * 589 * @param executor the javascript executor, usually {@link WebDriver} 590 * @param element the element to scroll to 591 * @since 8.3 592 */ 593 public static final void scrollToElement(WebElement element) { 594 ((JavascriptExecutor) AbstractTest.driver).executeScript("arguments[0].scrollIntoView(false);", element); 595 } 596 597 /** 598 * Forces a click on an element, to workaround non-effective clicks in miscellaneous situations, after having 599 * scrolled to it. 600 * 601 * @param executor the javascript executor, usually {@link WebDriver} 602 * @param element the element to scroll to 603 * @return true if element is clickable 604 * @since 8.3 605 */ 606 public static final boolean scrollAndForceClick(WebElement element) { 607 JavascriptExecutor executor = (JavascriptExecutor) AbstractTest.driver; 608 scrollToElement(element); 609 try { 610 // forced click to workaround non-effective clicks in miscellaneous situations 611 executor.executeScript("arguments[0].click();", element); 612 return true; 613 } catch (WebDriverException e) { 614 if (e.getMessage().contains("Element is not clickable at point")) { 615 log.debug("Element is not clickable yet"); 616 return false; 617 } 618 throw e; 619 } 620 } 621 622}