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