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