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 * navigate to a link text. wait until the link is available and click on it. 441 */ 442 public <T extends AbstractPage> T nav(Class<T> pageClass, String linkText) { 443 WebElement link = Locator.findElementWithTimeout(By.linkText(linkText)); 444 if (link == null) { 445 return null; 446 } 447 link.click(); 448 return asPage(pageClass); 449 } 450 451 /** 452 * Navigate to a specified url 453 * 454 * @param urlString url 455 * @throws MalformedURLException 456 */ 457 public void navToUrl(String urlString) throws MalformedURLException { 458 URL url = new URL(urlString); 459 driver.navigate().to(url); 460 } 461 462 /** 463 * Login as Administrator 464 * 465 * @return the Document base page (by default returned by CAP) 466 * @throws UserNotConnectedException 467 */ 468 public DocumentBasePage login() throws UserNotConnectedException { 469 return login(ADMINISTRATOR, ADMINISTRATOR); 470 } 471 472 public DocumentBasePage login(String username, String password) throws UserNotConnectedException { 473 DocumentBasePage documentBasePage = getLoginPage().login(username, password, DocumentBasePage.class); 474 documentBasePage.checkUserConnected(username); 475 return documentBasePage; 476 } 477 478 /** 479 * Login as default test user. 480 * 481 * @since 5.9.2 482 */ 483 public DocumentBasePage loginAsTestUser() throws UserNotConnectedException { 484 return login(TEST_USERNAME, TEST_PASSWORD); 485 } 486 487 /** 488 * Login using an invalid credential. 489 * 490 * @param username the username 491 * @param password the password 492 */ 493 public LoginPage loginInvalid(String username, String password) { 494 return getLoginPage().login(username, password, LoginPage.class); 495 } 496 497 /** 498 * Init the repository with a test Workspace form the {@code currentPage}. 499 * 500 * @param currentPage the current page 501 * @return the created Workspace page 502 * @throws Exception if initializing repository fails 503 * @deprecated since 8.3 504 */ 505 @Deprecated 506 protected DocumentBasePage initRepository(DocumentBasePage currentPage) throws Exception { 507 return createWorkspace(currentPage, "Test Workspace", "Test Workspace for my dear WebDriver."); 508 } 509 510 /** 511 * Cleans the repository (delete the test Workspace) from the {@code currentPage}. 512 * 513 * @param currentPage the current page 514 * @throws Exception if cleaning repository fails 515 * @deprecated since 8.3 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.writeStringToFile(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}