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