org.nuxeo.functionaltests.AbstractTest.java Source code

Java tutorial

Introduction

Here is the source code for org.nuxeo.functionaltests.AbstractTest.java

Source

/*
 * (C) Copyright 2011 Nuxeo SA (http://nuxeo.com/) and contributors.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser General Public License
 * (LGPL) version 2.1 which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/lgpl.html
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * Contributors:
 *     Sun Seng David TAN
 *     Florent Guillaume
 *     Benoit Delbosc
 *     Antoine Taillefer
 */
package org.nuxeo.functionaltests;

import static java.util.concurrent.TimeUnit.SECONDS;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.jar.Attributes;
import java.util.jar.JarFile;

import org.apache.commons.lang.SystemUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.browsermob.proxy.ProxyServer;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.rules.MethodRule;
import org.nuxeo.functionaltests.forms.FileWidgetElement;
import org.nuxeo.functionaltests.fragment.WebFragment;
import org.nuxeo.functionaltests.pages.AbstractPage;
import org.nuxeo.functionaltests.pages.DocumentBasePage;
import org.nuxeo.functionaltests.pages.DocumentBasePage.UserNotConnectedException;
import org.nuxeo.functionaltests.pages.FileDocumentBasePage;
import org.nuxeo.functionaltests.pages.LoginPage;
import org.nuxeo.functionaltests.pages.forms.FileCreationFormPage;
import org.nuxeo.functionaltests.pages.forms.WorkspaceFormPage;
import org.openqa.selenium.By;
import org.openqa.selenium.ElementNotVisibleException;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.NotFoundException;
import org.openqa.selenium.Proxy;
import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxProfile;
import org.openqa.selenium.internal.WrapsElement;
import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.support.PageFactory;
import org.openqa.selenium.support.ui.Clock;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.FluentWait;
import org.openqa.selenium.support.ui.SystemClock;
import org.openqa.selenium.support.ui.Wait;

import com.google.common.base.Function;

/**
 * Base functions for all pages.
 */
public abstract class AbstractTest {

    /**
     * @since 5.7
     */
    public static final String CHROME_DRIVER_DEFAULT_PATH_LINUX = "/usr/bin/chromedriver";

    /**
     * @since 5.7
     *        "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
     *        doesn't work
     */
    public static final String CHROME_DRIVER_DEFAULT_PATH_MAC = "/Applications/chromedriver";

    /**
     * @since 5.7
     */
    public static final String CHROME_DRIVER_DEFAULT_PATH_WINVISTA = SystemUtils.getUserHome().getPath()
            + "\\AppData\\Local\\Google\\Chrome\\Application\\chromedriver.exe";

    /**
     * @since 5.7
     */
    public static final String CHROME_DRIVER_DEFAULT_PATH_WINXP = SystemUtils.getUserHome().getPath()
            + "\\Local Settings\\Application Data\\Google\\Chrome\\Application\\chromedriver.exe";

    /**
     * @since 5.7
     */
    public static final String CHROME_DRIVER_DEFAULT_EXECUTABLE_NAME = "chromedriver";

    /**
     * @since 5.7
     */
    public static final String CHROME_DRIVER_WINDOWS_EXECUTABLE_NAME = "chromedriver.exe";

    private static final Log log = LogFactory.getLog(AbstractTest.class);

    public static final String NUXEO_URL = System.getProperty("nuxeoURL", "http://localhost:8080/nuxeo")
            .replaceAll("/$", "");

    public static final int LOAD_TIMEOUT_SECONDS = 30;

    public static final int LOAD_SHORT_TIMEOUT_SECONDS = 2;

    public static final int AJAX_TIMEOUT_SECONDS = 10;

    public static final int AJAX_SHORT_TIMEOUT_SECONDS = 2;

    public static final int POLLING_FREQUENCY_SECONDS = 1;

    private static final String FIREBUG_XPI = "firebug-1.6.2-fx.xpi";

    private static final String FIREBUG_VERSION = "1.6.2";

    private static final String FIREBUG_M2 = "firebug/firebug/1.6.2-fx";

    private static final int PROXY_PORT = 4444;

    private static final String HAR_NAME = "http-headers.json";

    public static final String SYSPROP_CHROME_DRIVER_PATH = "webdriver.chrome.driver";

    protected static RemoteWebDriver driver;

    protected static File tmp_firebug_xpi;

    protected static ProxyServer proxyServer = null;

    /**
     * Logger method to follow what's being run on server logs and take a
     * screenshot of the last page in case of failure
     */
    @Rule
    public MethodRule watchman = new LogTestWatchman(driver, NUXEO_URL);

    /**
     * This method will be executed before any method registered with JUnit
     * After annotation.
     *
     * @since 5.8
     */
    public void runBeforeAfters() {
        ((LogTestWatchman) watchman).runBeforeAfters();
    }

    @BeforeClass
    public static void initDriver() throws Exception {
        String browser = System.getProperty("browser", "firefox");
        // Use the same strings as command-line Selenium
        if (browser.equals("chrome") || browser.equals("firefox")) {
            initFirefoxDriver();
        } else if (browser.equals("googlechrome")) {
            initChromeDriver();
        } else {
            throw new RuntimeException("Browser not supported: " + browser);
        }
    }

    protected static void initFirefoxDriver() throws Exception {
        DesiredCapabilities dc = DesiredCapabilities.firefox();
        FirefoxProfile profile = new FirefoxProfile();
        // Disable native events (makes things break on Windows)
        profile.setEnableNativeEvents(false);
        // Set English as default language
        profile.setPreference("general.useragent.locale", "en");
        profile.setPreference("intl.accept_languages", "en");
        // Set other confs to speed up FF

        // Speed up firefox by pipelining requests on a single connection
        profile.setPreference("network.http.keep-alive", true);
        profile.setPreference("network.http.pipelining", true);
        profile.setPreference("network.http.proxy.pipelining", true);
        profile.setPreference("network.http.pipelining.maxrequests", 8);

        // Try to use less memory
        profile.setPreference("browser.sessionhistory.max_entries", 10);
        profile.setPreference("browser.sessionhistory.max_total_viewers", 4);
        profile.setPreference("browser.sessionstore.max_tabs_undo", 4);
        profile.setPreference("browser.sessionstore.interval", 1800000);

        // do not load images
        profile.setPreference("permissions.default.image", 2);

        // disable unresponsive script alerts
        profile.setPreference("dom.max_script_run_time", 0);
        profile.setPreference("dom.max_chrome_script_run_time", 0);

        // don't skip proxy for localhost
        profile.setPreference("network.proxy.no_proxies_on", "");

        // prevent different kinds of popups/alerts
        profile.setPreference("browser.tabs.warnOnClose", false);
        profile.setPreference("browser.tabs.warnOnOpen", false);
        profile.setPreference("extensions.newAddons", false);
        profile.setPreference("extensions.update.notifyUser", false);

        // disable autoscrolling
        profile.setPreference("browser.urlbar.autocomplete.enabled", false);

        // downloads conf
        profile.setPreference("browser.download.useDownloadDir", false);

        // prevent FF from running in offline mode when there's no network
        // connection
        profile.setPreference("toolkit.networkmanager.disable", true);

        // prevent FF from giving health reports
        profile.setPreference("datareporting.policy.dataSubmissionEnabled", false);
        profile.setPreference("datareporting.healthreport.uploadEnabled", false);
        profile.setPreference("datareporting.healthreport.service.firstRun", false);
        profile.setPreference("datareporting.healthreport.service.enabled", false);
        profile.setPreference("datareporting.healthreport.logging.consoleEnabled", false);

        // start page conf to speed up FF
        profile.setPreference("browser.startup.homepage", "about:blank");
        profile.setPreference("pref.browser.homepage.disable_button.bookmark_page", false);
        profile.setPreference("pref.browser.homepage.disable_button.restore_default", false);

        // misc confs to avoid useless updates
        profile.setPreference("browser.search.update", false);
        profile.setPreference("browser.bookmarks.restore_default_bookmarks", false);

        // misc confs to speed up FF
        profile.setPreference("extensions.ui.dictionary.hidden", true);
        profile.setPreference("layout.spellcheckDefault", 0);

        addFireBug(profile);
        Proxy proxy = startProxy();
        if (proxy != null) {
            // Does not work, but leave code for when it does
            // Workaround: use 127.0.0.2
            proxy.setNoProxy("");
            profile.setProxyPreferences(proxy);
        }
        dc.setCapability(FirefoxDriver.PROFILE, profile);
        driver = new FirefoxDriver(dc);
    }

    @SuppressWarnings("deprecation")
    protected static void initChromeDriver() throws Exception {
        if (System.getProperty(SYSPROP_CHROME_DRIVER_PATH) == null) {
            String chromeDriverDefaultPath = null;
            String chromeDriverExecutableName = CHROME_DRIVER_DEFAULT_EXECUTABLE_NAME;
            if (SystemUtils.IS_OS_LINUX) {
                chromeDriverDefaultPath = CHROME_DRIVER_DEFAULT_PATH_LINUX;
            } else if (SystemUtils.IS_OS_MAC) {
                chromeDriverDefaultPath = CHROME_DRIVER_DEFAULT_PATH_MAC;
            } else if (SystemUtils.IS_OS_WINDOWS_XP) {
                chromeDriverDefaultPath = CHROME_DRIVER_DEFAULT_PATH_WINXP;
                chromeDriverExecutableName = CHROME_DRIVER_WINDOWS_EXECUTABLE_NAME;
            } else if (SystemUtils.IS_OS_WINDOWS_VISTA) {
                chromeDriverDefaultPath = CHROME_DRIVER_DEFAULT_PATH_WINVISTA;
                chromeDriverExecutableName = CHROME_DRIVER_WINDOWS_EXECUTABLE_NAME;
            } else if (SystemUtils.IS_OS_WINDOWS) {
                // Unknown default path on other Windows OS. To be completed.
                chromeDriverExecutableName = CHROME_DRIVER_WINDOWS_EXECUTABLE_NAME;
            }

            if (chromeDriverDefaultPath != null && new File(chromeDriverDefaultPath).exists()) {
                log.warn(String.format("Missing property %s but found %s. Using it...", SYSPROP_CHROME_DRIVER_PATH,
                        chromeDriverDefaultPath));
                System.setProperty(SYSPROP_CHROME_DRIVER_PATH, chromeDriverDefaultPath);
            } else {
                // Can't find chromedriver in default location, check system
                // path
                File chromeDriverExecutable = findExecutableOnPath(chromeDriverExecutableName);
                if ((chromeDriverExecutable != null) && (chromeDriverExecutable.exists())) {
                    log.warn(String.format("Missing property %s but found %s. Using it...",
                            SYSPROP_CHROME_DRIVER_PATH, chromeDriverExecutable.getCanonicalPath()));
                    System.setProperty(SYSPROP_CHROME_DRIVER_PATH, chromeDriverExecutable.getCanonicalPath());
                } else {
                    log.error(String.format(
                            "Could not find the Chrome driver looking at %s or system path."
                                    + " Download it from %s and set its path with " + "the System property %s.",
                            chromeDriverDefaultPath, "http://code.google.com/p/chromedriver/downloads/list",
                            SYSPROP_CHROME_DRIVER_PATH));
                }
            }
        }
        DesiredCapabilities dc = DesiredCapabilities.chrome();
        ChromeOptions options = new ChromeOptions();
        options.addArguments(Arrays.asList("--ignore-certificate-errors"));
        Proxy proxy = startProxy();
        if (proxy != null) {
            proxy.setNoProxy("");
            dc.setCapability(CapabilityType.PROXY, proxy);
        }
        dc.setCapability(ChromeOptions.CAPABILITY, options);
        driver = new ChromeDriver(dc);
    }

    /**
     * @since 5.7
     */
    protected static File findExecutableOnPath(String executableName) {
        String systemPath = System.getenv("PATH");
        String[] pathDirs = systemPath.split(File.pathSeparator);
        File fullyQualifiedExecutable = null;
        for (String pathDir : pathDirs) {
            File file = new File(pathDir, executableName);
            if (file.isFile()) {
                fullyQualifiedExecutable = file;
                break;
            }
        }
        return fullyQualifiedExecutable;
    }

    @AfterClass
    public static void quitDriver() throws InterruptedException {
        if (driver != null) {
            driver.quit();
            driver = null;
        }

        removeFireBug();

        try {
            stopProxy();
        } catch (Exception e) {
            System.err.println("Could not stop proxy: " + e.getMessage());
        }
    }

    /**
     * Introspects the classpath and returns the list of files in it. FIXME:
     * should use HarnessRuntime#getClassLoaderFiles that returns the same
     * thing
     *
     * @return
     * @throws Exception
     */
    protected static List<String> getClassLoaderFiles() throws Exception {
        ClassLoader cl = AbstractTest.class.getClassLoader();
        URL[] urls = null;
        if (cl instanceof URLClassLoader) {
            urls = ((URLClassLoader) cl).getURLs();
        } else if (cl.getClass().getName().equals("org.apache.tools.ant.AntClassLoader")) {
            Method method = cl.getClass().getMethod("getClasspath");
            String cp = (String) method.invoke(cl);
            String[] paths = cp.split(File.pathSeparator);
            urls = new URL[paths.length];
            for (int i = 0; i < paths.length; i++) {
                urls[i] = new URL("file:" + paths[i]);
            }
        } else {
            System.err.println("Unknown classloader type: " + cl.getClass().getName());
            return null;
        }

        JarFile surefirebooterJar = null;
        for (URL url : urls) {
            URI uri = url.toURI();
            if (uri.getPath().matches(".*/nuxeo-runtime-[^/]*\\.jar")) {
                break;
            } else if (uri.getScheme().equals("file") && uri.getPath().contains("surefirebooter")) {
                surefirebooterJar = new JarFile(new File(uri));
            }
        }

        // special case for maven surefire with useManifestOnlyJar
        if (surefirebooterJar != null) {
            try {
                try {
                    String cp = surefirebooterJar.getManifest().getMainAttributes()
                            .getValue(Attributes.Name.CLASS_PATH);
                    if (cp != null) {
                        String[] cpe = cp.split(" ");
                        URL[] newUrls = new URL[cpe.length];
                        for (int i = 0; i < cpe.length; i++) {
                            // Don't need to add 'file:' with maven
                            // surefire >= 2.4.2
                            String newUrl = cpe[i].startsWith("file:") ? cpe[i] : "file:" + cpe[i];
                            newUrls[i] = new URL(newUrl);
                        }
                        urls = newUrls;
                    }
                } finally {
                    surefirebooterJar.close();
                }
            } catch (Exception e) {
                // skip
            }
        }
        // turn into files
        List<String> files = new ArrayList<String>(urls.length);
        for (URL url : urls) {
            files.add(url.toURI().getPath());
        }
        return files;
    }

    private static final String M2_REPO = "repository/";

    protected static void addFireBug(FirefoxProfile profile) throws Exception {
        // this is preventing from running tests in eclipse
        // profile.addExtension(AbstractTest.class, "/firebug.xpi");

        File xpi = null;
        List<String> clf = getClassLoaderFiles();
        for (String f : clf) {
            if (f.endsWith("/" + FIREBUG_XPI)) {
                xpi = new File(f);
            }
        }
        if (xpi == null) {
            String customM2Repo = System.getProperty("M2_REPO", M2_REPO).replaceAll("/$", "");
            // try to guess the location in the M2 repo
            for (String f : clf) {
                if (f.contains(customM2Repo)) {
                    String m2 = f.substring(0, f.indexOf(customM2Repo) + customM2Repo.length());
                    xpi = new File(m2 + "/" + FIREBUG_M2 + "/" + FIREBUG_XPI);
                    break;
                }
            }
        }
        if (xpi == null || !xpi.exists()) {
            log.warn(FIREBUG_XPI + " not found in classloader or local M2 repository");
            return;
        }
        profile.addExtension(xpi);

        // avoid "first run" page
        profile.setPreference("extensions.firebug.currentVersion", FIREBUG_VERSION);
    }

    protected static void removeFireBug() {
        if (tmp_firebug_xpi != null) {
            tmp_firebug_xpi.delete();
            tmp_firebug_xpi.getParentFile().delete();
        }
    }

    protected static Proxy startProxy() throws Exception {
        if (Boolean.TRUE.equals(Boolean.valueOf(System.getProperty("useProxy", "false")))) {
            proxyServer = new ProxyServer(PROXY_PORT);
            proxyServer.start();
            proxyServer.setCaptureHeaders(true);
            // Block access to tracking sites
            proxyServer.blacklistRequests("https?://www\\.nuxeo\\.com/embedded/wizard.*", 410);
            proxyServer.blacklistRequests("https?://.*\\.mktoresp\\.com/.*", 410);
            proxyServer.blacklistRequests(".*_mchId.*", 410);
            proxyServer.blacklistRequests("https?://.*\\.google-analytics\\.com/.*", 410);
            proxyServer.newHar("webdriver-test");
            Proxy proxy = proxyServer.seleniumProxy();
            return proxy;
        } else {
            return null;
        }
    }

    protected static void stopProxy() throws Exception {
        if (proxyServer != null) {
            String target = System.getProperty("nuxeo.log.dir");
            File harFile;
            if (target == null) {
                harFile = new File(HAR_NAME);
            } else {
                harFile = new File(target, HAR_NAME);
            }
            proxyServer.getHar().writeTo(harFile);
            proxyServer.stop();
        }
    }

    public static <T> T get(String url, Class<T> pageClassToProxy) {
        driver.get(url);
        return asPage(pageClassToProxy);
    }

    public static <T> T asPage(Class<T> pageClassToProxy) {
        T page = instantiatePage(pageClassToProxy);
        return fillElement(pageClassToProxy, page);
    }

    public static <T extends WebFragment> T getWebFragment(By by, Class<T> webFragmentClass) {
        WebElement element = findElementWithTimeout(by);
        return getWebFragment(element, webFragmentClass);
    }

    public static <T extends WebFragment> T getWebFragment(WebElement element, Class<T> webFragmentClass) {
        T webFragment = instantiateWebFragment(element, webFragmentClass);
        webFragment = fillElement(webFragmentClass, webFragment);
        // fillElement somehow overwrite the 'element' field, reset it.
        webFragment.setElement(element);
        return webFragment;
    }

    /**
     * Fills an instantiated page/form/widget attributes
     *
     * @since 5.7
     */
    public static <T> T fillElement(Class<T> pageClassToProxy, T page) {
        PageFactory.initElements(new VariableElementLocatorFactory(driver, AJAX_TIMEOUT_SECONDS), page);
        // check all required WebElements on the page and wait for their
        // loading
        List<String> fieldNames = new ArrayList<String>();
        List<WrapsElement> elements = new ArrayList<WrapsElement>();
        for (Field field : pageClassToProxy.getDeclaredFields()) {
            if (field.getAnnotation(Required.class) != null) {
                try {
                    field.setAccessible(true);
                    fieldNames.add(field.getName());
                    elements.add((WrapsElement) field.get(page));
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
        Clock clock = new SystemClock();
        long end = clock.laterBy(SECONDS.toMillis(LOAD_TIMEOUT_SECONDS));
        String notLoaded = null;
        while (clock.isNowBefore(end)) {
            notLoaded = anyElementNotLoaded(elements, fieldNames);
            if (notLoaded == null) {
                return page;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                // ignore
            }
        }
        throw new NoSuchElementException(
                "Timeout loading page " + pageClassToProxy.getSimpleName() + " missing element " + notLoaded);
    }

    protected static String anyElementNotLoaded(List<WrapsElement> proxies, List<String> fieldNames) {
        for (int i = 0; i < proxies.size(); i++) {
            WrapsElement proxy = proxies.get(i);
            try {
                // method implemented in LocatingElementHandler
                proxy.getWrappedElement();
            } catch (NoSuchElementException e) {
                return fieldNames.get(i);
            }
        }
        return null;
    }

    // private in PageFactory...
    protected static <T> T instantiatePage(Class<T> pageClassToProxy) {
        try {
            try {
                Constructor<T> constructor = pageClassToProxy.getConstructor(WebDriver.class);
                return constructor.newInstance(driver);
            } catch (NoSuchMethodException e) {
                return pageClassToProxy.newInstance();
            }
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    protected static <T extends WebFragment> T instantiateWebFragment(WebElement element,
            Class<T> webFragmentClass) {
        try {
            try {
                Constructor<T> constructor = webFragmentClass.getConstructor(WebDriver.class, WebElement.class);
                return constructor.newInstance(driver, element);
            } catch (NoSuchMethodException e) {
                return webFragmentClass.newInstance();
            }
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Returns true if corresponding element is found in the test page.
     *
     * @since 5.7
     */
    public boolean hasElement(By by) {
        boolean present;
        try {
            driver.findElement(by);
            present = true;
        } catch (NoSuchElementException e) {
            present = false;
        }
        return present;
    }

    /**
     * Finds the first {@link WebElement} using the given method, with a
     * timeout.
     *
     * @param by the locating mechanism
     * @param timeout the timeout in milliseconds
     * @return the first matching element on the current page, if found
     * @throws NoSuchElementException when not found
     */
    public static WebElement findElementWithTimeout(By by, int timeout) throws NoSuchElementException {
        return findElementWithTimeout(by, timeout, null);
    }

    /**
     * Finds the first {@link WebElement} using the given method, with a
     * timeout.
     *
     * @param by the locating mechanism
     * @param timeout the timeout in milliseconds
     * @param parentElement find from the element
     * @return the first matching element on the current page, if found
     * @throws NoSuchElementException when not found
     */
    public static WebElement findElementWithTimeout(By by, int timeout, WebElement parentElement)
            throws NoSuchElementException {
        Clock clock = new SystemClock();
        long end = clock.laterBy(timeout);
        NoSuchElementException lastException = null;
        while (clock.isNowBefore(end)) {
            try {
                WebElement element;
                if (parentElement == null) {
                    element = driver.findElement(by);
                } else {
                    element = parentElement.findElement(by);
                }
                if (element != null) {
                    return element;
                }
            } catch (NoSuchElementException e) {
                lastException = e;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                // ignore
            }
        }
        throw new NoSuchElementException(String.format("Couldn't find element '%s' after timeout", by),
                lastException);
    }

    /**
     * Finds the first {@link WebElement} using the given method, with a
     * timeout.
     *
     * @param by the locating mechanism
     * @param timeout the timeout in milliseconds
     * @return the first matching element on the current page, if found
     * @throws NoSuchElementException when not found
     */
    public static WebElement findElementWithTimeout(By by) throws NoSuchElementException {
        return findElementWithTimeout(by, LOAD_TIMEOUT_SECONDS * 1000);
    }

    public static List<WebElement> findElementsWithTimeout(final By by) throws NoSuchElementException {
        Wait<WebDriver> wait = new FluentWait<WebDriver>(driver).withTimeout(LOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS)
                .pollingEvery(POLLING_FREQUENCY_SECONDS, TimeUnit.SECONDS).ignoring(NoSuchElementException.class);
        return wait.until(new Function<WebDriver, List<WebElement>>() {
            public List<WebElement> apply(WebDriver driver) {
                List<WebElement> elements = driver.findElements(by);
                return elements.isEmpty() ? null : elements;
            }
        });
    }

    /**
     * Fluent wait for an element to be present, checking every 5 seconds
     *
     * @since 5.7.2
     */
    public static WebElement waitUntilElementPresent(final By locator) {
        Wait<WebDriver> wait = new FluentWait<WebDriver>(driver).withTimeout(LOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS)
                .pollingEvery(POLLING_FREQUENCY_SECONDS, TimeUnit.SECONDS).ignoring(NoSuchElementException.class);
        WebElement elt = wait.until(new Function<WebDriver, WebElement>() {
            public WebElement apply(WebDriver driver) {
                return driver.findElement(locator);
            }
        });
        return elt;
    }

    /**
     * Fluent wait for an element not to be present, checking every 5 seconds.
     *
     * @since 5.7.2
     */
    public static void waitUntilElementNotPresent(final By locator) {
        Wait<WebDriver> wait = new FluentWait<WebDriver>(driver).withTimeout(LOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS)
                .pollingEvery(POLLING_FREQUENCY_SECONDS, TimeUnit.SECONDS);
        wait.until((new Function<WebDriver, By>() {
            public By apply(WebDriver driver) {
                try {
                    driver.findElement(locator);
                } catch (NoSuchElementException ex) {
                    // ok
                    return locator;
                }
                return null;
            }
        }));
    }

    /**
     * Fluent wait for text to be present in the element retrieved with the
     * given method.
     *
     * @since 5.7.3
     */
    public static void waitForTextPresent(By locator, String text) {
        Wait<WebDriver> wait = new FluentWait<WebDriver>(driver).withTimeout(LOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS)
                .pollingEvery(POLLING_FREQUENCY_SECONDS, TimeUnit.SECONDS);
        wait.until(ExpectedConditions.textToBePresentInElement(locator, text));
    }

    /**
     * Fluent wait for text to be present in the given element.
     *
     * @since 5.7.3
     */
    public static void waitForTextPresent(final WebElement element, final String text) {
        Wait<WebDriver> wait = new FluentWait<WebDriver>(driver).withTimeout(LOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS)
                .pollingEvery(POLLING_FREQUENCY_SECONDS, TimeUnit.SECONDS);
        wait.until((new Function<WebDriver, Boolean>() {
            public Boolean apply(WebDriver driver) {
                try {
                    return element.getText().contains(text);
                } catch (StaleElementReferenceException e) {
                    return null;
                }
            }
        }));
    }

    /**
     * Fluent wait for text to be not present in the given element.
     *
     * @since 5.7.3
     */
    public static void waitForTextNotPresent(final WebElement element, final String text) {
        Wait<WebDriver> wait = new FluentWait<WebDriver>(driver).withTimeout(LOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS)
                .pollingEvery(POLLING_FREQUENCY_SECONDS, TimeUnit.SECONDS);
        wait.until((new Function<WebDriver, Boolean>() {
            public Boolean apply(WebDriver driver) {
                try {
                    return !element.getText().contains(text);
                } catch (StaleElementReferenceException e) {
                    return null;
                }
            }
        }));
    }

    /**
     * Returns true if {@code text} is present in the element retrieved with
     * the given method.
     *
     * @since 5.7.3
     */
    public static boolean isTextPresent(By by, String text) {
        return isTextPresent(driver.findElement(by), text);
    }

    /**
     * Returns true if {@code text} is present in the given element.
     *
     * @since 5.7.3
     */
    public static boolean isTextPresent(WebElement element, String text) {
        return element.getText().contains(text);
    }

    /**
     * Finds the first {@link WebElement} using the given method, with a
     * timeout.
     *
     * @param by the locating mechanism
     * @param timeout the timeout in milliseconds
     * @param parentElement find from the element
     * @return the first matching element on the current page, if found
     * @throws NoSuchElementException when not found
     */
    public static WebElement findElementWithTimeout(By by, WebElement parentElement) throws NoSuchElementException {
        return findElementWithTimeout(by, LOAD_TIMEOUT_SECONDS * 1000, parentElement);
    }

    /**
     * Waits until an element is enabled, with a timeout.
     *
     * @param element the element
     * @param timeout the timeout in milliseconds
     */
    public static void waitUntilEnabled(final WebElement element, int timeout) throws NotFoundException {
        Clock clock = new SystemClock();
        long end = clock.laterBy(timeout);
        while (clock.isNowBefore(end)) {
            if (element.isEnabled()) {
                return;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                // ignore
            }
        }
        throw new NotFoundException("Element not enabled after timeout: " + element);
    }

    /**
     * Waits until an element is enabled, with a timeout.
     *
     * @param element the element
     */
    public static void waitUntilEnabled(WebElement element) throws NotFoundException {
        waitUntilEnabled(element, AJAX_TIMEOUT_SECONDS * 1000);
    }

    /**
     * Finds the first {@link WebElement} using the given method, with a
     * {@code findElementTimeout}. Then waits until the element is enabled,
     * with a {@code waitUntilEnabledTimeout}.
     *
     * @param by the locating mechanism
     * @param findElementTimeout the find element timeout in milliseconds
     * @param waitUntilEnabledTimeout the wait until enabled timeout in
     *            milliseconds
     * @return the first matching element on the current page, if found
     * @throws NotFoundException if the element is not found or not enabled
     */
    public static WebElement findElementAndWaitUntilEnabled(By by, int findElementTimeout,
            int waitUntilEnabledTimeout) throws NotFoundException {

        // Find the element.
        WebElement element = findElementWithTimeout(by, findElementTimeout);

        // Try to wait until the element is enabled.
        Clock clock = new SystemClock();
        long end = clock.laterBy(findElementTimeout);
        WebDriverException lastException = null;
        while (clock.isNowBefore(end)) {
            try {
                waitUntilEnabled(element, waitUntilEnabledTimeout);
                return element;
            } catch (StaleElementReferenceException sere) {
                // Means the element is no longer attached to the DOM
                // => need to find it again.
                element = findElementWithTimeout(by, findElementTimeout);
                lastException = sere;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                // ignore
            }
        }
        throw new NotFoundException(String.format("Couldn't find element '%s' after timeout", by), lastException);
    }

    /**
     * Finds the first {@link WebElement} using the given method, with the
     * default timeout. Then waits until the element is enabled, with the
     * default timeout.
     *
     * @param by the locating mechanism
     * @return the first matching element on the current page, if found
     * @throws NotFoundException if the element is not found or not enabled
     */
    public static WebElement findElementAndWaitUntilEnabled(By by) throws NotFoundException {
        return findElementAndWaitUntilEnabled(by, LOAD_TIMEOUT_SECONDS * 1000, AJAX_TIMEOUT_SECONDS * 1000);
    }

    /**
     * Finds the first {@link WebElement} using the given method, with a
     * {@code findElementTimeout}. Then waits until the element is enabled,
     * with a {@code waitUntilEnabledTimeout}. Then clicks on the element.
     *
     * @param by the locating mechanism
     * @param findElementTimeout the find element timeout in milliseconds
     * @param waitUntilEnabledTimeout the wait until enabled timeout in
     *            milliseconds
     * @throws NotFoundException if the element is not found or not enabled
     */
    public static void findElementWaitUntilEnabledAndClick(By by, int findElementTimeout,
            int waitUntilEnabledTimeout) throws NotFoundException {

        // Find the element.
        WebElement element = findElementAndWaitUntilEnabled(by, findElementTimeout, waitUntilEnabledTimeout);

        // Try to click on the element.
        Clock clock = new SystemClock();
        long end = clock.laterBy(findElementTimeout);
        WebDriverException lastException = null;
        while (clock.isNowBefore(end)) {
            try {
                element.click();
                return;
            } catch (ElementNotVisibleException enve) {
                // Means the element is no visible yet
                // => need to find it again.
                element = findElementAndWaitUntilEnabled(by, findElementTimeout, waitUntilEnabledTimeout);
                lastException = enve;
            } catch (StaleElementReferenceException sere) {
                // Means the element is no longer attached to the DOM
                // => need to find it again.
                element = findElementAndWaitUntilEnabled(by, findElementTimeout, waitUntilEnabledTimeout);
                lastException = sere;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                // ignore
            }
        }
        throw new NotFoundException(String.format("Couldn't find element '%s' after timeout", by), lastException);
    }

    /**
     * Finds the first {@link WebElement} using the given method, with the
     * default timeout. Then waits until the element is enabled, with the
     * default timeout. Then clicks on the element.
     *
     * @param by the locating mechanism
     * @throws NotFoundException if the element is not found or not enabled
     */
    public static void findElementWaitUntilEnabledAndClick(By by) throws NotFoundException {
        findElementWaitUntilEnabledAndClick(by, LOAD_TIMEOUT_SECONDS * 1000, AJAX_TIMEOUT_SECONDS * 1000);
    }

    public LoginPage getLoginPage() {
        return get(NUXEO_URL + "/logout", LoginPage.class);
    }

    public LoginPage logout() {
        return getLoginPage();
    }

    /**
     * navigate to a link text. wait until the link is available and click on
     * it.
     */
    public <T extends AbstractPage> T nav(Class<T> pageClass, String linkText) {
        WebElement link = findElementWithTimeout(By.linkText(linkText));
        if (link == null) {
            return null;
        }
        link.click();
        return asPage(pageClass);
    }

    /**
     * Navigate to a specified url
     *
     * @param urlString url
     * @throws MalformedURLException
     */
    public void navToUrl(String urlString) throws MalformedURLException {
        URL url = new URL(urlString);
        driver.navigate().to(url);
    }

    /**
     * Login as Administrator
     *
     * @return the Document base page (by default returned by nuxeo dm)
     * @throws UserNotConnectedException
     */
    public DocumentBasePage login() throws UserNotConnectedException {
        return login("Administrator", "Administrator");
    }

    public DocumentBasePage login(String username, String password) throws UserNotConnectedException {
        DocumentBasePage documentBasePage = getLoginPage().login(username, password, DocumentBasePage.class);
        documentBasePage.checkUserConnected(username);
        return documentBasePage;
    }

    /**
     * Login using an invalid credential.
     *
     * @param username
     * @param password
     */
    public LoginPage loginInvalid(String username, String password) {
        LoginPage loginPage = getLoginPage().login(username, password, LoginPage.class);
        return loginPage;
    }

    /**
     * Init the repository with a test Workspace form the {@code currentPage}.
     *
     * @param currentPage the current page
     * @return the created Workspace page
     * @throws Exception if initializing repository fails
     */
    protected DocumentBasePage initRepository(DocumentBasePage currentPage) throws Exception {

        return createWorkspace(currentPage, "Test Workspace", "Test Workspace for my dear WebDriver.");
    }

    /**
     * Cleans the repository (delete the test Workspace) from the
     * {@code currentPage}.
     *
     * @param currentPage the current page
     * @throws Exception if cleaning repository fails
     */
    protected void cleanRepository(DocumentBasePage currentPage) throws Exception {

        deleteWorkspace(currentPage, "Test Workspace");
    }

    /**
     * Creates a Workspace form the {@code currentPage}.
     *
     * @param currentPage the current page
     * @param workspaceTitle the workspace title
     * @param workspaceDescription the workspace description
     * @return the created Workspace page
     */
    protected DocumentBasePage createWorkspace(DocumentBasePage currentPage, String workspaceTitle,
            String workspaceDescription) {

        // Go to Workspaces
        DocumentBasePage workspacesPage = currentPage.getNavigationSubPage().goToDocument("Workspaces");

        // Get Workspace creation form page
        WorkspaceFormPage workspaceCreationFormPage = workspacesPage.getWorkspacesContentTab()
                .getWorkspaceCreatePage();

        // Create Workspace
        DocumentBasePage workspacePage = workspaceCreationFormPage.createNewWorkspace(workspaceTitle,
                workspaceDescription);
        return workspacePage;
    }

    /**
     * Deletes the Workspace with title {@code workspaceTitle} from the
     * {@code currentPage}.
     *
     * @param currentPage the current page
     * @param workspaceTitle the workspace title
     */
    protected void deleteWorkspace(DocumentBasePage currentPage, String workspaceTitle) {

        // Go to Workspaces
        DocumentBasePage workspacesPage = currentPage.getNavigationSubPage().goToDocument("Workspaces");

        // Delete the Workspace
        workspacesPage.getContentTab().removeDocument(workspaceTitle);
    }

    /**
     * Creates a File form the {@code currentPage}.
     *
     * @param currentPage the current page
     * @param fileTitle the file title
     * @param fileDescription the file description
     * @param uploadBlob true if a blob needs to be uploaded (temporary file
     *            created for this purpose)
     * @param filePrefix the file prefix
     * @param fileSuffix the file suffix
     * @param fileContent the file content
     * @return the created File page
     * @throws IOException if temporary file creation fails
     */
    protected FileDocumentBasePage createFile(DocumentBasePage currentPage, String fileTitle,
            String fileDescription, boolean uploadBlob, String filePrefix, String fileSuffix, String fileContent)
            throws IOException {

        // Get File creation form page
        FileCreationFormPage fileCreationFormPage = currentPage.getContentTab().getDocumentCreatePage("File",
                FileCreationFormPage.class);

        // Create File
        FileDocumentBasePage filePage = fileCreationFormPage.createFileDocument(fileTitle, fileDescription,
                uploadBlob, filePrefix, fileSuffix, fileDescription);
        return filePage;
    }

    /**
     * @deprecated since 5.7, use a {@link FileWidgetElement} instead.
     */
    @Deprecated
    protected String getTmpFileToUploadPath(String filePrefix, String fileSuffix, String fileContent)
            throws IOException {
        throw new UnsupportedOperationException(
                "Method is deprecated since 5.7," + " use a FileWidgetElement instead");
    }

    /**
     * Get the current document id stored in the javascript ctx.currentDocument
     * variable of the current page.
     *
     * @return the current document id
     * @since 5.7
     */
    protected String getCurrentDocumentId() {
        return (String) ((JavascriptExecutor) driver).executeScript(String.format("return ctx.currentDocument;"));
    }

}