package org.syftkog.web.test.framework; import com.saucelabs.common.SauceOnDemandSessionIdProvider; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Set; import java.util.Stack; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.apache.commons.io.FileUtils; import org.openqa.selenium.By; import org.openqa.selenium.Capabilities; import org.openqa.selenium.Cookie; import org.openqa.selenium.Dimension; import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.OutputType; import org.openqa.selenium.SearchContext; import org.openqa.selenium.TakesScreenshot; import org.openqa.selenium.UnsupportedCommandException; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriverException; import org.openqa.selenium.WebElement; import org.openqa.selenium.interactions.HasInputDevices; import org.openqa.selenium.interactions.Keyboard; import org.openqa.selenium.interactions.Mouse; import org.openqa.selenium.internal.WrapsDriver; import org.openqa.selenium.remote.RemoteWebDriver; import org.openqa.selenium.support.ui.WebDriverWait; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.Assert; import org.testng.ITestResult; /** * This class used for driving a web browser. It extends the WebDriver to facilitate easier logging and context aware actions. * @author BenjaminLimb */ public class Driver implements WebDriver, JavascriptExecutor, WrapsDriver, HasInputDevices, TakesScreenshot, HasDriver, HasStepLogger, HasSearchContext, SauceOnDemandSessionIdProvider { /** * */ public final Logger LOG = LoggerFactory.getLogger(Driver.class); private final ExecutorService executorService; private WebDriver driver; private final Capabilities driverCapabilities; private final Stack<Page> pageHistory; private final Stack<String> urlHistory; // Used for detecting page changes. Never removed from. private final HashMap<String, Object> properties; private Environment environment; private StepLogger stepLogger; private DriverFactory factory; private ITestResult testResult; //Lifecycle controls private long driverIdleTimeoutMilliseconds = Long.parseLong(PropertiesRetriever.getString("driver.idleTimeoutMilliseconds", "10")) * 1000; private Boolean keepAlive = true; private Boolean inUse = true; private Boolean dirty = false; private long lastInUse = System.currentTimeMillis(); //END lifecycle control private long implicitWaitTimeInSeconds = Long.parseLong(PropertiesRetriever.getString("driver.implicitWaitTimeInSeconds", "10")); /** * * @param driver * @param driverCapabilities * @param executorService * @param factory */ public Driver(WebDriver driver, Capabilities driverCapabilities, ExecutorService executorService, DriverFactory factory) { this.urlHistory = new Stack<>(); this.pageHistory = new Stack<>(); this.driver = driver; this.stepLogger = new StepLogger(); this.driverCapabilities = driverCapabilities; this.executorService = GeneralUtils.replaceNull(executorService, Executors.newSingleThreadExecutor()); this.executorService.execute(new KeepAliveMonitor()); this.factory = factory; this.properties = new HashMap<>(); } /** * * @param driver * @param driverCapabilities */ public Driver(WebDriver driver, Capabilities driverCapabilities) { this(driver, driverCapabilities, null, null); } /** * * @param driver * @param driverCapabilities * @param executorService */ public Driver(WebDriver driver, Capabilities driverCapabilities, ExecutorService executorService) { this(driver, driverCapabilities, executorService, null); } /** * * @return */ @Override public WebDriver getWrappedDriver() { return driver; } /** * * @param text */ public void logStep(String text) { stepLogger.log(text); } /** * * @return */ public Capabilities getCapabilities() { return driverCapabilities; } /** * * @return */ public Page getPreviousPage() { return pageHistory.lastElement(); } /** * * @param page * @return */ public Driver addPageToDriverHistory(Page page) { pageHistory.push(page); return this; } /** * * @param url */ @Override public void get(String url) { urlHistory.add(url); driver.get(url); } /** * * @return */ public Page navigateBack() { logStep("Navigate BACK using browser."); driver.navigate().back(); if (!pageHistory.empty()) { pageHistory.pop(); if (!pageHistory.empty()) { Page lastPage = pageHistory.peek(); return lastPage; } else { LOG.debug("Cannot return previous page if it is not already stored in the driver. Did you click and navigate to a new page without pushing it to the page history?"); return null; } } return null; } /** * * @return */ @Override public String getCurrentUrl() { return driver.getCurrentUrl(); } /** * * @return */ public Driver prepareToNavigateToNewUrl() { urlHistory.push(getCurrentUrl()); return this; } /** * * @return */ public Driver waitForNavigateToNewUrl() { return waitUntilUrlChange(urlHistory.peek(), implicitWaitTimeInSeconds); } /** * * @return */ @Override public String getTitle() { return driver.getTitle(); } /** * * @param by * @return */ @Override public List<WebElement> findElements(By by) { return driver.findElements(by); } /** * * @param by * @return */ @Override public WebElement findElement(By by) { return driver.findElement(by); } /** * * @return */ @Override public String getPageSource() { return driver.getPageSource(); } private synchronized void terminate() { Assert.assertTrue(inUse == false); if (driver != null) { try { driver.quit(); if (factory != null) { factory.reportThatDriverQuitSuccessfully(this); } driver = null; // set the driver null so we don't try to use it anymore. } catch (UnsupportedCommandException ex) { LOG.error(ex.toString()); } } } // Closes a single browser window. /** * */ @Override public void close() { driver.close(); } // Immediately quit the driver; /** * */ @Override public void quit() { inUse = false; keepAlive = false; terminate(); } /** * */ public void recycle() { Assert.assertNotNull(factory, "Recycle should only be used if the driver factory is set!"); inUse = false; if (factory != null) { factory.returnDriver(this); } else { keepAlive = false; } } /** * * @return */ public Driver setInUse() { Assert.assertFalse(inUse, "A driver shouldn't be set in use twice without being recycled first."); inUse = true; return this; } /** * * @return */ @Override public Set<String> getWindowHandles() { return driver.getWindowHandles(); } /** * * @return */ @Override public String getWindowHandle() { return driver.getWindowHandle(); } /** * * @return */ @Override public WebDriver.TargetLocator switchTo() { return driver.switchTo(); } /** * * @return */ @Override public WebDriver.Navigation navigate() { return driver.navigate(); } /** * * @return */ @Override public WebDriver.Options manage() { return driver.manage(); } /** * * @param script * @param args * @return */ @Override public Object executeScript(String script, Object... args) { return ((JavascriptExecutor) driver).executeScript(script, args); } /** * * @param script * @param args * @return */ @Override public Object executeAsyncScript(String script, Object... args) { return ((JavascriptExecutor) driver).executeAsyncScript(script, args); } /** * * @return */ @Override public Keyboard getKeyboard() { return ((HasInputDevices) driver).getKeyboard(); } /** * * @return */ @Override public Mouse getMouse() { return ((HasInputDevices) driver).getMouse(); } /** * * @param oldUrl * @param timeoutInSeconds * @return */ public Driver waitUntilUrlChange(String oldUrl, long timeoutInSeconds) { WebDriverWait wait = new WebDriverWait(this, timeoutInSeconds); wait.until(ExpectedConditionsAdditional.urlChange(oldUrl)); return this; } /** * * @return */ public long getImplicitWaitTimeInSeconds() { return implicitWaitTimeInSeconds; } /** * * @param implicitWaitTimeInSeconds */ public void setImplicitWaitTimeInSeconds(long implicitWaitTimeInSeconds) { this.implicitWaitTimeInSeconds = implicitWaitTimeInSeconds; } /** * * @return */ public Boolean isAlive() { return keepAlive; } /** * * @return */ public Boolean isInUse() { return inUse; } /** * * @return */ public long getDriverIdleTimeoutMilliseconds() { return driverIdleTimeoutMilliseconds; } /** * * @param driverIdleTimeoutMilliseconds */ public void setDriverIdleTimeoutMilliseconds(long driverIdleTimeoutMilliseconds) { this.driverIdleTimeoutMilliseconds = driverIdleTimeoutMilliseconds; } /** * * @param <X> * @param ot * @return * @throws WebDriverException */ @Override public <X> X getScreenshotAs(OutputType<X> ot) throws WebDriverException { return (X) driver; } // Allow the driver to be reused. /** * * @return */ public Driver clean() { dirty = false; return this; } /** * * @return */ public Driver dirty() { dirty = true; return this; } /** * * @param hasContext * @return */ public Driver applyTestCaseContext(WrapsTestCaseContext hasContext) { TestCaseContext testCaseContext = hasContext.getWrappedTestCastContext(); stepLogger = testCaseContext.getStepLogger(); environment = testCaseContext.parameters().getEnvironment(); Dimension windowSize = testCaseContext.parameters().getWindowSize(); if (windowSize != null) { manage().window().setSize(windowSize); } return this; } /** * * @return */ @Override public Driver getDriver() { return this; } /** * * @return */ @Override public SearchContext getSearchContext() { return this; } /** * * @return */ @Override public StepLogger getStepLogger() { return stepLogger; } /** * * @param stepLogger */ public void setStepLogger(StepLogger stepLogger) { this.stepLogger = stepLogger; } /** * * @return */ @Override public String getSessionId() { if (driver instanceof RemoteWebDriver) { return ((RemoteWebDriver) driver).getSessionId().toString(); } return null; } private class KeepAliveMonitor implements Runnable { @Override public void run() { while (keepAlive) { long elapsedMilli = System.currentTimeMillis() - lastInUse; if (inUse) { lastInUse = System.currentTimeMillis(); } else if (dirty) { LOG.debug("DRIVER IS DIRTY SO IT WILL NOT BE REUSED."); keepAlive = false; } else if (elapsedMilli > getDriverIdleTimeoutMilliseconds()) { if (factory != null && !factory.tryRemoveAvailableDriver(Driver.this)) { continue; } LOG.debug("DRIVER TIMEOUT REACHED: " + driverIdleTimeoutMilliseconds / 1000.0 + " seconds " + driver.toString()); keepAlive = false; break; } try { Thread.sleep(1000); } catch (InterruptedException ex) { LOG.error("InterruptedException", ex); } } terminate(); } }; /** * * @param miliseconds */ public static void sleepForMilliseconds(long miliseconds) { try { Thread.sleep(miliseconds); } catch (InterruptedException ex) { throw new RuntimeException(ex); } } /** * * @param miliseconds */ public static void sleepForMilliseconds(int miliseconds) { try { Thread.sleep(miliseconds); } catch (InterruptedException ex) { throw new RuntimeException(ex); } } /** * * @return */ public ITestResult getTestResult() { return testResult; } /** * * @param testResult */ public void setTestResult(ITestResult testResult) { this.testResult = testResult; } /** * * @param environment */ public void goToEnvironment(Environment environment) { if(environment == null){ throw new RuntimeException("Environment must not be null."); } if (!driver.getCurrentUrl().contains(environment.getHost())) { driver.navigate().to(environment.getURL()); } } /** * * @param c */ public void setCookie(Cookie c) { dirty();// If we do any cookie manipulation then we are going to prevent the driver from reuse unless it's cleaned up. driver.manage().addCookie(c); Cookie cookieToRead = driver.manage().getCookieNamed(c.getName()); Assert.assertNotNull(cookieToRead); Assert.assertTrue(cookieToRead.getValue().equalsIgnoreCase(c.getValue())); } /** * * @param name */ public void deleteCookie(String name) { driver.manage().deleteCookieNamed(name); driver.navigate().refresh(); } /** * * @return */ public Environment getEnvironment() { return environment; } /** * * @param environment */ public void setEnvironment(Environment environment) { this.environment = environment; } public void takeScreenshot( String filename){ // driver.manage().window().setSize( new Dimension( 1024, 2048) ); // Set window size of image capture, otherwise will likely be small (Need to figure out how to capture entire window at specified width) File scrShot = ((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE); try{ FileUtils.copyFile(scrShot, new File("./target/"+filename+".png")); //Not sure if PNG is always the output type or how to change it if it isn't }catch(IOException e){ e.printStackTrace(); } } }