/*
* Tanaguru - Automated webpage assessment
* Copyright (C) 2008-2015 Tanaguru.org
*
* This file is part of Tanaguru.
*
* Tanaguru is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Contact us by mail: tanaguru AT tanaguru DOT org
*/
package org.tanaguru.sebuilder.interpreter;
import com.sebuilder.interpreter.Script;
import com.sebuilder.interpreter.TestRun;
import com.sebuilder.interpreter.Verify;
import com.sebuilder.interpreter.webdriverfactory.WebDriverFactory;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.*;
import javax.imageio.ImageIO;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.log4j.Logger;
import org.openqa.selenium.*;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.springframework.cache.annotation.Cacheable;
import org.tanaguru.sebuilder.interpreter.exception.TestRunException;
import org.tanaguru.sebuilder.tools.FirefoxDriverObjectPool;
/**
* A single run of a test getScript().
*
* @author jkowalczyk
*/
public class TgTestRun extends TestRun {
final static Logger LOGGER = Logger.getLogger(TgTestRun.class);
private boolean isStepOpenNewPage = false;
private Map<String, String> jsScriptMap;
public Map<String, String> getJsScriptMap() {
return jsScriptMap;
}
public void setJsScriptMap(Map<String, String> jsScriptMap) {
this.jsScriptMap = jsScriptMap;
}
private Collection<NewPageListener> newPageListeners;
public void addNewPageListener(NewPageListener newPageListener) {
if (newPageListeners == null) {
newPageListeners = new ArrayList<>();
}
this.newPageListeners.add(newPageListener);
}
public void addNewPageListeners(Collection<NewPageListener> newPageListeners) {
if (this.newPageListeners == null) {
this.newPageListeners = new ArrayList<>();
}
this.newPageListeners.addAll(newPageListeners);
}
public void removeNewPageListener(NewPageListener newPageListener) {
if (newPageListeners != null) {
this.newPageListeners.remove(newPageListener);
}
}
public void removeNewPageListeners(Collection<NewPageListener> newPageListeners) {
if (newPageListeners != null) {
this.newPageListeners.removeAll(newPageListeners);
}
}
private RemoteWebDriver rwd;
@Override
public RemoteWebDriver getDriver() {
if (firefoxDriverObjectPool != null) {
return rwd;
} else {
// keep the ability not to use an object pool
return super.getDriver();
}
}
private FirefoxDriverObjectPool firefoxDriverObjectPool;
public void setFirefoxDriverObjectPool(FirefoxDriverObjectPool firefoxDriverObjectPool) {
this.firefoxDriverObjectPool = firefoxDriverObjectPool;
}
/**
* Constructor
* @param script
* @param implicitelyWaitDriverTimeout
* @param pageLoadDriverTimeout
*/
public TgTestRun(Script script, int implicitelyWaitDriverTimeout, int pageLoadDriverTimeout) {
super(script, implicitelyWaitDriverTimeout, pageLoadDriverTimeout);
}
/**
* Constructor
* @param script
* @param log
* @param webDriverFactory
* @param webDriverConfig
* @param implicitelyWaitDriverTimeout
* @param pageLoadDriverTimeout
*/
public TgTestRun(
Script script,
Log log,
WebDriverFactory webDriverFactory,
HashMap<String, String> webDriverConfig,
int implicitelyWaitDriverTimeout,
int pageLoadDriverTimeout) {
super(script,
log,
webDriverFactory,
webDriverConfig,
implicitelyWaitDriverTimeout,
pageLoadDriverTimeout);
}
/**
* Constructor
* @param script
* @param webDriverFactory
* @param webDriverConfig
* @param implicitelyWaitDriverTimeout
* @param pageLoadDriverTimeout
*/
public TgTestRun(
Script script,
WebDriverFactory webDriverFactory,
HashMap<String, String> webDriverConfig,
int implicitelyWaitDriverTimeout,
int pageLoadDriverTimeout) {
super(script,
webDriverFactory,
webDriverConfig,
implicitelyWaitDriverTimeout,
pageLoadDriverTimeout);
}
/**
* @return True if there is another step to execute.
*/
@Override
public boolean hasNext() {
boolean hasNext = stepIndex < getScript().steps.size() - 1;
if (!hasNext && getDriver() != null) {
properlyCloseWebDriver();
}
return hasNext;
}
/**
* Executes the next step.
*
* @return True on success.
*/
@Override
public boolean next() {
if (stepIndex == -1) {
getLog().debug("Starting test run.");
}
initRemoteWebDriver();
isStepOpenNewPage = false;
getLog().debug("Running step " + (stepIndex+2) + ":"
+ getScript().steps.get(stepIndex+1).type.toString() + " step.");
boolean result = false;
String previousUrl = null;
try {
previousUrl = getDriver().getCurrentUrl();
result = getScript().steps.get(++stepIndex).type.run(this);
// wait a second to make sure the page is fully loaded
Thread.sleep(500);
if (!isStepOpenNewPage && !StringUtils.equals(previousUrl, getDriver().getCurrentUrl())) {
fireNewPage();
}
} catch (TimeoutException te) {
result = true;
if (!isStepOpenNewPage && !StringUtils.equals(previousUrl, getDriver().getCurrentUrl())) {
getLog().debug(" The page " + getDriver().getCurrentUrl() + " is fired as new page but"
+ " is incomplete : " + te.getMessage());
fireNewPage();
}
} catch (UnhandledAlertException uae) {
getLog().warn(uae.getMessage());
properlyCloseWebDriver();
throw new TestRunException(currentStep() + " failed.", uae, currentStep().toString(), stepIndex);
} catch (Exception e) {
getLog().warn(e.getCause());
getLog().warn(e.getMessage());
properlyCloseWebDriver();
throw new TestRunException(currentStep() + " failed.", e, currentStep().toString(), stepIndex);
}
if (!result) {
// If a verify failed, we just note this but continue.
if (currentStep().type instanceof Verify) {
getLog().error(currentStep() + " failed.");
return false;
}
// In all other cases, we throw an exception to stop the run.
RuntimeException e = new TestRunException(currentStep() + " failed.", currentStep().toString(), stepIndex);
e.fillInStackTrace();
getLog().error(e);
properlyCloseWebDriver();
throw e;
} else {
return true;
}
}
/**
*
* @param urlSuffix
*/
public void fireNewPage(String urlSuffix) {
isStepOpenNewPage = true;
if (newPageListeners == null) {
return;
}
getSourceCodeAndFireNewPage(getDriver().getCurrentUrl()+'#'+urlSuffix);
}
/**
*
*/
public void fireNewPage() {
isStepOpenNewPage = true;
if (newPageListeners == null) {
return;
}
getSourceCodeAndFireNewPage(getDriver().getCurrentUrl());
}
/**
*
* @param url
*/
private void getSourceCodeAndFireNewPage(String url) {
try {
try {
Thread.sleep(500);
} catch (InterruptedException ex) {
throw new TestRunException(currentStep() + " failed.", ex, currentStep().toString(), stepIndex);
}
String sourceCode = getDriver().getPageSource();
/* ##############################################################
* ACHTUNG !!!!!!!!!!!!!!!!!!!!!!!!!!
* this sendKeys action is here to grab the focus on the page.
* It is needed later by the js script to execute the focus()
* method on each element. Without it, the focus is kept by the adress
* bar.
*/
WebElement body = getDriver().findElementByTagName("html");
Map<String, String> jsScriptResult = Collections.EMPTY_MAP;
try {
body.sendKeys(Keys.TAB);
jsScriptResult = executeJsScripts();
} catch (WebDriverException wde) {
getLog().warn(wde.getMessage());
}
/*##############################################################*/
/* byte[] snapshot = createPageSnapshot();*/
for (NewPageListener npl : newPageListeners) {
npl.fireNewPage(url, sourceCode, null, jsScriptResult);
}
} catch (UnhandledAlertException uae) {
getLog().warn(uae.getMessage());
throw new TestRunException(currentStep() + " failed.", uae, currentStep().toString(), stepIndex);
}
}
int i = 0;
/**
*
* @return
*/
// @Cacheable("jsScriptResult")
private Map<String, String> executeJsScripts() {
i++;
LOGGER.debug("=================>Appel exec JS: "+i);
getLog().debug("Executing js");
Map<String, String> jsScriptResult = new HashMap<>();
for (Map.Entry<String, String> entry : jsScriptMap.entrySet()) {
try {
jsScriptResult.put(entry.getKey(), getDriver().executeScript(entry.getValue()).toString());
} catch (WebDriverException wde) {
getLog().warn("Script " + entry.getKey() + " has failed");
getLog().warn(wde.getMessage());
}
}
getLog().debug("Js executed");
return jsScriptResult;
}
/**
* This methods uses the screenshot service of Webdriver to create the page
* snapshot.
*
* @return
*/
private byte[] createPageSnapshot() {
if (!(getDriver() instanceof TakesScreenshot)) {
return null;
}
byte[] snapshot = null;
try {
getLog().debug("Creating snapshot");
snapshot = ((TakesScreenshot)getDriver()).getScreenshotAs(OutputType.BYTES);
BufferedImage snapshotImg = ImageIO.read(new ByteArrayInputStream(snapshot));
BufferedImage scaledImg = resizeImage(snapshotImg);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(scaledImg, "png", baos);
baos.flush();
snapshot = baos.toByteArray();
baos.close();
} catch (IOException ex) {
getLog().error("Error when creating snapshot");
getLog().error(ex);
}
getLog().error("Snapshot created");
return snapshot;
}
/**
* Resize the snapshot image to 270*170
*
* @param originalImage
* @return
*/
private BufferedImage resizeImage(BufferedImage originalImage) {
float scaleRatio = (float)270 / originalImage.getWidth();
int resizedImageHeight = Float.valueOf(originalImage.getHeight() * scaleRatio).intValue();
BufferedImage resizedImage = new BufferedImage(270, resizedImageHeight, originalImage.getType());
Graphics2D g = resizedImage.createGraphics();
g.drawImage(originalImage, 0, 0, 270, resizedImageHeight, null);
g.dispose();
return resizedImage.getSubimage(0, 0, 270, 170);
}
@Override
public void initRemoteWebDriver() {
if (firefoxDriverObjectPool != null && getDriver() == null) {
getLog().debug("Initialisation FirefoxDriver terminated ");
try {
rwd = firefoxDriverObjectPool.borrowObject();
} catch (Exception ex) {
getLog().warn("Firefox driver cannot be borrowed due to " + ex.getMessage());
}
// if the firefoxDriver object pool is not set, keep the default behaviour
} else if (getDriver() == null) {
getLog().debug("Launching initialisation of WebDriver");
super.initRemoteWebDriver();
getLog().debug("WebDriver initialised");
}
}
/**
* Properly Returns the WebDriver Object to the pool when finished or
* close and quit the driver if the object pool is not used
*/
private void properlyCloseWebDriver() {
getLog().debug("Closing Firefox driver.");
if (firefoxDriverObjectPool != null &&
getDriver() instanceof FirefoxDriver) {
//set the blank page before returning the webDriver instance
getDriver().get("");
try {
firefoxDriverObjectPool.returnObject((FirefoxDriver)getDriver());
} catch (Exception ex) {
getLog().warn("Firefox driver cannot be returned due to " + ex.getMessage());
}
} else {
try {
getDriver().close();
getDriver().quit();
} catch (Exception e) {
getLog().warn("An error occured while closing driver."
+ " A defunct firefox process may run on the system. "
+ " Trying to kill before leaving");
if (getDriver() instanceof FirefoxDriver) {
((FirefoxDriver)getDriver()).kill();
}
}
}
}
}