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