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