001/* 002 * (C) Copyright 2011-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 * Sun Seng David TAN 018 * Florent Guillaume 019 * Benoit Delbosc 020 * Antoine Taillefer 021 * Anahide Tchertchian 022 * Guillaume Renard 023 * Mathieu Guillaume 024 * Julien Carsique 025 */ 026package org.nuxeo.functionaltests; 027 028import static org.junit.Assert.assertEquals; 029import static org.junit.Assert.assertTrue; 030import static org.junit.Assume.assumeFalse; 031import static org.nuxeo.functionaltests.Constants.ADMINISTRATOR; 032 033import java.io.File; 034import java.io.IOException; 035import java.lang.reflect.Constructor; 036import java.lang.reflect.Field; 037import java.net.MalformedURLException; 038import java.net.URL; 039import java.util.ArrayList; 040import java.util.List; 041import java.util.concurrent.TimeUnit; 042 043import org.apache.commons.lang.SystemUtils; 044import org.apache.commons.logging.Log; 045import org.apache.commons.logging.LogFactory; 046import org.junit.After; 047import org.junit.AfterClass; 048import org.junit.BeforeClass; 049import org.junit.Rule; 050import org.junit.rules.MethodRule; 051import org.nuxeo.common.utils.FileUtils; 052import org.nuxeo.functionaltests.drivers.ChromeDriverProvider; 053import org.nuxeo.functionaltests.drivers.FirefoxDriverProvider; 054import org.nuxeo.functionaltests.drivers.RemoteFirefoxDriverProvider; 055import org.nuxeo.functionaltests.fragment.WebFragment; 056import org.nuxeo.functionaltests.pages.AbstractPage; 057import org.nuxeo.functionaltests.pages.DocumentBasePage; 058import org.nuxeo.functionaltests.pages.DocumentBasePage.UserNotConnectedException; 059import org.nuxeo.functionaltests.pages.FileDocumentBasePage; 060import org.nuxeo.functionaltests.pages.LoginPage; 061import org.nuxeo.functionaltests.pages.NoteDocumentBasePage; 062import org.nuxeo.functionaltests.pages.tabs.CollectionContentTabSubPage; 063import org.nuxeo.functionaltests.proxy.ProxyManager; 064import org.nuxeo.runtime.api.Framework; 065import org.openqa.selenium.By; 066import org.openqa.selenium.NoSuchElementException; 067import org.openqa.selenium.Proxy; 068import org.openqa.selenium.TimeoutException; 069import org.openqa.selenium.WebDriver; 070import org.openqa.selenium.WebElement; 071import org.openqa.selenium.internal.WrapsElement; 072import org.openqa.selenium.remote.CapabilityType; 073import org.openqa.selenium.remote.Command; 074import org.openqa.selenium.remote.DesiredCapabilities; 075import org.openqa.selenium.remote.DriverCommand; 076import org.openqa.selenium.remote.RemoteWebDriver; 077import org.openqa.selenium.support.PageFactory; 078import org.openqa.selenium.support.ui.FluentWait; 079import org.openqa.selenium.support.ui.Wait; 080 081import com.google.common.collect.ImmutableMap; 082 083/** 084 * Base functions for all pages. 085 */ 086public abstract class AbstractTest { 087 088 /** 089 * @since 5.9.2 090 */ 091 public final static String TEST_USERNAME = "jdoe"; 092 093 /** 094 * @since 5.9.2 095 */ 096 public final static String TEST_PASSWORD = "test"; 097 098 /** 099 * Polling frequency in milliseconds. 100 * 101 * @since 5.9.2 102 */ 103 public static final int POLLING_FREQUENCY_MILLISECONDS = 100; 104 105 public static final int POLLING_FREQUENCY_SECONDS = 1; 106 107 /** 108 * Page Load timeout in seconds. 109 * 110 * @since 5.9.2 111 */ 112 public static final int PAGE_LOAD_TIME_OUT_SECONDS = 60; 113 114 public static final int LOAD_TIMEOUT_SECONDS = 30; 115 116 /** 117 * Driver implicit wait in milliseconds. 118 * 119 * @since 8.3 120 */ 121 public static final int IMPLICIT_WAIT_MILLISECONDS = 200; 122 123 public static final int LOAD_SHORT_TIMEOUT_SECONDS = 2; 124 125 public static final int AJAX_TIMEOUT_SECONDS = 10; 126 127 public static final int AJAX_SHORT_TIMEOUT_SECONDS = 2; 128 129 /** 130 * @since 5.7 131 * @deprecated since 8.3 132 * @see ChromeDriverProvider 133 */ 134 @Deprecated 135 public static final String CHROME_DRIVER_DEFAULT_PATH_LINUX = ChromeDriverProvider.CHROME_DRIVER_DEFAULT_PATH_LINUX; 136 137 /** 138 * @since 5.7 "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" doesn't work 139 * @deprecated since 8.3 140 * @see ChromeDriverProvider 141 */ 142 @Deprecated 143 public static final String CHROME_DRIVER_DEFAULT_PATH_MAC = ChromeDriverProvider.CHROME_DRIVER_DEFAULT_PATH_MAC; 144 145 /** 146 * @since 5.7 147 * @deprecated since 8.3 148 * @see ChromeDriverProvider 149 */ 150 @Deprecated 151 public static final String CHROME_DRIVER_DEFAULT_PATH_WINVISTA = ChromeDriverProvider.CHROME_DRIVER_DEFAULT_PATH_WINVISTA; 152 153 /** 154 * @since 5.7 155 * @deprecated since 8.3 156 * @see ChromeDriverProvider 157 */ 158 @Deprecated 159 public static final String CHROME_DRIVER_DEFAULT_PATH_WINXP = ChromeDriverProvider.CHROME_DRIVER_DEFAULT_PATH_WINXP; 160 161 /** 162 * @since 5.7 163 * @deprecated since 8.3 164 * @see ChromeDriverProvider 165 */ 166 @Deprecated 167 public static final String CHROME_DRIVER_DEFAULT_EXECUTABLE_NAME = ChromeDriverProvider.CHROME_DRIVER_DEFAULT_EXECUTABLE_NAME; 168 169 /** 170 * @since 5.7 171 * @deprecated since 8.3 172 * @see ChromeDriverProvider 173 */ 174 @Deprecated 175 public static final String CHROME_DRIVER_WINDOWS_EXECUTABLE_NAME = ChromeDriverProvider.CHROME_DRIVER_WINDOWS_EXECUTABLE_NAME; 176 177 /** 178 * @deprecated since 8.3 179 * @see ChromeDriverProvider 180 */ 181 @Deprecated 182 public static final String SYSPROP_CHROME_DRIVER_PATH = ChromeDriverProvider.SYSPROP_CHROME_DRIVER_PATH; 183 184 static final Log log = LogFactory.getLog(AbstractTest.class); 185 186 public static final String NUXEO_URL = System.getProperty("nuxeoURL", "http://localhost:8080/nuxeo") 187 .replaceAll("/$", ""); 188 189 public static RemoteWebDriver driver; 190 191 protected static ProxyManager proxyManager; 192 193 /** 194 * Logger method to follow what's being run on server logs and take a screenshot of the last page in case of failure 195 */ 196 @Rule 197 public MethodRule watchman = new LogTestWatchman(driver, NUXEO_URL); 198 199 /** 200 * This method will be executed before any method registered with JUnit After annotation. 201 * 202 * @since 5.8 203 */ 204 public void runBeforeAfters() { 205 ((LogTestWatchman) watchman).runBeforeAfters(); 206 } 207 208 @BeforeClass 209 public static void initDriver() throws Exception { 210 String browser = System.getProperty("browser", "firefox"); 211 // Use the same strings as command-line Selenium 212 if (browser.equals("chrome") || browser.equals("firefox")) { 213 initFirefoxDriver(); 214 } else if (browser.equals("remotefirefox")) { 215 initRemoteFirefoxDriver(); 216 } else if (browser.equals("googlechrome")) { 217 initChromeDriver(); 218 } else { 219 throw new RuntimeException("Browser not supported: " + browser); 220 } 221 driver.manage().timeouts().pageLoadTimeout(PAGE_LOAD_TIME_OUT_SECONDS, TimeUnit.SECONDS); 222 driver.manage().timeouts().implicitlyWait(IMPLICIT_WAIT_MILLISECONDS, TimeUnit.MILLISECONDS); 223 } 224 225 protected static void initFirefoxDriver() throws Exception { 226 proxyManager = new ProxyManager(); 227 Proxy proxy = proxyManager.startProxy(); 228 if (proxy != null) { 229 proxy.setNoProxy(""); 230 } 231 DesiredCapabilities dc = DesiredCapabilities.firefox(); 232 dc.setCapability(CapabilityType.PROXY, proxy); 233 driver = new FirefoxDriverProvider().init(dc); 234 } 235 236 protected static void initRemoteFirefoxDriver() throws Exception { 237 proxyManager = new ProxyManager(); 238 Proxy proxy = proxyManager.startProxy(); 239 if (proxy != null) { 240 proxy.setNoProxy(""); 241 } 242 DesiredCapabilities dc = DesiredCapabilities.firefox(); 243 dc.setCapability(CapabilityType.PROXY, proxy); 244 driver = new RemoteFirefoxDriverProvider().init(dc); 245 } 246 247 protected static void initChromeDriver() throws Exception { 248 proxyManager = new ProxyManager(); 249 Proxy proxy = proxyManager.startProxy(); 250 DesiredCapabilities dc = DesiredCapabilities.chrome(); 251 if (proxy != null) { 252 proxy.setNoProxy(""); 253 dc.setCapability(CapabilityType.PROXY, proxy); 254 } 255 driver = new ChromeDriverProvider().init(dc); 256 } 257 258 /** 259 * @since 7.1 260 */ 261 @After 262 public void checkJavascriptError() { 263 if (driver != null) { 264 new JavaScriptErrorCollector(driver).checkForErrors(); 265 } 266 } 267 268 @AfterClass 269 public static void quitDriver() { 270 if (driver != null) { 271 driver.quit(); 272 driver = null; 273 } 274 275 try { 276 proxyManager.stopProxy(); 277 proxyManager = null; 278 } catch (Exception e) { 279 log.error("Could not stop proxy: " + e.getMessage()); 280 } 281 } 282 283 public static <T> T get(String url, Class<T> pageClassToProxy) { 284 if (driver != null) { 285 new JavaScriptErrorCollector(driver).checkForErrors(); 286 } 287 driver.get(url); 288 return asPage(pageClassToProxy); 289 } 290 291 /** 292 * Opens given url adding hardcoded Seam conversation named "0NXMAIN". 293 * 294 * @since 8.3 295 */ 296 public static void open(String url) { 297 if (driver != null) { 298 new JavaScriptErrorCollector(driver).checkForErrors(); 299 } 300 driver.get(NUXEO_URL + url + "?conversationId=0NXMAIN"); 301 } 302 303 /** 304 * Do not wait for page load. Do not handle error. Do not give explicit error in case of failure. This is a very raw 305 * get. 306 * 307 * @since 6.0 308 */ 309 public static <T> T getWithoutErrorHandler(String url, Class<T> pageClassToProxy) throws IOException { 310 Command command = new Command(AbstractTest.driver.getSessionId(), DriverCommand.GET, 311 ImmutableMap.of("url", url)); 312 AbstractTest.driver.getCommandExecutor().execute(command); 313 return asPage(pageClassToProxy); 314 } 315 316 public static WebDriver getPopup() { 317 String currentWindow = driver.getWindowHandle(); 318 for (String popup : driver.getWindowHandles()) { 319 if (popup.equals(currentWindow)) { 320 continue; 321 } 322 return driver.switchTo().window(popup); 323 } 324 return null; 325 } 326 327 public static <T> T asPage(Class<T> pageClassToProxy) { 328 T page = instantiatePage(pageClassToProxy); 329 return fillElement(pageClassToProxy, page); 330 } 331 332 public static <T extends WebFragment> T getWebFragment(By by, Class<T> webFragmentClass) { 333 WebElement element = Locator.findElementWithTimeout(by); 334 return getWebFragment(element, webFragmentClass); 335 } 336 337 public static <T extends WebFragment> T getWebFragment(WebElement element, Class<T> webFragmentClass) { 338 T webFragment = instantiateWebFragment(element, webFragmentClass); 339 webFragment = fillElement(webFragmentClass, webFragment); 340 // fillElement somehow overwrite the 'element' field, reset it. 341 webFragment.setElement(element); 342 return webFragment; 343 } 344 345 /** 346 * Fills an instantiated page/form/widget attributes 347 * 348 * @since 5.7 349 */ 350 public static <T> T fillElement(Class<T> pageClassToProxy, T page) { 351 PageFactory.initElements(new VariableElementLocatorFactory(driver, AJAX_TIMEOUT_SECONDS), page); 352 // check all required WebElements on the page and wait for their 353 // loading 354 final List<String> fieldNames = new ArrayList<>(); 355 final List<WrapsElement> elements = new ArrayList<>(); 356 for (Field field : pageClassToProxy.getDeclaredFields()) { 357 if (field.getAnnotation(Required.class) != null) { 358 try { 359 field.setAccessible(true); 360 fieldNames.add(field.getName()); 361 elements.add((WrapsElement) field.get(page)); 362 } catch (Exception e) { 363 throw new RuntimeException(e); 364 } 365 } 366 } 367 368 Wait<T> wait = new FluentWait<>(page).withTimeout(LOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS) 369 .pollingEvery(POLLING_FREQUENCY_MILLISECONDS, TimeUnit.MILLISECONDS); 370 try { 371 return wait.until(aPage -> { 372 String notLoaded = anyElementNotLoaded(elements, fieldNames); 373 if (notLoaded == null) { 374 // check if there are Jquery ajax requests to complete 375 if (pageClassToProxy.isAnnotationPresent(WaitForJQueryAjaxOnLoading.class)) { 376 new AjaxRequestManager(driver).waitForJQueryRequests(); 377 } 378 379 return aPage; 380 } else { 381 return null; 382 } 383 }); 384 } catch (TimeoutException e) { 385 throw new TimeoutException("not loaded: " + anyElementNotLoaded(elements, fieldNames), e); 386 } 387 } 388 389 protected static String anyElementNotLoaded(List<WrapsElement> proxies, List<String> fieldNames) { 390 for (int i = 0; i < proxies.size(); i++) { 391 WrapsElement proxy = proxies.get(i); 392 try { 393 // method implemented in LocatingElementHandler 394 proxy.getWrappedElement(); 395 } catch (NoSuchElementException e) { 396 return fieldNames.get(i); 397 } 398 } 399 return null; 400 } 401 402 // private in PageFactory... 403 protected static <T> T instantiatePage(Class<T> pageClassToProxy) { 404 try { 405 try { 406 Constructor<T> constructor = pageClassToProxy.getConstructor(WebDriver.class); 407 return constructor.newInstance(driver); 408 } catch (NoSuchMethodException e) { 409 return pageClassToProxy.newInstance(); 410 } 411 } catch (RuntimeException e) { 412 throw e; 413 } catch (Exception e) { 414 throw new RuntimeException(e); 415 } 416 } 417 418 protected static <T extends WebFragment> T instantiateWebFragment(WebElement element, Class<T> webFragmentClass) { 419 try { 420 try { 421 Constructor<T> constructor = webFragmentClass.getConstructor(WebDriver.class, WebElement.class); 422 return constructor.newInstance(driver, element); 423 } catch (NoSuchMethodException e) { 424 return webFragmentClass.newInstance(); 425 } 426 } catch (RuntimeException e) { 427 throw e; 428 } catch (Exception e) { 429 throw new RuntimeException(e); 430 } 431 } 432 433 public LoginPage getLoginPage() { 434 return get(NUXEO_URL + "/logout", LoginPage.class); 435 } 436 437 public LoginPage logout() { 438 return getLoginPage(); 439 } 440 441 /** 442 * navigate to a link text. wait until the link is available and click on it. 443 */ 444 public <T extends AbstractPage> T nav(Class<T> pageClass, String linkText) { 445 WebElement link = Locator.findElementWithTimeout(By.linkText(linkText)); 446 if (link == null) { 447 return null; 448 } 449 link.click(); 450 return asPage(pageClass); 451 } 452 453 /** 454 * Navigate to a specified url 455 * 456 * @param urlString url 457 * @throws MalformedURLException 458 */ 459 public void navToUrl(String urlString) throws MalformedURLException { 460 URL url = new URL(urlString); 461 driver.navigate().to(url); 462 } 463 464 /** 465 * Login as Administrator 466 * 467 * @return the Document base page (by default returned by CAP) 468 * @throws UserNotConnectedException 469 */ 470 public DocumentBasePage login() throws UserNotConnectedException { 471 return login(ADMINISTRATOR, ADMINISTRATOR); 472 } 473 474 public DocumentBasePage login(String username, String password) throws UserNotConnectedException { 475 DocumentBasePage documentBasePage = getLoginPage().login(username, password, DocumentBasePage.class); 476 documentBasePage.checkUserConnected(username); 477 return documentBasePage; 478 } 479 480 /** 481 * Login as default test user. 482 * 483 * @since 5.9.2 484 */ 485 public DocumentBasePage loginAsTestUser() throws UserNotConnectedException { 486 return login(TEST_USERNAME, TEST_PASSWORD); 487 } 488 489 /** 490 * Login using an invalid credential. 491 * 492 * @param username the username 493 * @param password the password 494 */ 495 public LoginPage loginInvalid(String username, String password) { 496 return getLoginPage().login(username, password, LoginPage.class); 497 } 498 499 /** 500 * Init the repository with a test Workspace form the {@code currentPage}. 501 * 502 * @param currentPage the current page 503 * @return the created Workspace page 504 * @throws Exception if initializing repository fails 505 */ 506 @Deprecated 507 protected DocumentBasePage initRepository(DocumentBasePage currentPage) throws Exception { 508 return createWorkspace(currentPage, "Test Workspace", "Test Workspace for my dear WebDriver."); 509 } 510 511 /** 512 * Cleans the repository (delete the test Workspace) from the {@code currentPage}. 513 * 514 * @param currentPage the current page 515 * @throws Exception if cleaning repository fails 516 */ 517 @Deprecated 518 protected void cleanRepository(DocumentBasePage currentPage) throws Exception { 519 deleteWorkspace(currentPage, "Test Workspace"); 520 } 521 522 /** 523 * Creates a Workspace from the {@code currentPage}. 524 * 525 * @param currentPage the current page 526 * @param workspaceTitle the workspace title 527 * @param workspaceDescription the workspace description 528 * @return the created Workspace page 529 * @deprecated since 8.3: use {@link DocumentBasePage#createWorkspace(String, String)} instead. 530 */ 531 @Deprecated 532 protected DocumentBasePage createWorkspace(DocumentBasePage currentPage, String workspaceTitle, 533 String workspaceDescription) { 534 return currentPage.createWorkspace(workspaceTitle, workspaceDescription); 535 } 536 537 /** 538 * Deletes the Workspace with title {@code workspaceTitle} from the {@code currentPage}. 539 * 540 * @param currentPage the current page 541 * @param workspaceTitle the workspace title 542 * @deprecated since 8.3: use {@link DocumentBasePage#deleteWorkspace(String)} instead. 543 */ 544 @Deprecated 545 protected void deleteWorkspace(DocumentBasePage currentPage, String workspaceTitle) { 546 currentPage.deleteWorkspace(workspaceTitle); 547 } 548 549 /** 550 * Creates a File form the {@code currentPage}. 551 * 552 * @param currentPage the current page 553 * @param fileTitle the file title 554 * @param fileDescription the file description 555 * @param uploadBlob true if a blob needs to be uploaded (temporary file created for this purpose) 556 * @param filePrefix the file prefix 557 * @param fileSuffix the file suffix 558 * @param fileContent the file content 559 * @return the created File page 560 * @throws IOException if temporary file creation fails 561 * @deprecated since 8.3: use {@link DocumentBasePage#createFile(String, String, boolean, String, String, String)} 562 * instead. 563 */ 564 @Deprecated 565 protected FileDocumentBasePage createFile(DocumentBasePage currentPage, String fileTitle, String fileDescription, 566 boolean uploadBlob, String filePrefix, String fileSuffix, String fileContent) throws IOException { 567 return currentPage.createFile(fileTitle, fileDescription, uploadBlob, filePrefix, fileSuffix, fileContent); 568 } 569 570 /** 571 * Creates a Collections container form the {@code currentPage}. 572 * 573 * @param currentPage the current page 574 * @param collectionsTitle the Collections container title 575 * @param fileDescription the collections description 576 * @return the created Collections page 577 * @deprecated since 8.3: use {@link DocumentBasePage#createCollections(String, String)} instead. 578 */ 579 @Deprecated 580 protected DocumentBasePage createCollections(DocumentBasePage currentPage, String collectionsTitle, 581 String fileDescription) { 582 return currentPage.createCollections(collectionsTitle, fileDescription); 583 } 584 585 /** 586 * Creates a Collection form the {@code currentPage}. 587 * 588 * @param currentPage the current page 589 * @param collectionsTitle the Collections container title 590 * @param fileDescription the collection description 591 * @return the created Collections page 592 * @deprecated since 8.3: use {@link DocumentBasePage#createCollection(String, String)} instead. 593 */ 594 @Deprecated 595 protected CollectionContentTabSubPage createCollection(DocumentBasePage currentPage, String collectionsTitle, 596 String fileDescription) { 597 return currentPage.createCollection(collectionsTitle, fileDescription); 598 } 599 600 /** 601 * Creates a temporary file and returns its absolute path. 602 * 603 * @param filePrefix the file prefix 604 * @param fileSuffix the file suffix 605 * @param fileContent the file content 606 * @return the temporary file to upload path 607 * @throws IOException if temporary file creation fails 608 * @since 5.9.3 609 */ 610 public static String getTmpFileToUploadPath(String filePrefix, String fileSuffix, String fileContent) 611 throws IOException { 612 // Create tmp file, deleted on exit 613 File tmpFile = Framework.createTempFile(filePrefix, fileSuffix); 614 tmpFile.deleteOnExit(); 615 FileUtils.writeFile(tmpFile, fileContent); 616 assertTrue(tmpFile.exists()); 617 618 // Check file URI protocol 619 assertEquals("file", tmpFile.toURI().toURL().getProtocol()); 620 621 // Return file absolute path 622 return tmpFile.getAbsolutePath(); 623 } 624 625 /** 626 * Get the current document id stored in the javascript ctx.currentDocument variable of the current page. 627 * 628 * @return the current document id 629 * @since 5.7 630 */ 631 protected String getCurrentDocumentId() { 632 return (String) driver.executeScript("return ctx.currentDocument;"); 633 } 634 635 /** 636 * Creates a Note form the {@code currentPage}. 637 * 638 * @param currentPage the current page 639 * @param noteTitle the note title 640 * @param noteDescription the note description 641 * @param defineNote true if the content of the note needs to be defined 642 * @param noteContent the content of the note 643 * @return the created note page. 644 * @throws IOException 645 * @since 5.9.4 646 * @deprecated since 8.3: use {@link DocumentBasePage#createNote(String, String, boolean, String)} instead. 647 */ 648 @Deprecated 649 protected NoteDocumentBasePage createNote(DocumentBasePage currentPage, String noteTitle, String noteDescription, 650 boolean defineNote, String noteContent) throws IOException { 651 return currentPage.createNote(noteTitle, noteDescription, defineNote, noteContent); 652 } 653 654 /** 655 * Do not run on windows with Firefox 26 (NXP-17848). 656 * 657 * @since 7.10 658 */ 659 protected void doNotRunOnWindowsWithFF26() { 660 String browser, browserVersion = null; 661 try { 662 browser = driver.getCapabilities().getBrowserName(); 663 browserVersion = driver.getCapabilities().getVersion(); 664 Float iBrowserVersion = Float.parseFloat(browserVersion); 665 assumeFalse(SystemUtils.IS_OS_WINDOWS && browser.equals("firefox") && iBrowserVersion <= 28.0); 666 } catch (NumberFormatException e) { 667 log.warn("Could not parse browser version: " + browserVersion); 668 } 669 } 670 671}