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.Assume.assumeFalse; 029import static org.junit.Assert.assertEquals; 030import static org.junit.Assert.assertTrue; 031 032import java.io.File; 033import java.io.IOException; 034import java.lang.reflect.Constructor; 035import java.lang.reflect.Field; 036import java.lang.reflect.Method; 037import java.net.MalformedURLException; 038import java.net.URI; 039import java.net.URL; 040import java.net.URLClassLoader; 041import java.util.ArrayList; 042import java.util.Arrays; 043import java.util.List; 044import java.util.concurrent.TimeUnit; 045import java.util.jar.Attributes; 046import java.util.jar.JarFile; 047 048import org.apache.commons.lang.SystemUtils; 049import org.apache.commons.logging.Log; 050import org.apache.commons.logging.LogFactory; 051import org.browsermob.proxy.ProxyServer; 052import org.junit.After; 053import org.junit.AfterClass; 054import org.junit.BeforeClass; 055import org.junit.Rule; 056import org.junit.rules.MethodRule; 057 058import org.nuxeo.common.Environment; 059import org.nuxeo.common.utils.FileUtils; 060import org.nuxeo.functionaltests.fragment.WebFragment; 061import org.nuxeo.functionaltests.pages.AbstractPage; 062import org.nuxeo.functionaltests.pages.DocumentBasePage; 063import org.nuxeo.functionaltests.pages.DocumentBasePage.UserNotConnectedException; 064import org.nuxeo.functionaltests.pages.FileDocumentBasePage; 065import org.nuxeo.functionaltests.pages.LoginPage; 066import org.nuxeo.functionaltests.pages.NoteDocumentBasePage; 067import org.nuxeo.functionaltests.pages.forms.CollectionCreationFormPage; 068import org.nuxeo.functionaltests.pages.forms.DublinCoreCreationDocumentFormPage; 069import org.nuxeo.functionaltests.pages.forms.FileCreationFormPage; 070import org.nuxeo.functionaltests.pages.forms.NoteCreationFormPage; 071import org.nuxeo.functionaltests.pages.forms.WorkspaceFormPage; 072import org.nuxeo.functionaltests.pages.tabs.CollectionContentTabSubPage; 073import org.nuxeo.runtime.api.Framework; 074 075import org.openqa.selenium.By; 076import org.openqa.selenium.JavascriptExecutor; 077import org.openqa.selenium.NoSuchElementException; 078import org.openqa.selenium.Proxy; 079import org.openqa.selenium.TimeoutException; 080import org.openqa.selenium.WebDriver; 081import org.openqa.selenium.WebElement; 082import org.openqa.selenium.chrome.ChromeDriver; 083import org.openqa.selenium.chrome.ChromeOptions; 084import org.openqa.selenium.firefox.FirefoxDriver; 085import org.openqa.selenium.firefox.FirefoxProfile; 086import org.openqa.selenium.internal.WrapsElement; 087import org.openqa.selenium.remote.CapabilityType; 088import org.openqa.selenium.remote.Command; 089import org.openqa.selenium.remote.DesiredCapabilities; 090import org.openqa.selenium.remote.DriverCommand; 091import org.openqa.selenium.remote.RemoteWebDriver; 092import org.openqa.selenium.support.PageFactory; 093import org.openqa.selenium.support.ui.FluentWait; 094import org.openqa.selenium.support.ui.Wait; 095 096import com.google.common.base.Function; 097import com.google.common.collect.ImmutableMap; 098import net.jsourcerer.webdriver.jserrorcollector.JavaScriptError; 099 100/** 101 * Base functions for all pages. 102 */ 103public abstract class AbstractTest { 104 105 /** 106 * @since 5.9.2 107 */ 108 public final static String TEST_USERNAME = "jdoe"; 109 110 /** 111 * @since 5.9.2 112 */ 113 public final static String TEST_PASSWORD = "test"; 114 115 /** 116 * Polling frequency in milliseconds. 117 * 118 * @since 5.9.2 119 */ 120 public static final int POLLING_FREQUENCY_MILLISECONDS = 100; 121 122 /** 123 * Page Load timeout in seconds. 124 * 125 * @since 5.9.2 126 */ 127 public static final int PAGE_LOAD_TIME_OUT_SECONDS = 60; 128 129 /** 130 * @since 5.7 131 */ 132 public static final String CHROME_DRIVER_DEFAULT_PATH_LINUX = "/usr/bin/chromedriver"; 133 134 /** 135 * @since 5.7 "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" doesn't work 136 */ 137 public static final String CHROME_DRIVER_DEFAULT_PATH_MAC = "/Applications/chromedriver"; 138 139 /** 140 * @since 5.7 141 */ 142 public static final String CHROME_DRIVER_DEFAULT_PATH_WINVISTA = SystemUtils.getUserHome().getPath() 143 + "\\AppData\\Local\\Google\\Chrome\\Application\\chromedriver.exe"; 144 145 /** 146 * @since 5.7 147 */ 148 public static final String CHROME_DRIVER_DEFAULT_PATH_WINXP = SystemUtils.getUserHome().getPath() 149 + "\\Local Settings\\Application Data\\Google\\Chrome\\Application\\chromedriver.exe"; 150 151 /** 152 * @since 5.7 153 */ 154 public static final String CHROME_DRIVER_DEFAULT_EXECUTABLE_NAME = "chromedriver"; 155 156 /** 157 * @since 5.7 158 */ 159 public static final String CHROME_DRIVER_WINDOWS_EXECUTABLE_NAME = "chromedriver.exe"; 160 161 static final Log log = LogFactory.getLog(AbstractTest.class); 162 163 public static final String NUXEO_URL = System.getProperty("nuxeoURL", "http://localhost:8080/nuxeo").replaceAll( 164 "/$", ""); 165 166 public static final int LOAD_TIMEOUT_SECONDS = 30; 167 168 public static final int LOAD_SHORT_TIMEOUT_SECONDS = 2; 169 170 public static final int AJAX_TIMEOUT_SECONDS = 10; 171 172 public static final int AJAX_SHORT_TIMEOUT_SECONDS = 2; 173 174 public static final int POLLING_FREQUENCY_SECONDS = 1; 175 176 private static final String FIREBUG_XPI = "firebug-1.6.2-fx.xpi"; 177 178 private static final String FIREBUG_VERSION = "1.6.2"; 179 180 private static final String FIREBUG_M2 = "firebug/firebug/1.6.2-fx"; 181 182 private static final int PROXY_PORT = 4444; 183 184 private static final String HAR_NAME = "http-headers.json"; 185 186 public static final String SYSPROP_CHROME_DRIVER_PATH = "webdriver.chrome.driver"; 187 188 public static RemoteWebDriver driver; 189 190 protected static File tmp_firebug_xpi; 191 192 protected static ProxyServer proxyServer = null; 193 194 /** 195 * Logger method to follow what's being run on server logs and take a screenshot of the last page in case of failure 196 */ 197 @Rule 198 public MethodRule watchman = new LogTestWatchman(driver, NUXEO_URL); 199 200 /** 201 * This method will be executed before any method registered with JUnit After annotation. 202 * 203 * @since 5.8 204 */ 205 public void runBeforeAfters() { 206 ((LogTestWatchman) watchman).runBeforeAfters(); 207 } 208 209 @BeforeClass 210 public static void initDriver() throws Exception { 211 String browser = System.getProperty("browser", "firefox"); 212 // Use the same strings as command-line Selenium 213 if (browser.equals("chrome") || browser.equals("firefox")) { 214 initFirefoxDriver(); 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 } 222 223 protected static void initFirefoxDriver() throws Exception { 224 DesiredCapabilities dc = DesiredCapabilities.firefox(); 225 FirefoxProfile profile = new FirefoxProfile(); 226 // Disable native events (makes things break on Windows) 227 profile.setEnableNativeEvents(false); 228 // Set English as default language 229 profile.setPreference("general.useragent.locale", "en"); 230 profile.setPreference("intl.accept_languages", "en"); 231 // Set other confs to speed up FF 232 233 // Speed up firefox by pipelining requests on a single connection 234 profile.setPreference("network.http.keep-alive", true); 235 profile.setPreference("network.http.pipelining", true); 236 profile.setPreference("network.http.proxy.pipelining", true); 237 profile.setPreference("network.http.pipelining.maxrequests", 8); 238 239 // Try to use less memory 240 profile.setPreference("browser.sessionhistory.max_entries", 10); 241 profile.setPreference("browser.sessionhistory.max_total_viewers", 4); 242 profile.setPreference("browser.sessionstore.max_tabs_undo", 4); 243 profile.setPreference("browser.sessionstore.interval", 1800000); 244 245 // disable unresponsive script alerts 246 profile.setPreference("dom.max_script_run_time", 0); 247 profile.setPreference("dom.max_chrome_script_run_time", 0); 248 249 // don't skip proxy for localhost 250 profile.setPreference("network.proxy.no_proxies_on", ""); 251 252 // prevent different kinds of popups/alerts 253 profile.setPreference("browser.tabs.warnOnClose", false); 254 profile.setPreference("browser.tabs.warnOnOpen", false); 255 profile.setPreference("extensions.newAddons", false); 256 profile.setPreference("extensions.update.notifyUser", false); 257 258 // disable autoscrolling 259 profile.setPreference("browser.urlbar.autocomplete.enabled", false); 260 261 // downloads conf 262 profile.setPreference("browser.download.useDownloadDir", false); 263 264 // prevent FF from running in offline mode when there's no network 265 // connection 266 profile.setPreference("toolkit.networkmanager.disable", true); 267 268 // prevent FF from giving health reports 269 profile.setPreference("datareporting.policy.dataSubmissionEnabled", false); 270 profile.setPreference("datareporting.healthreport.uploadEnabled", false); 271 profile.setPreference("datareporting.healthreport.service.firstRun", false); 272 profile.setPreference("datareporting.healthreport.service.enabled", false); 273 profile.setPreference("datareporting.healthreport.logging.consoleEnabled", false); 274 275 // start page conf to speed up FF 276 profile.setPreference("browser.startup.homepage", "about:blank"); 277 profile.setPreference("pref.browser.homepage.disable_button.bookmark_page", false); 278 profile.setPreference("pref.browser.homepage.disable_button.restore_default", false); 279 280 // misc confs to avoid useless updates 281 profile.setPreference("browser.search.update", false); 282 profile.setPreference("browser.bookmarks.restore_default_bookmarks", false); 283 284 // misc confs to speed up FF 285 profile.setPreference("extensions.ui.dictionary.hidden", true); 286 profile.setPreference("layout.spellcheckDefault", 0); 287 // For FF > 40 ? 288 profile.setPreference("startup.homepage_welcome_url.additional", "about:blank"); 289 290 // webdriver logging 291 if (Boolean.TRUE.equals(Boolean.valueOf(System.getenv("nuxeo.log.webriver")))) { 292 String location = System.getProperty("basedir") + File.separator + "target"; 293 File outputFolder = new File(location); 294 if (!outputFolder.exists() || !outputFolder.isDirectory()) { 295 outputFolder = null; 296 } 297 File webdriverlogFile = File.createTempFile("webdriver", ".log", outputFolder); 298 profile.setPreference("webdriver.log.file", webdriverlogFile.getAbsolutePath()); 299 log.warn("Webdriver logs saved in " + webdriverlogFile); 300 } 301 302 addFireBug(profile); 303 JavaScriptError.addExtension(profile); 304 Proxy proxy = startProxy(); 305 if (proxy != null) { 306 // Does not work, but leave code for when it does 307 // Workaround: use 127.0.0.2 308 proxy.setNoProxy(""); 309 // setProxyPreferences method does not exist with selenium version 2.43.0 310 // profile.setProxyPreferences(proxy); 311 dc.setCapability(CapabilityType.PROXY, proxy); 312 } 313 dc.setCapability(FirefoxDriver.PROFILE, profile); 314 driver = new FirefoxDriver(dc); 315 } 316 317 protected static void initChromeDriver() throws Exception { 318 if (System.getProperty(SYSPROP_CHROME_DRIVER_PATH) == null) { 319 String chromeDriverDefaultPath = null; 320 String chromeDriverExecutableName = CHROME_DRIVER_DEFAULT_EXECUTABLE_NAME; 321 if (SystemUtils.IS_OS_LINUX) { 322 chromeDriverDefaultPath = CHROME_DRIVER_DEFAULT_PATH_LINUX; 323 } else if (SystemUtils.IS_OS_MAC) { 324 chromeDriverDefaultPath = CHROME_DRIVER_DEFAULT_PATH_MAC; 325 } else if (SystemUtils.IS_OS_WINDOWS_XP) { 326 chromeDriverDefaultPath = CHROME_DRIVER_DEFAULT_PATH_WINXP; 327 chromeDriverExecutableName = CHROME_DRIVER_WINDOWS_EXECUTABLE_NAME; 328 } else if (SystemUtils.IS_OS_WINDOWS_VISTA) { 329 chromeDriverDefaultPath = CHROME_DRIVER_DEFAULT_PATH_WINVISTA; 330 chromeDriverExecutableName = CHROME_DRIVER_WINDOWS_EXECUTABLE_NAME; 331 } else if (SystemUtils.IS_OS_WINDOWS) { 332 // Unknown default path on other Windows OS. To be completed. 333 chromeDriverExecutableName = CHROME_DRIVER_WINDOWS_EXECUTABLE_NAME; 334 } 335 336 if (chromeDriverDefaultPath != null && new File(chromeDriverDefaultPath).exists()) { 337 log.warn(String.format("Missing property %s but found %s. Using it...", SYSPROP_CHROME_DRIVER_PATH, 338 chromeDriverDefaultPath)); 339 System.setProperty(SYSPROP_CHROME_DRIVER_PATH, chromeDriverDefaultPath); 340 } else { 341 // Can't find chromedriver in default location, check system 342 // path 343 File chromeDriverExecutable = findExecutableOnPath(chromeDriverExecutableName); 344 if ((chromeDriverExecutable != null) && (chromeDriverExecutable.exists())) { 345 log.warn(String.format("Missing property %s but found %s. Using it...", SYSPROP_CHROME_DRIVER_PATH, 346 chromeDriverExecutable.getCanonicalPath())); 347 System.setProperty(SYSPROP_CHROME_DRIVER_PATH, chromeDriverExecutable.getCanonicalPath()); 348 } else { 349 log.error(String.format("Could not find the Chrome driver looking at %s or system path." 350 + " Download it from %s and set its path with " + "the System property %s.", 351 chromeDriverDefaultPath, "http://code.google.com/p/chromedriver/downloads/list", 352 SYSPROP_CHROME_DRIVER_PATH)); 353 } 354 } 355 } 356 DesiredCapabilities dc = DesiredCapabilities.chrome(); 357 ChromeOptions options = new ChromeOptions(); 358 options.addArguments(Arrays.asList("--ignore-certificate-errors")); 359 Proxy proxy = startProxy(); 360 if (proxy != null) { 361 proxy.setNoProxy(""); 362 dc.setCapability(CapabilityType.PROXY, proxy); 363 } 364 dc.setCapability(ChromeOptions.CAPABILITY, options); 365 driver = new ChromeDriver(dc); 366 } 367 368 /** 369 * @since 5.7 370 */ 371 protected static File findExecutableOnPath(String executableName) { 372 String systemPath = System.getenv("PATH"); 373 String[] pathDirs = systemPath.split(File.pathSeparator); 374 File fullyQualifiedExecutable = null; 375 for (String pathDir : pathDirs) { 376 File file = new File(pathDir, executableName); 377 if (file.isFile()) { 378 fullyQualifiedExecutable = file; 379 break; 380 } 381 } 382 return fullyQualifiedExecutable; 383 } 384 385 /** 386 * @since 7.1 387 */ 388 @After 389 public void checkJavascriptError() { 390 if (driver != null) { 391 new JavaScriptErrorCollector(driver).checkForErrors(); 392 } 393 } 394 395 @AfterClass 396 public static void quitDriver() { 397 if (driver != null) { 398 driver.quit(); 399 driver = null; 400 } 401 removeFireBug(); 402 403 try { 404 stopProxy(); 405 } catch (Exception e) { 406 log.error("Could not stop proxy: " + e.getMessage()); 407 } 408 } 409 410 /** 411 * Introspects the classpath and returns the list of files in it. FIXME: should use 412 * HarnessRuntime#getClassLoaderFiles that returns the same thing 413 * 414 * @throws Exception 415 */ 416 protected static List<String> getClassLoaderFiles() throws Exception { 417 ClassLoader cl = AbstractTest.class.getClassLoader(); 418 URL[] urls = null; 419 if (cl instanceof URLClassLoader) { 420 urls = ((URLClassLoader) cl).getURLs(); 421 } else if (cl.getClass().getName().equals("org.apache.tools.ant.AntClassLoader")) { 422 Method method = cl.getClass().getMethod("getClasspath"); 423 String cp = (String) method.invoke(cl); 424 String[] paths = cp.split(File.pathSeparator); 425 urls = new URL[paths.length]; 426 for (int i = 0; i < paths.length; i++) { 427 urls[i] = new URL("file:" + paths[i]); 428 } 429 } else { 430 System.err.println("Unknown classloader type: " + cl.getClass().getName()); 431 return null; 432 } 433 434 JarFile surefirebooterJar = null; 435 for (URL url : urls) { 436 URI uri = url.toURI(); 437 if (uri.getPath().matches(".*/nuxeo-runtime-[^/]*\\.jar")) { 438 break; 439 } else if (uri.getScheme().equals("file") && uri.getPath().contains("surefirebooter")) { 440 surefirebooterJar = new JarFile(new File(uri)); 441 } 442 } 443 444 // special case for maven surefire with useManifestOnlyJar 445 if (surefirebooterJar != null) { 446 try { 447 try { 448 String cp = surefirebooterJar.getManifest() 449 .getMainAttributes() 450 .getValue(Attributes.Name.CLASS_PATH); 451 if (cp != null) { 452 String[] cpe = cp.split(" "); 453 URL[] newUrls = new URL[cpe.length]; 454 for (int i = 0; i < cpe.length; i++) { 455 // Don't need to add 'file:' with maven 456 // surefire >= 2.4.2 457 String newUrl = cpe[i].startsWith("file:") ? cpe[i] : "file:" + cpe[i]; 458 newUrls[i] = new URL(newUrl); 459 } 460 urls = newUrls; 461 } 462 } finally { 463 surefirebooterJar.close(); 464 } 465 } catch (Exception e) { 466 // skip 467 } 468 } 469 // turn into files 470 List<String> files = new ArrayList<>(urls.length); 471 for (URL url : urls) { 472 files.add(url.toURI().getPath()); 473 } 474 return files; 475 } 476 477 private static final String M2_REPO = "repository/"; 478 479 protected static void addFireBug(FirefoxProfile profile) throws Exception { 480 // this is preventing from running tests in eclipse 481 // profile.addExtension(AbstractTest.class, "/firebug.xpi"); 482 483 File xpi = null; 484 List<String> clf = getClassLoaderFiles(); 485 for (String f : clf) { 486 if (f.endsWith("/" + FIREBUG_XPI)) { 487 xpi = new File(f); 488 } 489 } 490 if (xpi == null) { 491 String customM2Repo = System.getProperty("M2_REPO", M2_REPO).replaceAll("/$", ""); 492 // try to guess the location in the M2 repo 493 for (String f : clf) { 494 if (f.contains(customM2Repo)) { 495 String m2 = f.substring(0, f.indexOf(customM2Repo) + customM2Repo.length()); 496 xpi = new File(m2 + "/" + FIREBUG_M2 + "/" + FIREBUG_XPI); 497 break; 498 } 499 } 500 } 501 if (xpi == null || !xpi.exists()) { 502 log.warn(FIREBUG_XPI + " not found in classloader or local M2 repository"); 503 return; 504 } 505 profile.addExtension(xpi); 506 507 // avoid "first run" page 508 profile.setPreference("extensions.firebug.currentVersion", FIREBUG_VERSION); 509 } 510 511 protected static void removeFireBug() { 512 if (tmp_firebug_xpi != null) { 513 tmp_firebug_xpi.delete(); 514 tmp_firebug_xpi.getParentFile().delete(); 515 } 516 } 517 518 protected static Proxy startProxy() throws Exception { 519 if (Boolean.TRUE.equals(Boolean.valueOf(System.getProperty("useProxy", "false")))) { 520 proxyServer = new ProxyServer(PROXY_PORT); 521 proxyServer.start(); 522 proxyServer.setCaptureHeaders(true); 523 // Block access to tracking sites 524 proxyServer.blacklistRequests("https?://www\\.nuxeo\\.com/embedded/wizard.*", 410); 525 proxyServer.blacklistRequests("https?://.*\\.mktoresp\\.com/.*", 410); 526 proxyServer.blacklistRequests(".*_mchId.*", 410); 527 proxyServer.blacklistRequests("https?://.*\\.google-analytics\\.com/.*", 410); 528 proxyServer.newHar("webdriver-test"); 529 Proxy proxy = proxyServer.seleniumProxy(); 530 return proxy; 531 } else { 532 return null; 533 } 534 } 535 536 protected static void stopProxy() throws Exception { 537 if (proxyServer != null) { 538 String target = System.getProperty(Environment.NUXEO_LOG_DIR); 539 File harFile; 540 if (target == null) { 541 harFile = new File(HAR_NAME); 542 } else { 543 harFile = new File(target, HAR_NAME); 544 } 545 proxyServer.getHar().writeTo(harFile); 546 proxyServer.stop(); 547 } 548 } 549 550 public static <T> T get(String url, Class<T> pageClassToProxy) { 551 if (driver != null) { 552 new JavaScriptErrorCollector(driver).checkForErrors(); 553 } 554 driver.get(url); 555 return asPage(pageClassToProxy); 556 } 557 558 /** 559 * Do not wait for page load. Do not handle error. Do not give explicit error in case of failure. This is a very raw 560 * get. 561 * 562 * @since 6.0 563 */ 564 public static <T> T getWithoutErrorHandler(String url, Class<T> pageClassToProxy) throws IOException { 565 Command command = new Command(AbstractTest.driver.getSessionId(), DriverCommand.GET, 566 ImmutableMap.of("url", url)); 567 AbstractTest.driver.getCommandExecutor().execute(command); 568 return asPage(pageClassToProxy); 569 } 570 571 public static WebDriver getPopup() { 572 String currentWindow = driver.getWindowHandle(); 573 for (String popup : driver.getWindowHandles()) { 574 if (popup.equals(currentWindow)) { 575 continue; 576 } 577 return driver.switchTo().window(popup); 578 } 579 return null; 580 } 581 582 public static <T> T asPage(Class<T> pageClassToProxy) { 583 T page = instantiatePage(pageClassToProxy); 584 return fillElement(pageClassToProxy, page); 585 } 586 587 public static <T extends WebFragment> T getWebFragment(By by, Class<T> webFragmentClass) { 588 WebElement element = Locator.findElementWithTimeout(by); 589 return getWebFragment(element, webFragmentClass); 590 } 591 592 public static <T extends WebFragment> T getWebFragment(WebElement element, Class<T> webFragmentClass) { 593 T webFragment = instantiateWebFragment(element, webFragmentClass); 594 webFragment = fillElement(webFragmentClass, webFragment); 595 // fillElement somehow overwrite the 'element' field, reset it. 596 webFragment.setElement(element); 597 return webFragment; 598 } 599 600 /** 601 * Fills an instantiated page/form/widget attributes 602 * 603 * @since 5.7 604 */ 605 public static <T> T fillElement(Class<T> pageClassToProxy, T page) { 606 PageFactory.initElements(new VariableElementLocatorFactory(driver, AJAX_TIMEOUT_SECONDS), page); 607 // check all required WebElements on the page and wait for their 608 // loading 609 final List<String> fieldNames = new ArrayList<>(); 610 final List<WrapsElement> elements = new ArrayList<>(); 611 for (Field field : pageClassToProxy.getDeclaredFields()) { 612 if (field.getAnnotation(Required.class) != null) { 613 try { 614 field.setAccessible(true); 615 fieldNames.add(field.getName()); 616 elements.add((WrapsElement) field.get(page)); 617 } catch (Exception e) { 618 throw new RuntimeException(e); 619 } 620 } 621 } 622 623 Wait<T> wait = new FluentWait<>(page).withTimeout(LOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS).pollingEvery( 624 POLLING_FREQUENCY_MILLISECONDS, TimeUnit.MILLISECONDS); 625 try { 626 return wait.until(new Function<T, T>() { 627 @Override 628 public T apply(T aPage) { 629 String notLoaded = anyElementNotLoaded(elements, fieldNames); 630 if (notLoaded == null) { 631 return aPage; 632 } else { 633 return null; 634 } 635 } 636 }); 637 } catch (TimeoutException e) { 638 throw new TimeoutException("not loaded: " + anyElementNotLoaded(elements, fieldNames), e); 639 } 640 } 641 642 protected static String anyElementNotLoaded(List<WrapsElement> proxies, List<String> fieldNames) { 643 for (int i = 0; i < proxies.size(); i++) { 644 WrapsElement proxy = proxies.get(i); 645 try { 646 // method implemented in LocatingElementHandler 647 proxy.getWrappedElement(); 648 } catch (NoSuchElementException e) { 649 return fieldNames.get(i); 650 } 651 } 652 return null; 653 } 654 655 // private in PageFactory... 656 protected static <T> T instantiatePage(Class<T> pageClassToProxy) { 657 try { 658 try { 659 Constructor<T> constructor = pageClassToProxy.getConstructor(WebDriver.class); 660 return constructor.newInstance(driver); 661 } catch (NoSuchMethodException e) { 662 return pageClassToProxy.newInstance(); 663 } 664 } catch (RuntimeException e) { 665 throw e; 666 } catch (Exception e) { 667 throw new RuntimeException(e); 668 } 669 } 670 671 protected static <T extends WebFragment> T instantiateWebFragment(WebElement element, Class<T> webFragmentClass) { 672 try { 673 try { 674 Constructor<T> constructor = webFragmentClass.getConstructor(WebDriver.class, WebElement.class); 675 return constructor.newInstance(driver, element); 676 } catch (NoSuchMethodException e) { 677 return webFragmentClass.newInstance(); 678 } 679 } catch (RuntimeException e) { 680 throw e; 681 } catch (Exception e) { 682 throw new RuntimeException(e); 683 } 684 } 685 686 public LoginPage getLoginPage() { 687 return get(NUXEO_URL + "/logout", LoginPage.class); 688 } 689 690 public LoginPage logout() { 691 return getLoginPage(); 692 } 693 694 /** 695 * navigate to a link text. wait until the link is available and click on it. 696 */ 697 public <T extends AbstractPage> T nav(Class<T> pageClass, String linkText) { 698 WebElement link = Locator.findElementWithTimeout(By.linkText(linkText)); 699 if (link == null) { 700 return null; 701 } 702 link.click(); 703 return asPage(pageClass); 704 } 705 706 /** 707 * Navigate to a specified url 708 * 709 * @param urlString url 710 * @throws MalformedURLException 711 */ 712 public void navToUrl(String urlString) throws MalformedURLException { 713 URL url = new URL(urlString); 714 driver.navigate().to(url); 715 } 716 717 /** 718 * Login as Administrator 719 * 720 * @return the Document base page (by default returned by CAP) 721 * @throws UserNotConnectedException 722 */ 723 public DocumentBasePage login() throws UserNotConnectedException { 724 return login("Administrator", "Administrator"); 725 } 726 727 public DocumentBasePage login(String username, String password) throws UserNotConnectedException { 728 DocumentBasePage documentBasePage = getLoginPage().login(username, password, DocumentBasePage.class); 729 documentBasePage.checkUserConnected(username); 730 return documentBasePage; 731 } 732 733 /** 734 * Login as default test user. 735 * 736 * @since 5.9.2 737 */ 738 public DocumentBasePage loginAsTestUser() throws UserNotConnectedException { 739 return login(TEST_USERNAME, TEST_PASSWORD); 740 } 741 742 /** 743 * Login using an invalid credential. 744 * 745 * @param username 746 * @param password 747 */ 748 public LoginPage loginInvalid(String username, String password) { 749 LoginPage loginPage = getLoginPage().login(username, password, LoginPage.class); 750 return loginPage; 751 } 752 753 /** 754 * Init the repository with a test Workspace form the {@code currentPage}. 755 * 756 * @param currentPage the current page 757 * @return the created Workspace page 758 * @throws Exception if initializing repository fails 759 */ 760 protected DocumentBasePage initRepository(DocumentBasePage currentPage) throws Exception { 761 return createWorkspace(currentPage, "Test Workspace", "Test Workspace for my dear WebDriver."); 762 } 763 764 /** 765 * Cleans the repository (delete the test Workspace) from the {@code currentPage}. 766 * 767 * @param currentPage the current page 768 * @throws Exception if cleaning repository fails 769 */ 770 protected void cleanRepository(DocumentBasePage currentPage) throws Exception { 771 deleteWorkspace(currentPage, "Test Workspace"); 772 } 773 774 /** 775 * Creates a Workspace form the {@code currentPage}. 776 * 777 * @param currentPage the current page 778 * @param workspaceTitle the workspace title 779 * @param workspaceDescription the workspace description 780 * @return the created Workspace page 781 */ 782 protected DocumentBasePage createWorkspace(DocumentBasePage currentPage, String workspaceTitle, 783 String workspaceDescription) { 784 // Go to Workspaces 785 DocumentBasePage workspacesPage = currentPage.getNavigationSubPage().goToDocument("Workspaces"); 786 // Get Workspace creation form page 787 WorkspaceFormPage workspaceCreationFormPage = workspacesPage.getWorkspacesContentTab().getWorkspaceCreatePage(); 788 // Create Workspace 789 DocumentBasePage workspacePage = workspaceCreationFormPage.createNewWorkspace(workspaceTitle, 790 workspaceDescription); 791 return workspacePage; 792 } 793 794 /** 795 * Deletes the Workspace with title {@code workspaceTitle} from the {@code currentPage}. 796 * 797 * @param currentPage the current page 798 * @param workspaceTitle the workspace title 799 */ 800 protected void deleteWorkspace(DocumentBasePage currentPage, String workspaceTitle) { 801 // Go to Workspaces 802 DocumentBasePage workspacesPage = currentPage.getNavigationSubPage().goToDocument("Workspaces"); 803 // Delete the Workspace 804 workspacesPage.getContentTab().removeDocument(workspaceTitle); 805 } 806 807 /** 808 * Creates a File form the {@code currentPage}. 809 * 810 * @param currentPage the current page 811 * @param fileTitle the file title 812 * @param fileDescription the file description 813 * @param uploadBlob true if a blob needs to be uploaded (temporary file created for this purpose) 814 * @param filePrefix the file prefix 815 * @param fileSuffix the file suffix 816 * @param fileContent the file content 817 * @return the created File page 818 * @throws IOException if temporary file creation fails 819 */ 820 protected FileDocumentBasePage createFile(DocumentBasePage currentPage, String fileTitle, String fileDescription, 821 boolean uploadBlob, String filePrefix, String fileSuffix, String fileContent) throws IOException { 822 // Get File creation form page 823 FileCreationFormPage fileCreationFormPage = currentPage.getContentTab().getDocumentCreatePage("File", 824 FileCreationFormPage.class); 825 // Create File 826 FileDocumentBasePage filePage = fileCreationFormPage.createFileDocument(fileTitle, fileDescription, uploadBlob, 827 filePrefix, fileSuffix, fileDescription); 828 return filePage; 829 } 830 831 /** 832 * Creates a Collections container form the {@code currentPage}. 833 * 834 * @param currentPage the current page 835 * @param collectionsTitle the Collections container title 836 * @param fileDescription the collections description 837 * @return the created Collections page 838 */ 839 protected DocumentBasePage createCollections(DocumentBasePage currentPage, String collectionsTitle, 840 String fileDescription) { 841 DublinCoreCreationDocumentFormPage dublinCoreDocumentFormPage = currentPage.getContentTab() 842 .getDocumentCreatePage( 843 "Collections", 844 DublinCoreCreationDocumentFormPage.class); 845 // Create File 846 DocumentBasePage documentBasePage = dublinCoreDocumentFormPage.createDocument(collectionsTitle, fileDescription); 847 return documentBasePage; 848 } 849 850 /** 851 * Creates a Collection form the {@code currentPage}. 852 * 853 * @param currentPage the current page 854 * @param collectionsTitle the Collections container title 855 * @param fileDescription the collection description 856 * @return the created Collections page 857 */ 858 protected CollectionContentTabSubPage createCollection(DocumentBasePage currentPage, String collectionsTitle, 859 String fileDescription) { 860 CollectionCreationFormPage collectionCreationFormPage = currentPage.getContentTab().getDocumentCreatePage( 861 "Collection", CollectionCreationFormPage.class); 862 // Create File 863 CollectionContentTabSubPage documentBasePage = collectionCreationFormPage.createDocument(collectionsTitle, 864 fileDescription); 865 return documentBasePage; 866 } 867 868 /** 869 * Creates a temporary file and returns its absolute path. 870 * 871 * @param filePrefix the file prefix 872 * @param fileSuffix the file suffix 873 * @param fileContent the file content 874 * @return the temporary file to upload path 875 * @throws IOException if temporary file creation fails 876 * @since 5.9.3 877 */ 878 public static String getTmpFileToUploadPath(String filePrefix, String fileSuffix, String fileContent) 879 throws IOException { 880 // Create tmp file, deleted on exit 881 File tmpFile = Framework.createTempFile(filePrefix, fileSuffix); 882 tmpFile.deleteOnExit(); 883 FileUtils.writeFile(tmpFile, fileContent); 884 assertTrue(tmpFile.exists()); 885 886 // Check file URI protocol 887 assertEquals("file", tmpFile.toURI().toURL().getProtocol()); 888 889 // Return file absolute path 890 return tmpFile.getAbsolutePath(); 891 } 892 893 /** 894 * Get the current document id stored in the javascript ctx.currentDocument variable of the current page. 895 * 896 * @return the current document id 897 * @since 5.7 898 */ 899 protected String getCurrentDocumentId() { 900 return (String) ((JavascriptExecutor) driver).executeScript(String.format("return ctx.currentDocument;")); 901 } 902 903 /** 904 * Creates a Note form the {@code currentPage}. 905 * 906 * @param currentPage the current page 907 * @param noteTitle the note title 908 * @param noteDescription the note description 909 * @param defineNote true if the content of the note needs to be defined 910 * @param noteContent the content of the note 911 * @return the created note page. 912 * @throws IOException 913 * @since 5.9.4 914 */ 915 protected NoteDocumentBasePage createNote(DocumentBasePage currentPage, String noteTitle, String noteDescription, 916 boolean defineNote, String noteContent) throws IOException { 917 // Get the Note creation form 918 NoteCreationFormPage noteCreationPage = currentPage.getContentTab().getDocumentCreatePage("Note", 919 NoteCreationFormPage.class); 920 // Create a Note 921 NoteDocumentBasePage notePage = noteCreationPage.createNoteDocument(noteTitle, noteDescription, defineNote, 922 noteContent); 923 return notePage; 924 } 925 926 /** 927 * Do not run on windows with Firefox 26 (NXP-17848). 928 * 929 * @since 7.10 930 */ 931 protected void doNotRunOnWindowsWithFF26() { 932 String browser, browserVersion = null; 933 try { 934 browser = driver.getCapabilities().getBrowserName(); 935 browserVersion = driver.getCapabilities().getVersion(); 936 Float iBrowserVersion = Float.parseFloat(browserVersion); 937 assumeFalse(SystemUtils.IS_OS_WINDOWS && browser.equals("firefox") && iBrowserVersion <= 28.0); 938 } catch (NumberFormatException e) { 939 log.warn("Could not parse browser version: " + browserVersion); 940 } 941 } 942 943}