/*
* (C) Copyright 2011-2016 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Sun Seng David TAN
* Florent Guillaume
* Benoit Delbosc
* Antoine Taillefer
* Anahide Tchertchian
* Guillaume Renard
* Mathieu Guillaume
* Julien Carsique
*/
package org.nuxeo.functionaltests;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.nuxeo.functionaltests.Constants.ADMINISTRATOR;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.FileUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.rules.MethodRule;
import org.nuxeo.functionaltests.drivers.ChromeDriverProvider;
import org.nuxeo.functionaltests.drivers.FirefoxDriverProvider;
import org.nuxeo.functionaltests.drivers.RemoteFirefoxDriverProvider;
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.NoteDocumentBasePage;
import org.nuxeo.functionaltests.pages.tabs.CollectionContentTabSubPage;
import org.nuxeo.functionaltests.proxy.ProxyManager;
import org.nuxeo.runtime.api.Framework;
import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.Proxy;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.internal.WrapsElement;
import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.Command;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.DriverCommand;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.support.PageFactory;
import org.openqa.selenium.support.ui.FluentWait;
import org.openqa.selenium.support.ui.Wait;
import com.google.common.collect.ImmutableMap;
/**
* Base functions for all pages.
*/
public abstract class AbstractTest {
/**
* @since 5.9.2
*/
public final static String TEST_USERNAME = "jdoe";
/**
* @since 5.9.2
*/
public final static String TEST_PASSWORD = "test";
/**
* Polling frequency in milliseconds.
*
* @since 5.9.2
*/
public static final int POLLING_FREQUENCY_MILLISECONDS = 100;
public static final int POLLING_FREQUENCY_SECONDS = 1;
/**
* Page Load timeout in seconds.
*
* @since 5.9.2
*/
public static final int PAGE_LOAD_TIME_OUT_SECONDS = 60;
public static final int LOAD_TIMEOUT_SECONDS = 30;
/**
* Driver implicit wait in milliseconds.
*
* @since 8.3
*/
public static final int IMPLICIT_WAIT_MILLISECONDS = 200;
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;
/**
* @since 5.7
* @deprecated since 8.3
* @see ChromeDriverProvider
*/
@Deprecated
public static final String CHROME_DRIVER_DEFAULT_PATH_LINUX = ChromeDriverProvider.CHROME_DRIVER_DEFAULT_PATH_LINUX;
/**
* @since 5.7 "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" doesn't work
* @deprecated since 8.3
* @see ChromeDriverProvider
*/
@Deprecated
public static final String CHROME_DRIVER_DEFAULT_PATH_MAC = ChromeDriverProvider.CHROME_DRIVER_DEFAULT_PATH_MAC;
/**
* @since 5.7
* @deprecated since 8.3
* @see ChromeDriverProvider
*/
@Deprecated
public static final String CHROME_DRIVER_DEFAULT_PATH_WINVISTA = ChromeDriverProvider.CHROME_DRIVER_DEFAULT_PATH_WINVISTA;
/**
* @since 5.7
* @deprecated since 8.3
* @see ChromeDriverProvider
*/
@Deprecated
public static final String CHROME_DRIVER_DEFAULT_PATH_WINXP = ChromeDriverProvider.CHROME_DRIVER_DEFAULT_PATH_WINXP;
/**
* @since 5.7
* @deprecated since 8.3
* @see ChromeDriverProvider
*/
@Deprecated
public static final String CHROME_DRIVER_DEFAULT_EXECUTABLE_NAME = ChromeDriverProvider.CHROME_DRIVER_DEFAULT_EXECUTABLE_NAME;
/**
* @since 5.7
* @deprecated since 8.3
* @see ChromeDriverProvider
*/
@Deprecated
public static final String CHROME_DRIVER_WINDOWS_EXECUTABLE_NAME = ChromeDriverProvider.CHROME_DRIVER_WINDOWS_EXECUTABLE_NAME;
/**
* @deprecated since 8.3
* @see ChromeDriverProvider
*/
@Deprecated
public static final String SYSPROP_CHROME_DRIVER_PATH = ChromeDriverProvider.SYSPROP_CHROME_DRIVER_PATH;
static final Log log = LogFactory.getLog(AbstractTest.class);
public static final String NUXEO_URL = System.getProperty("nuxeoURL", "http://localhost:8080/nuxeo")
.replaceAll("/$", "");
public static RemoteWebDriver driver;
protected static ProxyManager proxyManager;
/**
* 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("remotefirefox")) {
initRemoteFirefoxDriver();
} else if (browser.equals("googlechrome")) {
initChromeDriver();
} else {
throw new RuntimeException("Browser not supported: " + browser);
}
driver.manage().timeouts().pageLoadTimeout(PAGE_LOAD_TIME_OUT_SECONDS, TimeUnit.SECONDS);
driver.manage().timeouts().implicitlyWait(IMPLICIT_WAIT_MILLISECONDS, TimeUnit.MILLISECONDS);
}
protected static void initFirefoxDriver() throws Exception {
proxyManager = new ProxyManager();
Proxy proxy = proxyManager.startProxy();
if (proxy != null) {
proxy.setNoProxy("");
}
DesiredCapabilities dc = DesiredCapabilities.firefox();
dc.setCapability(CapabilityType.PROXY, proxy);
driver = new FirefoxDriverProvider().init(dc);
}
protected static void initRemoteFirefoxDriver() throws Exception {
proxyManager = new ProxyManager();
Proxy proxy = proxyManager.startProxy();
if (proxy != null) {
proxy.setNoProxy("");
}
DesiredCapabilities dc = DesiredCapabilities.firefox();
dc.setCapability(CapabilityType.PROXY, proxy);
driver = new RemoteFirefoxDriverProvider().init(dc);
}
protected static void initChromeDriver() throws Exception {
proxyManager = new ProxyManager();
Proxy proxy = proxyManager.startProxy();
DesiredCapabilities dc = DesiredCapabilities.chrome();
if (proxy != null) {
proxy.setNoProxy("");
dc.setCapability(CapabilityType.PROXY, proxy);
}
driver = new ChromeDriverProvider().init(dc);
}
/**
* @since 7.1
*/
@After
public void checkJavascriptError() {
if (driver != null) {
new JavaScriptErrorCollector(driver).checkForErrors();
}
}
@AfterClass
public static void quitDriver() {
if (driver != null) {
driver.quit();
driver = null;
}
try {
proxyManager.stopProxy();
proxyManager = null;
} catch (Exception e) {
log.error("Could not stop proxy: " + e.getMessage());
}
}
public static <T> T get(String url, Class<T> pageClassToProxy) {
if (driver != null) {
new JavaScriptErrorCollector(driver).checkForErrors();
}
driver.get(url);
return asPage(pageClassToProxy);
}
/**
* Opens given url adding hardcoded Seam conversation named "0NXMAIN".
*
* @since 8.3
*/
public static void open(String url) {
if (driver != null) {
new JavaScriptErrorCollector(driver).checkForErrors();
}
driver.get(NUXEO_URL + url + "?conversationId=0NXMAIN");
}
/**
* Do not wait for page load. Do not handle error. Do not give explicit error in case of failure. This is a very raw
* get.
*
* @since 6.0
*/
public static <T> T getWithoutErrorHandler(String url, Class<T> pageClassToProxy) throws IOException {
Command command = new Command(AbstractTest.driver.getSessionId(), DriverCommand.GET,
ImmutableMap.of("url", url));
AbstractTest.driver.getCommandExecutor().execute(command);
return asPage(pageClassToProxy);
}
public static WebDriver getPopup() {
String currentWindow = driver.getWindowHandle();
for (String popup : driver.getWindowHandles()) {
if (popup.equals(currentWindow)) {
continue;
}
return driver.switchTo().window(popup);
}
return null;
}
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 = Locator.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
final List<String> fieldNames = new ArrayList<>();
final List<WrapsElement> elements = new ArrayList<>();
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);
}
}
}
Wait<T> wait = new FluentWait<>(page).withTimeout(LOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS)
.pollingEvery(POLLING_FREQUENCY_MILLISECONDS, TimeUnit.MILLISECONDS);
try {
return wait.until(aPage -> {
String notLoaded = anyElementNotLoaded(elements, fieldNames);
if (notLoaded == null) {
// check if there are Jquery ajax requests to complete
if (pageClassToProxy.isAnnotationPresent(WaitForJQueryAjaxOnLoading.class)) {
new AjaxRequestManager(driver).waitForJQueryRequests();
}
return aPage;
} else {
return null;
}
});
} catch (TimeoutException e) {
throw new TimeoutException("not loaded: " + anyElementNotLoaded(elements, fieldNames), e);
}
}
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);
}
}
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 = Locator.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 CAP)
* @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 as default test user.
*
* @since 5.9.2
*/
public DocumentBasePage loginAsTestUser() throws UserNotConnectedException {
return login(TEST_USERNAME, TEST_PASSWORD);
}
/**
* Login using an invalid credential.
*
* @param username the username
* @param password the password
*/
public LoginPage loginInvalid(String username, String password) {
return getLoginPage().login(username, password, LoginPage.class);
}
/**
* 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
* @deprecated since 8.3
*/
@Deprecated
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
* @deprecated since 8.3
*/
@Deprecated
protected void cleanRepository(DocumentBasePage currentPage) throws Exception {
deleteWorkspace(currentPage, "Test Workspace");
}
/**
* Creates a Workspace from the {@code currentPage}.
*
* @param currentPage the current page
* @param workspaceTitle the workspace title
* @param workspaceDescription the workspace description
* @return the created Workspace page
* @deprecated since 8.3: use {@link DocumentBasePage#createWorkspace(String, String)} instead.
*/
@Deprecated
protected DocumentBasePage createWorkspace(DocumentBasePage currentPage, String workspaceTitle,
String workspaceDescription) {
return currentPage.createWorkspace(workspaceTitle, workspaceDescription);
}
/**
* Deletes the Workspace with title {@code workspaceTitle} from the {@code currentPage}.
*
* @param currentPage the current page
* @param workspaceTitle the workspace title
* @deprecated since 8.3: use {@link DocumentBasePage#deleteWorkspace(String)} instead.
*/
@Deprecated
protected void deleteWorkspace(DocumentBasePage currentPage, String workspaceTitle) {
currentPage.deleteWorkspace(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
* @deprecated since 8.3: use {@link DocumentBasePage#createFile(String, String, boolean, String, String, String)}
* instead.
*/
@Deprecated
protected FileDocumentBasePage createFile(DocumentBasePage currentPage, String fileTitle, String fileDescription,
boolean uploadBlob, String filePrefix, String fileSuffix, String fileContent) throws IOException {
return currentPage.createFile(fileTitle, fileDescription, uploadBlob, filePrefix, fileSuffix, fileContent);
}
/**
* Creates a Collections container form the {@code currentPage}.
*
* @param currentPage the current page
* @param collectionsTitle the Collections container title
* @param fileDescription the collections description
* @return the created Collections page
* @deprecated since 8.3: use {@link DocumentBasePage#createCollections(String, String)} instead.
*/
@Deprecated
protected DocumentBasePage createCollections(DocumentBasePage currentPage, String collectionsTitle,
String fileDescription) {
return currentPage.createCollections(collectionsTitle, fileDescription);
}
/**
* Creates a Collection form the {@code currentPage}.
*
* @param currentPage the current page
* @param collectionsTitle the Collections container title
* @param fileDescription the collection description
* @return the created Collections page
* @deprecated since 8.3: use {@link DocumentBasePage#createCollection(String, String)} instead.
*/
@Deprecated
protected CollectionContentTabSubPage createCollection(DocumentBasePage currentPage, String collectionsTitle,
String fileDescription) {
return currentPage.createCollection(collectionsTitle, fileDescription);
}
/**
* Creates a temporary file and returns its absolute path.
*
* @param filePrefix the file prefix
* @param fileSuffix the file suffix
* @param fileContent the file content
* @return the temporary file to upload path
* @throws IOException if temporary file creation fails
* @since 5.9.3
*/
public static String getTmpFileToUploadPath(String filePrefix, String fileSuffix, String fileContent)
throws IOException {
// Create tmp file, deleted on exit
File tmpFile = Framework.createTempFile(filePrefix, fileSuffix);
tmpFile.deleteOnExit();
FileUtils.writeStringToFile(tmpFile, fileContent);
assertTrue(tmpFile.exists());
// Check file URI protocol
assertEquals("file", tmpFile.toURI().toURL().getProtocol());
// Return file absolute path
return tmpFile.getAbsolutePath();
}
/**
* 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) driver.executeScript("return ctx.currentDocument;");
}
/**
* Creates a Note form the {@code currentPage}.
*
* @param currentPage the current page
* @param noteTitle the note title
* @param noteDescription the note description
* @param defineNote true if the content of the note needs to be defined
* @param noteContent the content of the note
* @return the created note page.
* @throws IOException
* @since 5.9.4
* @deprecated since 8.3: use {@link DocumentBasePage#createNote(String, String, boolean, String)} instead.
*/
@Deprecated
protected NoteDocumentBasePage createNote(DocumentBasePage currentPage, String noteTitle, String noteDescription,
boolean defineNote, String noteContent) throws IOException {
return currentPage.createNote(noteTitle, noteDescription, defineNote, noteContent);
}
}