package com.jazzautomation;
import com.google.common.base.Charsets;
import com.google.common.base.Optional;
import com.google.common.io.Resources;
import com.jazzautomation.action.PageAction;
import com.jazzautomation.page.DomElement;
import com.jazzautomation.page.Page;
import com.jazzautomation.ui.Browsers;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.codehaus.jackson.map.ObjectMapper;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.Proxy.ProxyType;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxProfile;
import org.openqa.selenium.ie.InternetExplorerDriver;
import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.safari.SafariDriver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import static com.jazzautomation.util.Constants.*;
/**
* UIManager holds all pages and their web components, page actions. It loads all configurations from settings.properties then go through all
* registeredPages to load page object classes as well as their xml configurations.
*/
@SuppressWarnings({
"AssignmentToStaticFieldFromInstanceMethod",
"UnusedDeclaration",
"TypeMayBeWeakened",
"StaticMethodOnlyUsedInOneClass",
"KeySetIterationMayUseEntrySet"
})
public class WebUIManager
{
private static final Logger LOG = LoggerFactory.getLogger(WebUIManager.class);
private static final String VERSION = "version";
public static final String SYSTEM_BROWSERS_SETTING = "browsers";
public static final String SYSTEM_REPORTS_PATH = "jazz.reports";
public static final String SYSTEM_CONFIGURATION_PATH = "jazz.configs";
public static final String WEBDRIVER_CHROME_DRIVER = "webdriver.chrome.driver";
private static WebUIManager instance;
// TODO - Move these to method level variables, or move this class away from a Singleton.
private Map<String, Page> pages = new HashMap<>();
private Map<String, DomElement> domElementPool = new HashMap<>();
private Map<String, List<PageAction>> pageActions = new HashMap<>();
// configurations and reportsPath
private String configurationsPath;
private String reportsPath;
// from jazz.properties
private String projectName = "";
private boolean useRemoteWebDriver;
private static String remoteWebDriverUrl;
private static int pageLoadTimeout = 10;
private static int actionPace = 1;
private String customClasspath;
private static String featureNames;
// from system properties
private Browsers browser = null;
private String platform = null;
private String browserVersion = null;
private Properties settings = new Properties();
/**
* Singleton instance of UIManager to hold information of pages and page actions.
*
* @return The singleton UIManager
*
* @throws IOException
*/
public static WebUIManager getInstance()
{
// jsheridan CODEREVIEW - Seems kludgy - can't you use Spring to get a singleton?
if (instance == null)
{
instance = new WebUIManager();
}
return instance;
}
/** called by the UI to reintialize the driver code so subsequent invocations will work. */
public static void reinitialize()
{
instance = null;
getInstance();
}
/*
* private constructor.
*/
private WebUIManager()
{
long startTime = System.currentTimeMillis();
configurationsPath = System.getProperty(SYSTEM_CONFIGURATION_PATH);
reportsPath = System.getProperty(SYSTEM_REPORTS_PATH);
browser = getBrowserFromSystemProperty();
String useRemote = System.getProperty("remote");
useRemoteWebDriver = BooleanUtils.toBoolean(useRemote);
File configurationsFile = StringUtils.isNotEmpty(configurationsPath) ? new File(configurationsPath)
: new File("configurations");
configurationsPath = configurationsFile.getAbsolutePath();
getOrCreateLogsPath();
LOG.info("Initializing Jazz Automation configuration sub-system");
// read settings files:
LOG.info("Reading jazz.properties from [" + configurationsPath + File.separator + "jazz.properties]");
LOG.info("Accessing reports directory at [" + reportsPath + "]");
try(InputStream stream = new FileInputStream(new File(configurationsPath, JAZZ + ".properties")))
{
settings.load(stream);
loadConfiguration();
long endTime = System.currentTimeMillis();
LOG.info("Jazz Automation started successfully in [" + (endTime - startTime) + "] milliseconds.");
}
catch (FileNotFoundException e)
{
LOG.error("An unexpected error has occurred; a configuration file could not be found.", e);
}
catch (IOException e)
{
LOG.error("An unexpected error has occurred.", e);
}
}
private Browsers getBrowserFromSystemProperty()
{
Optional<Browsers> possible = Browsers.findValueOf(System.getProperty(SYSTEM_BROWSERS_SETTING));
if (possible.isPresent())
{
return possible.get();
}
return Browsers.NOT_SPECIFIED;
}
private void getOrCreateLogsPath()
{
File logsFile;
if (StringUtils.isNotEmpty(reportsPath))
{
logsFile = new File(reportsPath);
// recursively create file path if needed
if (!logsFile.exists())
{
logsFile.mkdirs();
}
}
else
{
logsFile = new File("reports");
}
reportsPath = logsFile.getAbsolutePath();
}
// getter and setters
public Map<String, List<PageAction>> getPageActions()
{
return pageActions;
}
public List<PageAction> getPageActionListFromPage(String pageName)
{
return pageActions.get(pageName);
}
public DomElement getDomElementFromPool(String identifier)
{
return domElementPool.get(identifier);
}
/**
* Given a page name, return WebPage object in the Map of pages.
*
* @param pageName of the WebPage.
*
* @return WebPage.
*/
public Page getPage(String pageName)
{
return pages.get(pageName);
}
/**
* loading setting.properties and populate all pages from its configurations.
*
* @throws FileNotFoundException
*/
private void loadConfiguration() throws IOException
{
if (StringUtils.isNotEmpty(settings.getProperty(PROJECT_NAME)))
{
projectName = settings.getProperty(PROJECT_NAME);
}
if (StringUtils.isNotEmpty(settings.getProperty(USE_REMOTE)))
{
useRemoteWebDriver = Boolean.valueOf(settings.getProperty(USE_REMOTE));
}
if (useRemoteWebDriver)
{
if (StringUtils.isNotEmpty(settings.getProperty(REMOTE_WEB_DRIVER_URL)))
{
remoteWebDriverUrl = settings.getProperty(REMOTE_WEB_DRIVER_URL);
}
else
{
LOG.error("When setting 'useRemote' to true, you must specify property: remoteWebDriverUrl.");
System.exit(0);
}
}
boolean useXml = false;
if (StringUtils.isNotEmpty(settings.getProperty(SETTINGS_USE_XML)))
{
useXml = Boolean.valueOf(settings.getProperty(SETTINGS_USE_XML));
}
String pagesDirectoryName = settings.getProperty(PAGES_DIRECTORY_NAME);
// by default, use pages as the folder name
pagesDirectoryName = (StringUtils.isEmpty(pagesDirectoryName)) ? "pages"
: pagesDirectoryName;
String pagesFolderPath = configurationsPath + File.separator + pagesDirectoryName;
if (settings.getProperty(PAGE_LOAD_TIMEOUT) != null)
{
pageLoadTimeout = Integer.parseInt(settings.getProperty(PAGE_LOAD_TIMEOUT).trim());
}
if (settings.getProperty(ACTION_PACE) != null)
{
actionPace = Integer.parseInt(settings.getProperty(ACTION_PACE).trim());
}
if (settings.getProperty(FEATURE_NAMES_EXECUTION) != null)
{
featureNames = settings.getProperty(FEATURE_NAMES_EXECUTION);
}
if (settings.getProperty(BROWSER) != null)
{
Optional<Browsers> possible = Browsers.findValueOf(settings.getProperty(BROWSER));
if (possible.isPresent())
{
browser = possible.get(); // only override if it exists
}
}
if (StringUtils.isNotBlank(settings.getProperty(CUSTOM_CLASSPATH)))
{
customClasspath = settings.getProperty(CUSTOM_CLASSPATH).trim();
}
// loop through directory, if useXml, add all files with xml postfix to pages, otherwise add all json files
File pageFolder = new File(pagesFolderPath);
if (!pageFolder.isDirectory())
{
LOG.error("'Pages' folder must be a directory, [" + pageFolder.getAbsolutePath() + "] is invalid.");
throw new RuntimeException('\'' + pageFolder.getAbsolutePath() + "' must be a directory. ");
}
List<String> fileNames = Arrays.asList(pageFolder.list());
// sort into alphabetical order so that we can always have override features for web components
Collections.sort(fileNames);
LOG.info("Processing configuration files at " + configurationsPath + File.separator + "pages" + File.separator + ":");
for (String fileName : fileNames)
{
addPage(useXml, pagesFolderPath, fileName);
}
}
/** add page entry with DOM elements. */
private void addPage(boolean useXml, String pagesFolderPath, String fileName) throws IOException
{
try(FileInputStream fileIn = new FileInputStream(pagesFolderPath + File.separator + fileName))
{
LOG.info("Processing page configuration file: " + fileName + ", useXml = " + useXml);
Page page;
if (useXml)
{
if (!fileName.toLowerCase().endsWith(".xml"))
{
LOG.info("Ignored file: " + fileName);
return;
}
try
{
JAXBContext jaxbContext = JAXBContext.newInstance(Page.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
page = (Page) jaxbUnmarshaller.unmarshal(fileIn);
LOG.info("Loaded web page " + page.getPageName() + " from " + fileName);
}
catch (JAXBException e)
{ // error out if we cannot marshall the XML into a page
e.printStackTrace();
return;
}
}
else
{
if (!fileName.toLowerCase().endsWith(".json"))
{
return;
}
try
{
ObjectMapper objectMapper = new ObjectMapper();
page = objectMapper.readValue(fileIn, Page.class);
}
catch (IOException e)
{
LOG.warn("Error parsing page [" + fileName + ']', e);
return;
}
}
// something is wrong with the page, let's ignore it.
if (page == null)
{
LOG.warn("Failed to load web page [" + fileName + "] continue to load next.");
return;
}
// put the pages into internal storage!
pages.put(page.getPageName(), page);
// add to domElementPool
Map<String, DomElement> domElementsInPage = page.getDomElements();
for (String componentIdentifier : domElementsInPage.keySet())
{
DomElement domElement = domElementsInPage.get(componentIdentifier);
domElement.setIdentifier(componentIdentifier);
// only add the first one in so that other page can override it.
if (domElementPool.get(componentIdentifier) == null)
{
domElement.setPageInfo(page.getPageName());
domElementPool.put(componentIdentifier, domElement);
}
}
LOG.info("Successfully adding page entry of " + page.getPageName() + " with domElements: " + page.getDomElements());
}
}
// building navigation graphs - future development
private void buildNavigationGraph()
{
// always starts from the first page object.
String startingPathName = settings.getProperty("startingPageName").trim();
List<PageAction> pageActionsForFirstPage = pageActions.get(startingPathName);
for (PageAction pageAction : pageActionsForFirstPage)
{
List<String> path = new ArrayList<>();
// path.add(startingPathName);
String pageName = pageAction.getSourcePageName();
String nextPageName = pageAction.getTargetPageName();
String actionString = PageAction.serializeList(pageAction.getActionChains().get(0));
String key = pageName + ':' + actionString;
path.add(key);
}
}
private boolean shouldPathStop(List<String> aPath, String key)
{
for (String pageName : aPath)
{
if (pageName.equals(key))
{
return true;
}
}
return false;
}
private ArrayList<String> clonePath(List<String> path)
{
ArrayList<String> newPath = new ArrayList<>();
for (String pageName : path)
{
newPath.add(pageName);
}
return newPath;
}
// chrome driver
public WebDriver getChromeDriver() throws MalformedURLException
{
DesiredCapabilities capabilities = DesiredCapabilities.chrome();
capabilities.setCapability("chrome.switches", Arrays.asList(settings.getProperty("chrome.chrome.switches")));
capabilities.setCapability(PLATFORM, applyPlatform());
WebDriver driver;
if (useRemoteWebDriver)
{
driver = new RemoteWebDriver(new URL(remoteWebDriverUrl), capabilities);
}
else
{
if (System.getProperty(WEBDRIVER_CHROME_DRIVER) == null)
{
String chromeDriver = Drivers.CHROME.getDriverName();
if (StringUtils.isEmpty(chromeDriver))
{
throw new IllegalArgumentException("No chrome driver specified! Please specify as a system property or in your jazz.properties file.");
}
System.setProperty(WEBDRIVER_CHROME_DRIVER, chromeDriver);
}
driver = new ChromeDriver(capabilities);
}
return driver;
}
// jsheridan CODEREVIEW - note that if we used an enum, this logic could be moved into that, so the browser enum knew how to get it's driver
// firefox
public WebDriver getFirefoxDriver() throws MalformedURLException
{
WebDriver driver;
if (useRemoteWebDriver)
{
DesiredCapabilities capabilities = DesiredCapabilities.firefox();
capabilities.setCapability(PLATFORM, applyPlatform());
capabilities.setCapability(VERSION, applyVersion());
driver = new RemoteWebDriver(new URL(remoteWebDriverUrl), capabilities);
}
else
{
FirefoxProfile profile = new FirefoxProfile();
String proxyType = settings.getProperty("firefox.network.proxy.type");
if (proxyType.equalsIgnoreCase(ProxyType.AUTODETECT.name()))
{
profile.setPreference("network.proxy.type", ProxyType.AUTODETECT.ordinal());
}
else if (proxyType.equalsIgnoreCase(ProxyType.DIRECT.name()))
{
profile.setPreference("network.proxy.type", ProxyType.DIRECT.ordinal());
}
driver = new FirefoxDriver(profile);
driver.manage().window().maximize();
}
return driver;
}
// ie driver
public WebDriver getIEDriver() throws MalformedURLException
{
WebDriver driver;
if (useRemoteWebDriver)
{
DesiredCapabilities capabilities = DesiredCapabilities.internetExplorer();
capabilities.setCapability(PLATFORM, applyPlatform());
capabilities.setCapability(VERSION, applyVersion());
driver = new RemoteWebDriver(new URL(remoteWebDriverUrl), capabilities);
}
else
{
DesiredCapabilities capabilitiesInternet = new DesiredCapabilities();
capabilitiesInternet.setCapability("ignoreProtectedModeSettings", settings.getProperty("ie.ignoreProtectedModeSettings"));
capabilitiesInternet.setCapability("platform", applyPlatform());
capabilitiesInternet.setCapability(CapabilityType.VERSION, applyVersion());
driver = new InternetExplorerDriver(capabilitiesInternet);
}
driver.manage().window().maximize();
return driver;
}
// safari
public WebDriver getSafariDriver() throws MalformedURLException
{
if (useRemoteWebDriver)
{
DesiredCapabilities capabilities = DesiredCapabilities.safari();
capabilities.setCapability(PLATFORM, applyPlatform());
capabilities.setCapability(VERSION, applyVersion());
return new RemoteWebDriver(new URL(remoteWebDriverUrl), capabilities);
}
else
{
return new SafariDriver();
}
}
// safari
public WebDriver getIPadDriver() throws MalformedURLException
{
if (useRemoteWebDriver)
{
DesiredCapabilities capabilities = DesiredCapabilities.ipad();
capabilities.setCapability(PLATFORM, applyPlatform());
capabilities.setCapability(VERSION, applyVersion());
return new RemoteWebDriver(new URL(remoteWebDriverUrl), capabilities);
}
else
{
return new SafariDriver();
}
}
public JavascriptExecutor getJQueryDriver(WebDriver webDriver)
{
return (JavascriptExecutor) webDriver;
}
public static void loadJQuery(JavascriptExecutor jsDriver)
{
Object jquery = jsDriver.executeScript(" if ( typeof $ != 'undefined') { return 1;} else { return null; }");
if (jquery == null)
{
URL jqueryUrl = Resources.getResource("jquery-1.8.0.min.js");
String jqueryText = "";
try
{
jqueryText = Resources.toString(jqueryUrl, Charsets.UTF_8);
}
catch (IOException e)
{
LOG.warn("Error obtaining jquery library.", e);
}
LOG.info("\tEnable Jquery");
jsDriver.executeScript(jqueryText);
}
}
public String getCustomJS(String jsName)
{
URL jsUrl = Resources.getResource(jsName);
String jsText = "";
try
{
jsText = Resources.toString(jsUrl, Charsets.UTF_8);
}
catch (IOException e)
{
LOG.info("Error in javascript!", e);
}
return jsText;
}
private String applyPlatform()
{
if ((platform == null) || platform.trim().isEmpty())
{
return settings.getProperty(PLATFORM);
}
else
{
return platform;
}
}
private String applyVersion()
{
if ((browserVersion == null) || browserVersion.trim().isEmpty())
{
return settings.getProperty(BROWSER_VERSION);
}
else
{
return browserVersion;
}
}
public String getConfigurationsPath()
{
return configurationsPath;
}
public String getLogsPath()
{
return reportsPath;
}
public void setLogsPath(String logsPath)
{
reportsPath = logsPath;
}
public static String getRemoteWebDriverUrl()
{
return remoteWebDriverUrl;
}
public boolean isUseRemoteWebDriver()
{
return useRemoteWebDriver;
}
public void setRemoteWebDriverUrl(String remoteWebDriverUrl)
{
this.remoteWebDriverUrl = remoteWebDriverUrl;
}
public String getProjectName()
{
return projectName;
}
public void setProjectName(String projectName)
{
this.projectName = projectName;
}
public static int getPageLoadTimeout()
{
return pageLoadTimeout;
}
public static void setPageLoadTimeout(int pageLoadTimeout)
{
WebUIManager.pageLoadTimeout = pageLoadTimeout;
}
public static int getActionPace()
{
return actionPace;
}
public static void setActionPace(int actionPace)
{
WebUIManager.actionPace = actionPace;
}
public static String getFeatureNames()
{
return featureNames;
}
public static void setFeatureNames(String featureNames)
{
WebUIManager.featureNames = featureNames;
}
public void setUseRemoteWebDriver(boolean useRemoteWebDriver)
{
this.useRemoteWebDriver = useRemoteWebDriver;
}
public Browsers getBrowser()
{
return browser;
}
public void setBrowser(Browsers browser, boolean overwriteSystem)
{
// if system property is set, use that instead
if (overwriteSystem)
{
this.browser = browser;
}
else
{
this.browser = getBrowserFromSystemProperty();
}
}
public String getBrowserVersion()
{
return browserVersion;
}
public void setBrowserVersion(String browserVersion)
{
this.browserVersion = browserVersion;
}
public String getPlatform()
{
return platform;
}
public void setPlatform(String platform)
{
this.platform = platform;
}
public String getCustomClasspath()
{
return customClasspath;
}
public void setCustomClasspath(String customClasspath)
{
this.customClasspath = customClasspath;
}
public Map<String, Page> getPages()
{
return pages;
}
}