All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.automationrockstars.design.gir.webdriver.DriverFactory Maven / Gradle / Ivy

/*******************************************************************************
 * Copyright (c) 2015, 2016 Automation RockStars Ltd.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Apache License v2.0
 * which accompanies this distribution, and is available at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Contributors:
 *     Automation RockStars - initial API and implementation
 *******************************************************************************/
package com.automationrockstars.design.gir.webdriver;
import static com.automationrockstars.design.gir.webdriver.plugin.UiObjectFindPluginService.findPlugins;

import java.awt.Desktop;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;

import org.apache.commons.collections.list.UnmodifiableList;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.openqa.selenium.By;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.HasCapabilities;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.Platform;
import org.openqa.selenium.Proxy;
import org.openqa.selenium.SearchContext;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxProfile;
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
import org.openqa.selenium.ie.InternetExplorerDriver;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.internal.WrapsDriver;
import org.openqa.selenium.remote.BrowserType;
import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.automationrockstars.base.ConfigLoader;
import com.automationrockstars.bmo.AllureStoryReporter;
import com.automationrockstars.bmo.GenericAllureStoryReporter;
import com.automationrockstars.design.gir.webdriver.plugin.UiDriverPluginService;
import com.automationrockstars.design.gir.webdriver.plugin.UiObjectActionPluginService;
import com.automationrockstars.design.gir.webdriver.plugin.UiObjectFindPluginService;
import com.automationrockstars.design.gir.webdriver.plugin.UiObjectInfoPluginService;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.PeekingIterator;




public class DriverFactory {

	private static Capabilities capabilities;

	public static final String WEBDRIVER_SESSION = "webdriver.session"; 
	private static final ThreadLocal instances = new InheritableThreadLocal<>();
	private static final List activeDrivers = Lists.newCopyOnWriteArrayList();

	private static final WebDriver instance(){
		return instances.get();
	};
	private static final Logger log = LoggerFactory.getLogger(DriverFactory.class);

	private static final ThreadLocal url = new ThreadLocal<>();

	public static void url(String url){
		DriverFactory.url.set(url);
	}

	public static String url(){
		return url.get();
	}

	public static final WebDriver getUnwrappedDriver(){
		WebDriver toUnwrap = null;
		if (isWds()){
			toUnwrap = wdsInstance();
		} else {
			if (instance() == null){
				getDriver();
			}
			toUnwrap = instance();
		}
		return unwrap(toUnwrap);
	}

	public static WebDriver unwrap(WebDriver wrapped){
		if (WrapsDriver.class.isAssignableFrom(wrapped.getClass())){
			return unwrap(((WrapsDriver)wrapped).getWrappedDriver());
		} else return wrapped;
	}
	public static boolean canScreenshot(){
		return instance() != null || providers.get() != null;
	}
	public static byte[] getScreenshot(){
		return ((RemoteWebDriver)getUnwrappedDriver()).getScreenshotAs(OutputType.BYTES);
	}
	public static String getScreenshotAsBase64(){
		return ((RemoteWebDriver)getUnwrappedDriver()).getScreenshotAs(OutputType.BASE64);
	}

	public static void displayScreenshotFile(){
		try {
			File screenshot = getScreenshotFile();
			if (FileUtils.waitFor(screenshot, 1)){
				log.info("Screenshot file available {}",screenshot.getAbsolutePath());
				Desktop.getDesktop().open(screenshot);
			} else {
				log.warn("File {} not created",screenshot);
			}
		} catch (IOException e) {
			log.error("Cannot display screenshot due to",e);
		}
	}

	private static WebDriver DISPLAY_BROWSER = null;

	public static void displayScreenshotBrowser(){
		if (DISPLAY_BROWSER == null){
			DISPLAY_BROWSER = new ChromeDriver();
		}
		if (DISPLAY_BROWSER != null){
			DISPLAY_BROWSER.get("data:image/gif;base64,"+getScreenshotAsBase64());
		}
	}

	public static File getScreenshotFile(){
		return ((RemoteWebDriver)getUnwrappedDriver()).getScreenshotAs(OutputType.FILE);
	}
	public static void setCapabilities(Capabilities capabilities){
		DriverFactory.capabilities = capabilities;
	}

	public static Actions actions(){
		return new Actions(getUnwrappedDriver());
	}
	private static final String GRID_URL = ConfigLoader.config().getString("grid.url", null);
	private static final boolean isGridAvailable(String gridUrl){
		boolean result = true;	

		CloseableHttpResponse gridResponse;
		try {
			CloseableHttpClient cl = HttpClients.createDefault();
			gridResponse = cl.execute(new HttpGet(new URI(gridUrl)));

			if (gridResponse.getStatusLine().getStatusCode() == 404){
				log.warn("Response from contacting grid {}",IOUtils.toString(gridResponse.getEntity().getContent()));
				result = false;
			}
			gridResponse.close();
			cl.close(); 
		} catch (Throwable e) {
			result = false;
			log.error("Selenium grid not available due to {}",e.getMessage());
		}

		return result;
	}
	private static boolean checkGridExtras(String gridUrl, RemoteWebDriver driver){
		String gridExtras = GridUtils.getNodeExtras(gridUrl, driver);
		if (gridExtras == null){
			log.info("No grid extras foud");
			return false;
		} else {
			log.info("Grid extras available at {}",gridExtras);
			return true;
		}

	}

	private static final ThreadLocal browser = new InheritableThreadLocal(){
		protected String initialValue(){
			String browser = DEF_BROWSER;
			if (! isWds()){
				if (browserQueue().hasNext()){
					browser = browserQueue().next(); 
				} else {
					if (ConfigLoader.config().containsKey("webdriver.browser")){
						browser = ConfigLoader.config().getString("webdriver.browser"); 
					}
				}
			}
			return browser;	
		}


	};
	private static final String MATRIX_PROP = "webdriver.matrix.browsers";
	private static final String BROWSER_PROP = "webdriver.browser";
	private static final String DEF_BROWSER_PROP = "webdriver.browser.default";
	private static final String DEF_BROWSER = "phantomjs";
	private static PeekingIterator matrix = null;
	private static boolean pluginInitialized = false;
	private static synchronized Iterator browserQueue(){		
		while (matrix == null || ! matrix.hasNext() || matrix.peek() == null ){
			matrix = browserMatrix();
		}
		if (! pluginInitialized){
			UiObjectFindPluginService.findPlugins();
			UiObjectActionPluginService.actionPlugins();
			UiObjectInfoPluginService.infoPlugins();
			UiDriverPluginService.driverPlugins();
			pluginInitialized = true;
		}

		return matrix;
	}


	public static synchronized PeekingIterator browserMatrix(){
		PeekingIterator result ;
		if (ConfigLoader.config().containsKey(MATRIX_PROP)){
			result = Iterators.peekingIterator(Iterators.forArray(ConfigLoader.config().getStringArray(MATRIX_PROP)));
		} else {
			String bName = ConfigLoader.config().getString(BROWSER_PROP,ConfigLoader.config().getString(DEF_BROWSER_PROP,DEF_BROWSER));
			if (bName.equalsIgnoreCase("ie")){
				bName = BrowserType.IE;
				log.info("Using {} for browser",bName);
			}
			result = Iterators.peekingIterator(Iterators.cycle(bName));
		}
		return result;
	}
	public static void setBrowser(String browser){
		DriverFactory.browser.set(browser);
	}
	
	private static synchronized void enableGridExtras(RemoteWebDriver driver){
		if (checkGridExtras(GRID_URL,driver)){
			String videoLink = String.format("%s/download_video/%s.mp4", GridUtils.getNodeExtras(GRID_URL, driver),driver.getSessionId().toString());
			ConfigLoader.config().addProperty("webdriver.video", videoLink);
			List videos = ConfigLoader.config().getList("webdriver.videos", new ArrayList>());
			String story = null;
			try {
				if (! Strings.isNullOrEmpty(GenericAllureStoryReporter.storyName())){
					story = GenericAllureStoryReporter.storyName();
				} else if (! Strings.isNullOrEmpty(AllureStoryReporter.storyName())){
					story = AllureStoryReporter.storyName();
				} 
			} catch (Throwable t){

			}
			if (Strings.isNullOrEmpty(story)){
				story = "recording " + driver.getSessionId();
			}

			String title = "video_" + story;
			int videoRepetition = 0;
			String titlePostfix = "";
			Optional titleAlreadyThere = Optional.absent();
			do {
				if (videoRepetition > 0){
					titlePostfix = String.format("_%s", videoRepetition);
				}
				final String titleBase = title + titlePostfix;
				titleAlreadyThere = FluentIterable.from(videos).firstMatch(new Predicate() {

					@SuppressWarnings("unchecked")
					@Override
					public boolean apply(Object input) {
						return ((Map) input).keySet().contains(titleBase);
					}
				});
				if (! titleAlreadyThere.isPresent()){
					title = titleBase;
				} else {
					videoRepetition++;
				}
			} while (titleAlreadyThere.isPresent());
			log.info("Session for story {}",title);
			videos.add(Collections.singletonMap(title, videoLink));
			ConfigLoader.config().setProperty("webdriver.videos", videos);
		}
	}
	private static WebDriver createRemoteDriver(){
		if (ConfigLoader.config().containsKey("noui")){
			return new HtmlUnitDriver(true);
		}
		log.info("Creating browser for {}",browser.get());
		try {
			String name = browser.get().toLowerCase();
			if(name.equalsIgnoreCase("ie") || name.equals("internet_explorer") || name.equals("internet explorer")){
				name= BrowserType.IE;
			}
			if (Strings.isNullOrEmpty(GRID_URL) || ! isGridAvailable(GRID_URL)){			
				log.info("Grid not detected");
				if(name.equals(BrowserType.IE)){				
					return new InternetExplorerDriver(getCapabilities(BrowserType.IE));
				} 					
				String browserName = StringUtils.capitalize(name + "Driver");
				WebDriver res = null;
				try {
					res = (WebDriver) Class.forName("org.openqa.selenium." + name+"."+browserName).getConstructor(Capabilities.class).newInstance(getCapabilities(name));
				} catch (Exception e) {
					throw new IllegalStateException(String.format("Browser for name %s failed to start", name),e);
				}
				return res;
			} else {
				log.info("Using Selenium grid at {}",GRID_URL);
				URL gridUrl = new URL(GRID_URL);
				RemoteWebDriver result = new RemoteWebDriver(gridUrl,getCapabilities(name));
				log.info("Driver instance created {}",result);
				log.debug("Test session id {}",result.getSessionId());
				log.info("Executing on node {}",GridUtils.getNode(GRID_URL, result));
				enableGridExtras(result);
				return result;
			}
		} catch (MalformedURLException e) {
			throw new RuntimeException("Cannot connect to grid", e);
		}
	}

	private static synchronized void closeDriver(WebDriver driver){
		UiDriverPluginService.driverPlugins().beforeCloseDriver(driver);
		if (driver != null){
			try {
				driver.close();
				if (! dontUseQuit()){
					driver.quit();
				}
			} catch (Throwable ignore){
			}
			activeDrivers.remove(driver);
		}
		UiDriverPluginService.driverPlugins().afterCloseDriver();
	}
	public static void closeDriver(){
		closeDriver(instance());
		instances.remove();
	}

	public static List activeSessions(){
		return Lists.newArrayList(Iterables.transform(activeInstances(),new Function() {

			@Override
			public String apply(WebDriver input) {
				return ((RemoteWebDriver)unwrap(input)).getSessionId().toString();
			}
		}));
	}

	public static boolean isSessionOpened(final String session){
		return Iterables.tryFind(activeInstances(), new Predicate() {

			@Override
			public boolean apply(WebDriver input) {
				return ((RemoteWebDriver)unwrap(input)).getSessionId().toString().equals(session);
			}
		}).isPresent();
	}

	@SuppressWarnings("unchecked")
	public static List activeInstances(){
		return UnmodifiableList.decorate(activeDrivers);
	}

	public static void closeSession(final String session){
		Optional driver = Iterables.tryFind(activeInstances(), new Predicate() {

			@Override
			public boolean apply(WebDriver input) {
				return ((RemoteWebDriver)unwrap(input)).getSessionId().toString().equals(session);
			}
		});
		if (driver.isPresent()){
			closeDriver(driver.get());
		}
	}
	public static void destroy(){
		closeDriver();
		matrix = null;
		browser.remove();
	}

	private static final boolean dontUseQuit(){
		return ConfigLoader.config().getBoolean("webdriver.close.only",false);
	}
	public static final String DEFAULT_AUTO_DOWNLOAD = "application/octet-stream,application/x-gzip,application/gzip,application/zip,application/pdf,application/vnd.cups-pdf";
	private static String autoDownloadFiles(){
		return ConfigLoader.config().getString("webdriver.autodownload.files",DEFAULT_AUTO_DOWNLOAD);
	}
	private static Capabilities getCapabilities( String driverType){
		DesiredCapabilities result = new DesiredCapabilities();

		switch (driverType) {
		case BrowserType.FIREFOX:
			result = DesiredCapabilities.firefox();
			FirefoxProfile profile = (FirefoxProfile) ConfigLoader.config().getProperty("firefox.profile");
			if (profile == null){
				profile = new FirefoxProfile();
				if (ConfigLoader.config().getBoolean("webdriver.accept.java",true)){
					profile.setPreference("plugin.state.java", 2);
				}
				if (ConfigLoader.config().getBoolean("webdriver.accept.ssl",true)){
					profile.setAcceptUntrustedCertificates(true);
					profile.setAssumeUntrustedCertificateIssuer(true);
				}
				if (ConfigLoader.config().getBoolean("webdriver.autodownload",true)){
					profile.setPreference("browser.download.folderList",2);
					profile.setPreference("browser.helperApps.alwaysAsk.force", false);
					profile.setPreference("browser.download.manager.showWhenStarting",false);
					profile.setPreference("browser.helperApps.neverAsk.saveToDisk",autoDownloadFiles());
				}
			} 
			result.setCapability(FirefoxDriver.PROFILE, profile);
			result.setCapability(CapabilityType.ACCEPT_SSL_CERTS, true);
			break;
		case BrowserType.IE:
			result = DesiredCapabilities.internetExplorer();
			result.setCapability("ignoreProtectedModeSettings", true);
			result.setCapability(CapabilityType.ACCEPT_SSL_CERTS, true);
			result.setCapability("ie.validateCookieDocumentType", false);
			break;
		case BrowserType.CHROME:
			result = DesiredCapabilities.chrome();
			ChromeOptions chromOptions = new ChromeOptions();
			//			chromOptions.setExperimentalOption("excludeSwitches",Lists.newArrayList("ignore-certificate-errors"));
			chromOptions.addArguments("chrome.switches","--disable-extensions");
			result.setCapability(ChromeOptions.CAPABILITY, chromOptions);
			result.setCapability(CapabilityType.ACCEPT_SSL_CERTS, true);
			break;
		default:
			result = new DesiredCapabilities(driverType,"",Platform.ANY);
			break;
		} 
		String proxy= ConfigLoader.config().getString("webdriver.proxy"); 
		if (! Strings.isNullOrEmpty(proxy)){
			Proxy p = new Proxy();
			p.setHttpProxy(proxy)
			.setSslProxy(proxy);
			result.setCapability(CapabilityType.PROXY, p);
			log.info("Using proxy {}",proxy);
		}
		return concatenate(result,capabilities);
	}

	private static Capabilities concatenate(Capabilities... toJoin){
		return new DesiredCapabilities(toJoin);
	}

	private static Thread createDriverCloser(final WebDriver driver){
		return new Thread(new Runnable() {

			@Override
			public void run() {
				closeDriver(driver);
			}
		});
	}
	public static boolean isWrong(WebDriver driver){
		try {
			driver.getCurrentUrl();
			return false;	
		} catch (Exception e){
			return true;
		}
	}
	public static interface WdsProvider {
		WebDriver wds();
	}
	private static ThreadLocal providers = new ThreadLocal<>();

	public static final void setWds(WdsProvider provider){
		UiDriverPluginService.driverPlugins().beforeInstantiateDriver();
		providers.set(provider);
		UiDriverPluginService.driverPlugins().afterInstantiateDriver(provider.wds());
	}

	public static final ConcurrentMap wdsInstances = Maps.newConcurrentMap();

	private static boolean isWds(){
		return providers.get() != null;
	}
	private static WebDriver wdsInstance(){
		WebDriver wds = providers.get().wds();
		UiDriverPluginService.driverPlugins().beforeGetDriver();
		if (wdsInstances.get(wds) == null){
			wdsInstances.put(wds, new VisibleElementFilter(providers.get().wds()));
		}
		UiDriverPluginService.driverPlugins().afterGetDriver(wdsInstances.get(wds));
		return wdsInstances.get(wds);		
	}
	public static final WebDriver getDriver(){
		UiDriverPluginService.driverPlugins().beforeGetDriver();
		if (isWds()) return wdsInstance();
		if (instance() == null || isWrong(instance())){
			UiDriverPluginService.driverPlugins().beforeInstantiateDriver();
			WebDriver dr = createRemoteDriver();
			if (wrap(dr)){
				dr =new VisibleElementFilter(dr);
			}
			instances.set(dr);
			activeDrivers.add(dr);
			Runtime.getRuntime().addShutdownHook(createDriverCloser(instance()));
			UiDriverPluginService.driverPlugins().afterInstantiateDriver(dr);

		}
		UiDriverPluginService.driverPlugins().afterGetDriver(instance());
		return instance();
	}

	private static boolean wrap(WebDriver driver) {
		if (HasCapabilities.class.isAssignableFrom(driver.getClass())){
		return ! ((HasCapabilities)driver).getCapabilities().is("cannot_wrap");
		} else return true;
		
	}

	private static boolean hasCapabilities(WebDriver driver, Capabilities capabilities){
		return ((RemoteWebDriver)unwrap(driver)).getCapabilities().equals(capabilities);
	}
	public static final WebDriver getDriver(Capabilities desiredCapabilities){
		if (instance() == null || ! hasCapabilities(instance(), desiredCapabilities)){
			setCapabilities(desiredCapabilities);
			instances.set(new VisibleElementFilter(createRemoteDriver()));
		}
		return instance();
	}

	public static final WebDriverWait delay(long timeInSeconds){
		return new WebDriverWait(getDriver(), timeInSeconds);
	}
	public static final WebDriverWait delay(){
		return delay(ConfigLoader.config().getLong("webdriver.timeouts.implicitlywait",5));

	}



	public static class VisibleElementFilter implements HasCapabilities, JavascriptExecutor, WebDriver, WrapsDriver {

		private final WebDriver driver;
		private final SearchContext searchContext;

		public VisibleElementFilter(WebDriver driver){
			this.driver = driver;
			searchContext = new FilterableSearchContext(driver);
		}
		public void get(String url) {
			driver.get(url);

		}

		public String toString(){
			return String.format("VisibleElementFilter of %s", driver);
		}
		public String getCurrentUrl() {
			return driver.getCurrentUrl();
		}

		public String getTitle() {
			return driver.getTitle();
		}

		private static final UiObject browser = new UiObject(null,By.tagName("body"),"WebDriver");
		public List findElements(By by) {
			findPlugins().beforeFindElement(browser,by);
			List result = UiObject.wrapAll(searchContext.findElements(by), by);
			findPlugins().afterFindElements(browser,by,result);
			return result;
		}

		public WebElement findElement(By by) {
			findPlugins().beforeFindElement(browser,by);
			WebElement result = UiObject.wrap(searchContext.findElement(by),by);
			findPlugins().afterFindElement(browser,by,result);
			return result;
		}

		public String getPageSource() {
			return driver.getPageSource();
		}

		public void close() {
			driver.close();

		}

		public void quit() {
			driver.quit();			
		}

		public Set getWindowHandles() {
			return driver.getWindowHandles();
		}

		public String getWindowHandle() {
			return driver.getWindowHandle();
		}

		public TargetLocator switchTo() {
			return driver.switchTo();
		}

		public Navigation navigate() {
			return driver.navigate();
		}

		public Options manage() {
			return driver.manage();
		}

		public WebDriver getWrappedDriver(){
			return driver;
		}
		@Override
		public Object executeScript(String script, Object... args) {
			return ((JavascriptExecutor)driver).executeScript(script,args);
		}
		@Override
		public Object executeAsyncScript(String script, Object... args) {
			return ((JavascriptExecutor)driver).executeAsyncScript(script, args);
		}
		@Override
		public Capabilities getCapabilities() {
			return ((HasCapabilities) driver).getCapabilities();
		}
	}

	public static String browserName(){
		return ((RemoteWebDriver)getUnwrappedDriver()).getCapabilities().getBrowserName();
	}

	public static boolean isIe(){
		return browserName().equals(BrowserType.IE);
	}

	public static boolean isPhantom() {
		return browserName().equals(BrowserType.PHANTOMJS);
	}

}