// Copyright (c) 2004-2006. Released under the Canoo Webtest license.
package com.canoo.webtest.extension;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import com.canoo.webtest.engine.Context;
import com.canoo.webtest.engine.ContextHelper;
import com.canoo.webtest.engine.StepExecutionException;
import com.canoo.webtest.engine.StepFailedException;
import com.canoo.webtest.engine.WebClientContext;
import com.canoo.webtest.interfaces.IVerificationStep;
import com.canoo.webtest.steps.Step;
import com.canoo.webtest.util.FileUtil;
import com.gargoylesoftware.htmlunit.JavaScriptPage;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.TextPage;
import com.gargoylesoftware.htmlunit.WebResponse;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.xml.XmlPage;
/**
* Compares the current response with a reference file.<p>
* <p/>
* @author Paul King, David Louvton
* @author Marc Guillemot
* @webtest.step category="Extension"
* name="verifyContent"
* description="Compares the current response with an expected file.
* Supports content filters such as <stepref name='lineSeparatorFilter'/>
* and <stepref name='replaceFilter'/> and the nested <stepref name='table' category='Core'/> step."
*/
public class VerifyContent extends AbstractProcessFiltersStep implements IVerificationStep
{
private static final Logger LOG = Logger.getLogger(VerifyContent.class);
private File fReferenceFile;
private boolean fReadFiltered = true;
private static final String DIFF_MODE_AUTO = "auto";
private static final String DIFF_MODE_BIN = "bin";
private static final String DIFF_MODE_TEXT = "text";
private static final String DIFF_MODE_REGEXPERLINE = "regexperline";
private String fMode = DIFF_MODE_AUTO;
public File getReferenceFile() {
return fReferenceFile;
}
/**
* @param mode the diff mode to use
* @webtest.parameter required="no"
* description="The diff mod to use for comparison.
* Possible values are
* \"auto\" (the default, uses \"text\" or \"bin\" depending on the type of the content to compare),
* \"text\" (performs a unified diff like diff),
* \"bin\" (compares byte per byte and displays differences in hex format),
* and \"regexPerLine\" (each line of the reference file is a regex that should match the actual content)."
*/
public void setMode(final String mode) {
fMode = mode;
}
public String getMode() {
return fMode;
}
/**
* @param file
* @webtest.parameter required="yes"
* description="The file to compare to"
*/
public void setReferenceFile(final File file) {
fReferenceFile = file;
}
public boolean isReadFiltered() {
return fReadFiltered;
}
/**
* @param readFiltered
* @webtest.parameter
* required="no"
* default="true"
* description="Indicates that when reading <em>referenceFile</em> that the content should be filtered."
*/
public void setReadFiltered(final boolean readFiltered) {
fReadFiltered = readFiltered;
}
protected void verifyParameters() {
super.verifyParameters();
nullParamCheck(getReferenceFile(), "referenceFile");
if (!getReferenceFile().exists()) {
throw new StepFailedException("Reference file doesn't exist: " + getReferenceFile().getAbsolutePath());
}
final String mode = getMode().toLowerCase();
final String[] allowedModes = {DIFF_MODE_AUTO, DIFF_MODE_BIN, DIFF_MODE_TEXT, DIFF_MODE_REGEXPERLINE};
if (!ArrayUtils.contains(allowedModes, mode))
{
throw new StepExecutionException("Unallowed diff mode >" + getMode() + "<. "
+ "Allowed modes are "
+ ArrayUtils.toString(allowedModes)
+ ".", this);
}
}
public void doExecute() throws Exception {
final WebClientContext.StoredResponses origResponses = getContext().getResponses();
final String result = diffContentWithExpected();
getContext().restoreResponses(origResponses);
if (!StringUtils.isEmpty(result)) {
final StepFailedException e = new StepFailedException("Current response and the reference differ.", this);
e.addDetail("diff", result);
throw e;
}
}
private String diffContentWithExpected() throws IOException {
final List steps = getSteps();
final Context context = getContext();
applyTableFilterIfNeeded(context);
applyExtractionIfNeeded(context);
for (final Iterator iter = steps.iterator(); iter.hasNext();) {
final Step step = (Step) iter.next();
executeContainedStep(step);
}
final WebClientContext.StoredResponses srcResponses = context.getResponses();
final Page actualPage = context.getCurrentResponse();
final WebResponse actualResponse = actualPage.getWebResponse();
LOG.debug("Processig reference file: " + getReferenceFile());
WebClientContext.StoredResponses referenceResponses = preProcessFiles(getReferenceFile(), context);
if (isReadFiltered()) {
LOG.debug("Applying filter on reference file too");
context.restoreResponses(referenceResponses);
for (final Iterator iter = steps.iterator(); iter.hasNext();) {
final Step step = (Step) iter.next();
step.execute();
}
referenceResponses = context.getResponses();
context.restoreResponses(srcResponses);
}
LOG.debug("Source: " + actualResponse.getContentType() + " (" + actualResponse.getRequestUrl() + ")");
context.restoreResponses(referenceResponses);
final Page referencePage = context.getCurrentResponse();
final WebResponse referenceResponse = referencePage.getWebResponse();
LOG.debug("Reference: " + referenceResponse.getContentType() + " (" + referenceResponse.getRequestUrl() + ")");
return produceDiffMessage(actualPage, referencePage);
}
private WebClientContext.StoredResponses preProcessFiles(final File destfile, final Context context) {
LOG.debug("Loading reference file: " + destfile);
final byte[] destBytes = FileUtil.readFileToByteArray(destfile, this);
ContextHelper.defineAsCurrentResponse(context, destBytes,
context.getCurrentResponse().getWebResponse().getContentType(), "http:" + getClass().getName());
return context.getResponses();
}
private String produceDiffMessage(final Page actualPage, final Page referencePage) throws IOException {
String labelReference = getReferenceFile().getAbsolutePath();
String labelActual = "current response";
if (!getSteps().isEmpty())
{
labelActual += " (filtered)";
if (isReadFiltered())
labelReference += " (filtered)";
}
final VerifyContentDiff diff = getDiff(actualPage, referencePage);
LOG.debug("Mode >" + getMode() + "<, using: " + diff);
return diff.compare(referencePage.getWebResponse(), actualPage.getWebResponse(), labelReference, labelActual);
}
/**
* Gets the diff command to use for comparison. This may have been specified
* otherwise a default diff is taken according to the content type of the pages
* @param actualPage
* @param referencePage
*/
private VerifyContentDiff getDiff(final Page actualPage, final Page referencePage) {
String mode = getMode().toLowerCase();
if (DIFF_MODE_AUTO.equals(mode))
{
if (isTextResponse(actualPage) && isTextResponse(referencePage))
mode = DIFF_MODE_TEXT;
else
mode = DIFF_MODE_BIN;
}
if (DIFF_MODE_TEXT.equals(mode))
return new VerifyContentTextDiff();
else if (DIFF_MODE_REGEXPERLINE.equals(mode))
return new VerifyContentRegexPerLineDiff();
else
return new VerifyContentBinDiff();
}
/**
*/
static boolean isTextResponse(final Page _page)
{
// don't rely on mime type as not only text/* are "text" answers (for instance application/xhtml+xml)
return _page instanceof HtmlPage || _page instanceof TextPage || _page instanceof XmlPage || _page instanceof JavaScriptPage;
}
}