// Copyright � 2002-2007 Canoo Engineering AG, Switzerland.
package com.canoo.webtest.engine;
import com.canoo.webtest.ant.WebtestTask;
import com.canoo.webtest.interfaces.IPropertyHandler;
import com.canoo.webtest.steps.HtmlParserMessage;
import com.canoo.webtest.util.Checker;
import com.canoo.webtest.util.MapUtil;
import com.gargoylesoftware.htmlunit.*;
import com.gargoylesoftware.htmlunit.javascript.JavaScriptEngine;
import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLDocument;
import com.gargoylesoftware.htmlunit.util.Cookie;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.WordUtils;
import org.apache.log4j.Logger;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.IntrospectionHelper;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.util.*;
/**
* Captures configuration information.<p>
*
* @author unknown
* @author Marc Guillemot
* @author Paul King
* @webtest.step category="General"
* name="config"
* description="This is a nested task of
* <stepref name='webtest' category='General'/>
* and is used to configure the target host system to use for a particular
* <stepref name='webtest' category='General'/>
* and several other features such as reporting of test results
* and printing of debug information."
*/
public class Configuration extends Task {
private static final Logger LOG = Logger.getLogger(Configuration.class);
public static final int PORT_HTTP = 80;
public static final int PORT_HTTPS = 443;
public static final int DEFAULT_PORT = PORT_HTTP;
public static final String DEFAULT_HOST = "localhost";
public static final String PROTOCOL_HTTP = "http";
public static final String PROTOCOL_HTTPS = "https";
public static final String PROTOCOL_FILE = "file";
private static final String URL_SEPARATOR = "/";
/**
* The number of seconds before http connections timeout: (default={@value})
*/
protected static final int DEFAULT_TIMEOUT = 5 * 60; // protected visibility to generate javadoc
private int fPort = DEFAULT_PORT;
private int fTimeout = DEFAULT_TIMEOUT;
private String fProtocol = PROTOCOL_HTTP;
private boolean fSaveResponse;
private boolean fEasyAjax = false;
private int fEasyAjaxDelay = 2000;
private boolean fUseInsecureSSL_ = false;
private String sslKeyStore;
private String sslKeyStoreType;
private String sslKeyStorePassword;
private String sslTrustStore;
private String sslTrustStorePassword;
private String fSavePrefix = "response";
private String fAutoRefresh = "false";
private String fResultFile = "WebTestReport.xml";
private boolean fShowHtmlParserOutput;
private boolean fHaltOnError;
private boolean fHaltOnFailure;
private String fErrorProperty;
private String fFailureProperty;
private String fDefaultPropertyType;
private File fSummaryFile;
private String fHost = DEFAULT_HOST;
private String fBasePath;
private File fResultPath;
private File fWebtestResultDir;
private String fBrowser;
private boolean fRespectVisibility_;
private IPropertyHandler fPropertyHandler = new IPropertyHandler() {
public String getProperty(String propertyName) {
return getProject().getProperty(propertyName);
}
};
private final List fHeaderList = new LinkedList();
private final List fOptionList = new LinkedList();
private Context fContext;
private int resultFolderIndex = -1;
/**
* The task properties that may take default values from the corresponding wt.config.* project properties
* (when not configured explicitly on the task).
*/
private final static String[] PROPERTIES = {"autorefresh", "basepath", "defaultpropertytype",
"errorproperty", "failureproperty", "haltonerror", "haltonfailure",
"host", "port", "protocol", "resultpath", "saveprefix", "saveresponse", "showhtmlparseroutput",
"timeout", "easyajax", "easyajaxdelay", "browser",
"useInsecureSSL",
"sslKeyStore", "sslKeyStoreType", "sslKeyStorePassword",
"sslTrustStore", "sslTrustStorePassword"};
/**
* Configuration constructor used by instance creation as nested element in ant.<p>
*/
public Configuration() {
fHaltOnError = true;
fHaltOnFailure = true;
}
/**
* Get's called from Ant once the project and target references have been set
* but before the properties are configured
*/
public void init() {
// read values from wt.config.* properties
configureDefaultFromProjectProperties();
}
/**
* Configuration constructor used to generate a default configuration when the user omit it.<p>
*/
public Configuration(final WebtestTask testSpec) {
// default properties
fSaveResponse = true;
fErrorProperty = "webtest.error";
fFailureProperty = "webtest.failure";
fShowHtmlParserOutput = true;
setProject(testSpec.getProject());
setOwningTarget(testSpec.getOwningTarget());
init(); // as ant does, here to configure from wt.config.* properties
}
/* Must be done in execute not init as task attributes not available in init */
public void execute() throws BuildException {
configureDefaultFromProjectProperties();
setSystemProperties();
if (getResultpath() == null) {
setResultpath(getProject().resolveFile("webtest-results"));
}
prepareResultDir(getResultpath());
Checker.assertTrue(getResultFile() != null, "Result file cannot be null when writing summary");
fSummaryFile = new File(getWebTestResultDir(), getResultFile());
LOG.debug("Result file: " + fSummaryFile.getAbsolutePath());
try {
setupWebClient();
} catch (IOException e) {
throw new BuildException(e);
}
}
private void setSystemProperties() {
setSystemPropertyIfNotNull("javax.net.ssl.trustStore", getSslTrustStore());
setSystemPropertyIfNotNull("javax.net.ssl.trustStorePassword", getSslTrustStorePassword());
}
private void setSystemPropertyIfNotNull(String key, String value) {
if (value != null) {
System.setProperty(key, value);
}
}
protected void setupWebClient() throws IOException {
WebClient webClient = createWebClient();
webClient = fContext.getWebtest().getWebtestCustomizer().customizeWebClient(webClient);
fContext.setWebClient(webClient);
// catcher for JS background errors (as long as HtmlUnit doesn't provide a better solution to handle this)
final Thread mainThread = Thread.currentThread();
final JavaScriptEngine myEngine = new JavaScriptEngine(webClient) {
protected void handleJavaScriptException(final ScriptException scriptException, final boolean triggerOnError) {
if (Thread.currentThread() != mainThread && getWebClient().isThrowExceptionOnScriptError()) {
fContext.setBackgroundJSError(scriptException);
}
super.handleJavaScriptException(scriptException, false);
}
};
webClient.setJavaScriptEngine(myEngine);
// CSV report about sent/received requests/responses
final File csvFile = new File(getWebTestResultDir(), "requests.csv");
final WebConnection newConnection = new CSVTracingWebConnection(webClient.getWebConnection(), csvFile);
webClient.setWebConnection(newConnection);
}
private void configureDefaultFromProjectProperties() {
// read the properties that have been configured, they should not be replaced!
final Set existingProps = new HashSet();
if (getRuntimeConfigurableWrapper() != null) // null when no config is used
{
for (final Iterator iter = getRuntimeConfigurableWrapper().getAttributeMap().keySet().iterator(); iter.hasNext(); ) {
existingProps.add(((String) iter.next()).toLowerCase());
}
}
final IntrospectionHelper ih = IntrospectionHelper.getHelper(getProject(), getClass());
for (int i = 0; i < PROPERTIES.length; ++i) {
final String propName = PROPERTIES[i];
if (!existingProps.contains(propName)) {
final String propValue = getProject().getProperty("wt.config." + propName);
if (propValue != null) {
LOG.info("Using " + propName + " from project property wt.config." + propName + ": " + propValue);
ih.setAttribute(getProject(), this, propName, propValue);
}
}
}
}
// package protection for testing purposes
void prepareResultDir(final File resultDir) {
if (resultDir.exists() && !resultDir.isDirectory()) {
throw new BuildException("Result dir is not a directory: " + resultDir.getAbsolutePath());
}
// compute subdir for this test
fWebtestResultDir = computeSubFolder(resultDir);
LOG.info("Creating result directory: " + fWebtestResultDir.getAbsolutePath());
if (!fWebtestResultDir.mkdirs()) {
throw new BuildException("Failed to create result dir: " + fWebtestResultDir.getAbsolutePath());
}
}
/**
* Compute the name of the subfolder for this test
*
* @param _resultDir the "main" result dir
* @return the name of the subfolder
*/
protected File computeSubFolder(final File _resultDir) {
if (resultFolderIndex == -1)
resultFolderIndex = getResultFolderIndex(_resultDir);
final String prefix = StringUtils.leftPad(String.valueOf(resultFolderIndex), 3, '0');
final String fixedTestName = WordUtils.capitalize(fContext.getWebtest().getName()).replaceAll("\\W", "");
final int dirNameMaxLength = 40;
String name = StringUtils.left(prefix + "_" + fixedTestName, dirNameMaxLength);
final String suffix = getProject().getProperty("~wt.config.resultfolder.suffix");
if (suffix != null) {
name += suffix;
}
return new File(_resultDir, name);
}
/**
* Sets the index of the result folder. Normally this shouldn't be set from
* outside but the new experimental feature "WebTest parallel" currently needs it.
*
* @param index the index
*/
public void setResultFolderIndex(final int index) {
resultFolderIndex = index;
}
/**
* Get the index to use as prefix for the dedicated result folder of this test
*
* @param _resultDir the base result directory
* @return the index
*/
protected int getResultFolderIndex(final File _resultDir) {
int lastIndex = 0;
final File[] children = _resultDir.listFiles();
if (children != null) // null when _resultDir is not (yet?) a directory
{
for (int i = 0; i < children.length; i++) {
final File f = children[i];
if (f.getName().matches("\\d{3,}_.*")) {
final int index = Integer.parseInt(StringUtils.substringBefore(f.getName(), "_"));
lastIndex = Math.max(lastIndex, index);
}
}
}
return lastIndex + 1;
}
/**
* Gets the file where the summary should be written.<p>
*
* @return <code>null</code> if no resultfile was specified
*/
public File getSummaryFile() {
// TODO: remove it!
return fSummaryFile;
}
/**
* @param header
* @webtest.nested.parameter required="no"
* description="Specify http headers by name and value"
*/
public void addHeader(final Header header) {
fHeaderList.add(header);
}
public List getHeaderList() {
return fHeaderList;
}
/**
* @param option
* @webtest.nested.parameter required="no"
* description="Tweak the underlying web client options/settings"
*/
public void addOption(final Option option) {
fOptionList.add(option);
}
public List getOptionList() {
return fOptionList;
}
public String getBasePath() {
return fBasePath;
}
/**
* Gets the User-Agent header to be sent.<p>
*
* @return <code>null</code> if none has been configured
*/
public String getUserAgent() {
LOG.debug("Headers: " + getHeaderList());
for (final Iterator iter = getHeaderList().iterator(); iter.hasNext(); ) {
final Header elt = (Header) iter.next();
if ("User-Agent".equals(elt.getName())) {
LOG.debug("Found User-Agent header: " + elt.getValue());
return elt.getValue();
}
LOG.debug("Not User-Agent header: " + elt.getName());
}
return null;
}
/**
* Indicates if the default port for the protocol is used (that is port is not needed in the url).<p>
*/
private boolean isDefaultPort() {
return (PROTOCOL_HTTP.equals(getProtocol()) && PORT_HTTP == getPort())
|| (PROTOCOL_HTTPS.equals(getProtocol()) && PORT_HTTPS == getPort());
}
public String getHost() {
return fHost;
}
public int getPort() {
return fPort;
}
public String getProtocol() {
return fProtocol;
}
/**
* This is the configured general result dir.
* This value should not be used directly as results will be placed in a sub folder of it in the future
*
* @return the configured result path
*/
public File getResultpath() {
return fResultPath;
}
/**
* Gets the directory where all artifacts of this specific test should be saved.
* This is currently the same than {@link #getResultpath()} but this may change in the future.
*
* @return the folder
*/
public File getWebTestResultDir() {
return fWebtestResultDir;
}
public String getSavePrefix() {
return fSavePrefix;
}
/**
* Completes a URL based on configuration values by expanding
* where needed from a base URL (protocol and optionally
* host:port). If the provided page is complete (already with protocol), it is returned as is.<p>
*
* @param page the maybe relative target URL
* @return the assembled URL
*/
public String getUrlForPage(final String page) {
// first test if the page starts with a protocol like "http://", "https://", "file:/"
final int index = StringUtils.indexOf(page, "://");
if (index > -1 && index < 6 || (page != null && page.toLowerCase().startsWith("file:/"))) {
return page;
} else if (PROTOCOL_FILE.equals(getProtocol())) {
return createFileBasedUrl(page);
} else {
return createNetworkBasedUrl(page);
}
}
private String createFileBasedUrl(final String page) {
return getProtocol() + ":" + combineBasePathAndPage(getBasePath(), page);
}
private String createNetworkBasedUrl(final String page) {
final StringBuffer url = new StringBuffer(getProtocol());
url.append("://");
url.append(getHost());
if (!isDefaultPort()) {
url.append(":");
url.append(getPort());
}
url.append(combineBasePathAndPage(getBasePath(), page));
return url.toString();
}
private static String combineBasePathAndPage(final String basePath, final String page) {
String basePathClean = StringUtils.strip(basePath, URL_SEPARATOR);
basePathClean = StringUtils.isEmpty(basePathClean) ? "" : (URL_SEPARATOR + basePathClean);
String pageClean = StringUtils.stripStart(page, URL_SEPARATOR);
pageClean = StringUtils.isEmpty(pageClean) ? "" : (URL_SEPARATOR + pageClean);
return basePathClean + pageClean;
}
public boolean isSaveResponse() {
return fSaveResponse;
}
public void setSavePrefix(String savePrefix) {
fSavePrefix = savePrefix;
}
/**
* Defines the constant base path used to construct request URLs,
* e.g. "shop" can be considered a basepath in "http://www.myhost.com/shop/productlist"
* and "http://www.myhost.com/shop/checkout".<p>
*
* @param newBasePath the new value
*/
public void setBasepath(final String newBasePath) {
fBasePath = newBasePath;
}
public void setHost(final String newHost) {
fHost = newHost;
}
public void setPort(final int newPort) {
fPort = newPort;
}
public void setProtocol(final String newProtocol) {
fProtocol = newProtocol;
}
public void setResultpath(final File newResultPath) {
fResultPath = newResultPath;
}
public void setSaveresponse(final boolean newSaveResponse) {
fSaveResponse = newSaveResponse;
}
public void setSummary(final boolean newSummary) {
LOG.warn("Config attribute summary is deprecated and doesn't do anything");
}
public void setHaltonerror(final boolean haltOnError) {
fHaltOnError = haltOnError;
}
public void setHaltonfailure(final boolean haltOnFailure) {
fHaltOnFailure = haltOnFailure;
}
public void setErrorProperty(final String errorProperty) {
fErrorProperty = errorProperty;
}
public void setFailureProperty(final String failureProperty) {
fFailureProperty = failureProperty;
}
public String getFailureProperty() {
return fFailureProperty;
}
public String getErrorProperty() {
return fErrorProperty;
}
public String getDefaultPropertyType() {
return fDefaultPropertyType;
}
public void setDefaultPropertyType(final String type) {
fDefaultPropertyType = type;
}
public void setShowhtmlparseroutput(final boolean showParserOutput) {
fShowHtmlParserOutput = showParserOutput;
}
public boolean isShowHtmlParserOutput() {
return fShowHtmlParserOutput;
}
public boolean isHaltOnFailure() {
return fHaltOnFailure;
}
public boolean isHaltOnError() {
return fHaltOnError;
}
public Map getParameterDictionary() {
final Map map = new HashMap();
map.put("host", getHost());
map.put("protocol", getProtocol());
map.put("port", String.valueOf(getPort()));
map.put("timeout", String.valueOf(getTimeout()));
map.put("basepath", getBasePath());
map.put("resultpath", getResultpath());
map.put("saveresponse", isSaveResponse() ? "yes" : "no");
map.put("saveprefix", getSavePrefix());
map.put("haltonerror", isHaltOnError() ? "yes" : "no");
map.put("haltonfailure", isHaltOnFailure() ? "yes" : "no");
MapUtil.putIfNotNull(map, "errorproperty", getErrorProperty());
MapUtil.putIfNotNull(map, "failureproperty", getFailureProperty());
MapUtil.putIfNotNull(map, "defaultpropertytype", getDefaultPropertyType());
map.put("autorefresh", getAutoRefresh());
map.put("showhtmlparseroutput", isShowHtmlParserOutput() ? "yes" : "no");
map.put("resultfile", getResultFile());
map.put("browser", getBrowser());
// todo add new props for reporting
return map;
}
public String getResultFile() {
return fResultFile;
}
public void setResultfile(final String resultFile) {
// fResultFile = resultFile;
}
public void setPropertyHandler(final IPropertyHandler propertyHandler) {
fPropertyHandler = propertyHandler;
}
public String getExternalProperty(final String name) {
if (fPropertyHandler == null) {
throw new IllegalStateException("No property handler configured!");
}
return fPropertyHandler.getProperty(name);
}
/**
* Returns true if the client should automatically follow page refresh requests.<p>
*/
public String getAutoRefresh() {
return fAutoRefresh;
}
/**
* Determines if the client should automatically follow page refresh requests.<p>
*
* @param str the new refresh value
*/
public void setAutoRefresh(final String str) {
fAutoRefresh = str;
}
/**
* Defines the context. This is called to set the context on the task before {@link #execute()}
* is called
*
* @param context the context
*/
public void setContext(final Context context) {
fContext = context;
}
/**
* Gets the context for the current webtest
*/
public Context getContext() {
return fContext;
}
/**
* Configures the webclient used for the test
*/
public WebClient createWebClient() {
final Configuration cfg = this;
final String strUserAgent = cfg.getUserAgent();
final BrowserVersion browserVersion = setupBrowserVersion(fBrowser, strUserAgent);
fBrowser = browserVersion.getNickname(); // to see it in the reports
final WebClient webClient = setupWebClient(browserVersion);
webClient.setTimeout(getTimeout() * 1000);
setupHtmlParser(webClient, cfg);
setupRefreshHandler(webClient, cfg); // auto refresh settings
webClient.setThrowExceptionOnScriptError(true); // option for this and report js errors?
try {
setupSSLIfNeeded(webClient);
} catch (final GeneralSecurityException e) {
throw new RuntimeException(e);
}
setupHttpHeaders(webClient, cfg);
setupOptions(webClient, cfg);
configurePageCreator(webClient);
return webClient;
}
private void setupSSLIfNeeded(WebClient webClient) throws GeneralSecurityException {
if (shouldUseClientCert()) {
webClient.setSSLClientCertificate(getCertUrl(), getCertPassword(), getCertType());
}
if (getUseInsecureSSL()) {
webClient.setUseInsecureSSL(getUseInsecureSSL());
}
}
boolean shouldUseClientCert() {
return getCertUrl() != null;
}
URL getCertUrl() {
String pathOrURL = getSslKeyStore();
if (pathOrURL == null) {
return null;
}
try {
File certFile = new File(pathOrURL);
if (certFile.exists()) {
return certFile.toURI().toURL();
}
} catch (MalformedURLException e) {
e.printStackTrace();
}
try {
return new URL(pathOrURL);
} catch (MalformedURLException e) {
e.printStackTrace();
return null;
}
}
String getCertPassword() {
return getSslKeyStorePassword();
}
String getCertType() {
String type = getSslKeyStoreType();
return type == null ? "jks" : type;
}
/**
* Configures WebTest's custom page creator able to create PDFPage (as long as this is not integrated
* directly into HtmlUnit)
*/
protected void configurePageCreator(final WebClient webClient) {
webClient.setPageCreator(new PdfAwarePageCreator());
}
static BrowserVersion setupBrowserVersion(final String browserName, final String strUserAgent) {
BrowserVersion browserVersion = null;
if (browserName != null) {
final String browserNameLC = browserName.toLowerCase().trim();
if ("ff3".equals(browserNameLC) || "firefox3".equals(browserNameLC))
browserVersion = BrowserVersion.FIREFOX_3;
else if ("ff3.6".equals(browserNameLC) || "firefox3.6".equals(browserNameLC))
browserVersion = BrowserVersion.FIREFOX_3_6;
else if ("ff10".equals(browserNameLC) || "firefox10".equals(browserNameLC))
browserVersion = BrowserVersion.FIREFOX_10;
else if ("ie6".equals(browserNameLC) || "internetexplorer6".equals(browserNameLC))
browserVersion = BrowserVersion.INTERNET_EXPLORER_6;
else if ("ie7".equals(browserNameLC) || "internetexplorer7".equals(browserNameLC))
browserVersion = BrowserVersion.INTERNET_EXPLORER_7;
else if ("ie8".equals(browserNameLC) || "internetexplorer8".equals(browserNameLC))
browserVersion = BrowserVersion.INTERNET_EXPLORER_8;
else
throw new IllegalArgumentException("Illegal browser version: >" + browserName + "<");
}
if (strUserAgent != null) {
// as long as all browser properties are not configurable from the task,
// use a "base" browser
final BrowserVersion baseBrowser;
if (browserVersion != null) {
baseBrowser = browserVersion;
} else if (strUserAgent.indexOf("Gecko") != -1) {
baseBrowser = BrowserVersion.FIREFOX_3;
} else {
baseBrowser = BrowserVersion.INTERNET_EXPLORER_6;
}
browserVersion = new BrowserVersion(baseBrowser.getApplicationName(),
baseBrowser.getApplicationVersion(), strUserAgent,
baseBrowser.getBrowserVersionNumeric());
LOG.info("Using browser version (" + browserVersion.getApplicationName() + ", "
+ browserVersion.getApplicationVersion() + ", " + strUserAgent + ", "
+ ", " + browserVersion.getBrowserVersionNumeric()
+ "). If the javascript support is not as expected, then it's time to go into the sources");
} else if (browserVersion == null) { // nothing specified
browserVersion = BrowserVersion.INTERNET_EXPLORER_6;
LOG.info("Surfing with default browser " + browserVersion.getUserAgent());
} else {
LOG.info("Surfing with browser " + browserVersion.getNickname());
}
return browserVersion;
}
// Paul Devine 4/12/2005: adding proxy support (for now only UsernamePasswordCredentials. For a proxy
// that does not require authentication the credentials will not be consulted anyway, so it's ok to
// leave user/password null or blank in those situations.)
// package protected for testing purposes
static WebClient setupWebClient(final BrowserVersion browserVersion) {
final WebClient webClient;
final DefaultCredentialsProvider credentialProvider = new DefaultCredentialsProvider();
String proxyHost = System.getProperty("http.proxyHost");
if (proxyHost != null && proxyHost.length() > 0) {
// the properties are set for instance by org.apache.tools.ant.taskdefs.optional.net.SetProxy
// the proxy setting
final int proxyPort = Integer.parseInt(System.getProperty("http.proxyPort", "80"));
LOG.info("Configuring proxy from http.proxyHost* system properties: "
+ proxyHost + ":" + proxyPort);
webClient = new WebClient(browserVersion, proxyHost, proxyPort);
configureProxy(webClient, credentialProvider);
} else {
webClient = new WebClient(browserVersion);
}
webClient.setCredentialsProvider(credentialProvider);
return webClient;
}
/**
* Configures the proxy settings from the system properties
*/
static void configureProxy(final WebClient webClient, final DefaultCredentialsProvider credentialProvider) {
// the non proxy hosts if any
final String nonProxyHostsSetting = System.getProperty("http.nonProxyHosts");
LOG.info("Configuring proxy from http.nonProxyHosts system property: " + nonProxyHostsSetting);
if (nonProxyHostsSetting != null) {
final String[] nonProxyHosts = nonProxyHostsSetting.split("\\|");
for (int i = 0; i < nonProxyHosts.length; ++i) {
String nonProxyHost = nonProxyHosts[i];
nonProxyHost = nonProxyHost.replaceAll("\\.", "\\\\."); // escape "."
nonProxyHost = nonProxyHost.replaceAll("\\*", ".*"); // give regex meaning to *
LOG.debug("addHostsToProxyBypass: >" + nonProxyHost + "<");
webClient.getProxyConfig().addHostsToProxyBypass(nonProxyHost);
}
}
// does the proxy need authentification?
if (System.getProperty("http.proxyUser") != null) {
final String proxyUser = System.getProperty("http.proxyUser");
final String proxyPassword = System.getProperty("http.proxyPassword");
LOG.info("Configuring proxy credentials from http.proxyHost* system properties: "
+ proxyUser + ", " + proxyPassword);
credentialProvider.addCredentials(proxyUser, proxyPassword);
}
}
private static void setupHtmlParser(final WebClient webClient, final Configuration cfg) {// Sets a collector to catch parser warnings in order to report
// misformed/invalid HTML in responses
if (cfg.isShowHtmlParserOutput()) {
webClient.setHTMLParserListener(new HtmlParserMessage.MessageCollector());
LOG.debug("Configured a parser listener to collect messages generated while parsing html");
} else {
LOG.debug("showHtmlParserOutput is off, no listener configured");
}
}
private static void setupRefreshHandler(final WebClient webClient, final Configuration cfg) {
final boolean bUseDelay;
final boolean bRefreshAll;
final int acceptedRefreshDelay;
if (cfg.getAutoRefresh().matches("\\d+")) {
bUseDelay = true;
bRefreshAll = false;
acceptedRefreshDelay = Integer.parseInt(cfg.getAutoRefresh());
} else {
bUseDelay = false;
bRefreshAll = Project.toBoolean(cfg.getAutoRefresh());
acceptedRefreshDelay = 0;
}
LOG.debug("Configuring RefreshHandler (refreshAll: " + bRefreshAll + ", useDelay: " + bUseDelay + ", refreshDelay: " + acceptedRefreshDelay);
final RefreshHandler refreshHandler = new RefreshHandler() {
public void handleRefresh(final Page page, final URL url, final int iTimeBeforeRefresh) throws IOException {
final boolean bRefresh = bRefreshAll || (bUseDelay && iTimeBeforeRefresh <= acceptedRefreshDelay);
if (bRefresh) {
LOG.info("Performing refresh to " + url + " (delay: " + iTimeBeforeRefresh + ") according to configuration");
final WebWindow window = page.getEnclosingWindow();
if (window == null) {
return;
}
final WebClient client = window.getWebClient();
client.getPage(window, new WebRequest(url));
} else {
LOG.info("no refresh performed to " + url + " (delay: " + iTimeBeforeRefresh + ") according to configuration");
}
}
};
webClient.setRefreshHandler(refreshHandler);
}
/**
* Sets the http headers configured through the <header/> subelements of
* <config>
*/
private static void setupHttpHeaders(final WebClient webClient, final Configuration cfg) {
if (cfg.getHeaderList().size() > 0) {
LOG.info("Configuring " + cfg.getHeaderList().size() + " HTTP header field(s)");
}
// default value for Accept-Language (gets overwritten below if configured in headers)
webClient.addRequestHeader("Accept-Language", "en-us,en;q=0.5");
for (final Iterator iter = cfg.getHeaderList().iterator(); iter.hasNext(); ) {
final Header header = (Header) iter.next();
if ("User-Agent".equals(header.getName())) {
LOG.info("Skipped User-Agent header as it has already been configured in the BrowserVersion");
} else if ("Cookie".equals(header.getName())) {
try {
webClient.getCookieManager().addCookie(
HTMLDocument.buildCookie(header.getValue(),
new URL(cfg.getUrlForPage("/"))));
} catch (MalformedURLException e) {
e.printStackTrace();
}
} else {
webClient.addRequestHeader(header.getName(), header.getValue());
LOG.info("Configured header \"" + header.getName() + "\": " + header.getValue());
}
}
}
/**
* Prepares the underlying browser client using option subelements
* in the config. Currently calls setXXX methods in the WebClient
* API. See the HtmlUnit JavaDocs for more details.
*/
private static void setupOptions(final WebClient webClient, final Configuration cfg) {
final List options = cfg.getOptionList();
for (Iterator iter = options.iterator(); iter.hasNext(); ) {
final Option option = (Option) iter.next();
boolean found = tryBooleanCallingMethod(option, webClient);
if (!found) {
found = tryIntCallingMethod(option, webClient);
}
if (!found) {
LOG.warn("Unknown option <" + option.getName() + ">. Ignored.");
}
}
}
/**
* Invokes a setXXX method in response to an option subelement
* in the config named 'XXX'. Currently only boolean values are supported.
*
* @param option
* @param optionObject
* @return <code>true</code> if option has been successfully set
*/
private static boolean tryBooleanCallingMethod(final Option option, final Object optionObject) {
final Class[] booleanClass = {boolean.class};
try {
tryCallMethod(optionObject, option, booleanClass, new Object[]{Boolean.valueOf(option.getValue())});
return true;
} catch (Exception e) {
LOG.info("Exception while trying to set boolean option: " + e.getMessage());
return false;
}
}
/**
* Invokes a setXXX method in response to an option subelement
* in the config named 'XXX'. Currently only boolean values are supported.
*
* @param option
* @param optionObject
* @return <code>true</code> if option has been successfully set
*/
private static boolean tryIntCallingMethod(final Option option, final Object optionObject) {
final Class[] intClass = {int.class};
try {
tryCallMethod(optionObject, option, intClass, new Object[]{Integer.valueOf(option.getValue())});
return true;
} catch (Exception e) {
LOG.info("Exception while trying to set integer option: " + e.getMessage());
return false;
}
}
private static void tryCallMethod(final Object optionObject, final Option option,
final Class[] typeSpec, final Object[] params) throws Exception {
final Method method = optionObject.getClass().getDeclaredMethod("set" + option.getName(), typeSpec);
method.invoke(optionObject, params);
LOG.info("set option <" + option.getName() + "> to value <" + option.getValue() + ">");
}
/**
* Gets the timeout in seconds
*
* @return the timeout (default value is {@link #DEFAULT_TIMEOUT}).
*/
public int getTimeout() {
return fTimeout;
}
/**
* Sets the timeout
*
* @param timeout the new value (in seconds), set <code>0</code> for no timeout
*/
public void setTimeout(final int timeout) {
fTimeout = timeout;
}
/**
* Experimental.
* Indicates if WebTest should wait for completion of background js tasks after each step.
* This should become true per default once HtmlUnit has the necessary functionality to
* control this which is not yet the case with HtmlUnit 1.14.
* This feature is intentionally undocumented.
*
* @param b the new value
*/
public void setEasyAjax(final boolean b) {
fEasyAjax = b;
}
public boolean isEasyAjax() {
return fEasyAjax;
}
/**
* Experimental.
* See {@link #setEasyAjax(boolean)}
*
* @param delay the new value
*/
public void setEasyAjaxDelay(int delay) {
fEasyAjaxDelay = delay;
}
/**
* Experimental. Default value is 2000
*/
public int getEasyAjaxDelay() {
return fEasyAjaxDelay;
}
/**
* Defines if insecure SSL should be used, ie that certificates should not be validated
* and therefore outdated or self-signed certificates accepted.
*
* @param insecureSSL the new value
*/
public void setUseInsecureSSL(final boolean insecureSSL) {
fUseInsecureSSL_ = insecureSSL;
}
/**
* Indicate if insecure SSL should be used, ie that certificates should not be validated
* and therefore outdated or self-signed certificates accepted.
*
* @return the value (default is <code>false</code>)
*/
public boolean getUseInsecureSSL() {
return fUseInsecureSSL_;
}
public String getSslKeyStore() {
return sslKeyStore;
}
/**
* Defines the path of the keystore file that is used to load the certificates for client authentication
*
* @param sslKeyStore
*/
public void setSslKeyStore(String sslKeyStore) {
this.sslKeyStore = sslKeyStore;
}
/**
* @return the path of the keystore file that is used to load the certificates for client authentication
*/
public String getSslKeyStoreType() {
return sslKeyStoreType;
}
/**
* Defines the type of the keystore that is used to load the certificates for client authentication
*
* @param sslKeyStoreType
*/
public void setSslKeyStoreType(String sslKeyStoreType) {
this.sslKeyStoreType = sslKeyStoreType;
}
/**
* @return the type of the keystore that is used to load the certificates for client authentication
*/
public String getSslKeyStorePassword() {
return sslKeyStorePassword;
}
public void setSslKeyStorePassword(String sslKeyStorePassword) {
this.sslKeyStorePassword = sslKeyStorePassword;
}
public String getSslTrustStore() {
return sslTrustStore;
}
public void setSslTrustStore(String sslTrustStore) {
this.sslTrustStore = sslTrustStore;
}
public String getSslTrustStorePassword() {
return sslTrustStorePassword;
}
public void setSslTrustStorePassword(String sslTrustStorePassword) {
this.sslTrustStorePassword = sslTrustStorePassword;
}
/**
* Experimental: indicates if WebTest should try to work only on displayed elements.
* Currently not all WebTest steps respect this configuration option.
* No documentation is generated as long as it is experimental.
*
* @since Build 1783
*/
public void setRespectVisibility(final boolean b) {
fRespectVisibility_ = b;
}
/**
* Experimental: indicates if WebTest tries to work only on displayed elements.
* Currently not all WebTest steps respect this configuration option.
*
* @return default is <code>false</code>
* @since Build 1783
*/
public boolean isRespectVisibility() {
return fRespectVisibility_;
}
/**
* Configures the browser that should be simulated in the test
*
* @param browser the browser name like "Firefox3" or "FF3"
*/
public void setBrowser(final String browser) {
fBrowser = browser;
}
/**
* Gets the configured browser
*
* @return <code>null</code> if nothing has been configured
*/
public String getBrowser() {
return fBrowser;
}
}