001: // Copyright © 2002-2007 Canoo Engineering AG, Switzerland.
002: package com.canoo.webtest.engine;
003:
004: import java.io.File;
005: import java.io.IOException;
006: import java.lang.reflect.Method;
007: import java.net.URL;
008: import java.util.HashMap;
009: import java.util.HashSet;
010: import java.util.Iterator;
011: import java.util.LinkedList;
012: import java.util.List;
013: import java.util.Map;
014: import java.util.Set;
015:
016: import org.apache.commons.lang.StringUtils;
017: import org.apache.commons.lang.WordUtils;
018: import org.apache.log4j.Logger;
019: import org.apache.tools.ant.BuildException;
020: import org.apache.tools.ant.IntrospectionHelper;
021: import org.apache.tools.ant.Project;
022: import org.apache.tools.ant.Task;
023:
024: import com.canoo.webtest.ant.WebtestTask;
025: import com.canoo.webtest.interfaces.IPropertyHandler;
026: import com.canoo.webtest.plugins.pdftest.htmlunit.PDFPage;
027: import com.canoo.webtest.plugins.pdftest.htmlunit.pdfbox.PdfBoxPDFPage;
028: import com.canoo.webtest.steps.HtmlParserMessage;
029: import com.canoo.webtest.util.Checker;
030: import com.canoo.webtest.util.MapUtil;
031: import com.gargoylesoftware.htmlunit.BrowserVersion;
032: import com.gargoylesoftware.htmlunit.DefaultCredentialsProvider;
033: import com.gargoylesoftware.htmlunit.DefaultPageCreator;
034: import com.gargoylesoftware.htmlunit.Page;
035: import com.gargoylesoftware.htmlunit.RefreshHandler;
036: import com.gargoylesoftware.htmlunit.WebClient;
037: import com.gargoylesoftware.htmlunit.WebRequestSettings;
038: import com.gargoylesoftware.htmlunit.WebResponse;
039: import com.gargoylesoftware.htmlunit.WebWindow;
040:
041: /**
042: * Captures configuration information.<p>
043: *
044: * @author unknown
045: * @author Marc Guillemot
046: * @author Paul King
047: * @webtest.step category="General"
048: * name="config"
049: * description="This is a nested task of
050: * <stepref name='webtest' category='General'/>
051: * and is used to configure the target host system to use for a particular
052: * <stepref name='webtest' category='General'/>
053: * and several other features such as reporting of test results
054: * and printing of debug information."
055: */
056: public class Configuration extends Task {
057: private static final Logger LOG = Logger
058: .getLogger(Configuration.class);
059: public static final int PORT_HTTP = 80;
060: public static final int PORT_HTTPS = 443;
061: public static final int DEFAULT_PORT = PORT_HTTP;
062: public static final String DEFAULT_HOST = "localhost";
063: public static final String PROTOCOL_HTTP = "http";
064: public static final String PROTOCOL_HTTPS = "https";
065: public static final String PROTOCOL_FILE = "file";
066: private static final String URL_SEPARATOR = "/";
067:
068: /** The number of seconds before http connections timeout: (default={@value}) */
069: protected static final int DEFAULT_TIMEOUT = 5 * 60; // protected visibility to generate javadoc
070:
071: private boolean fSummary;
072: private int fPort = DEFAULT_PORT;
073: private int fTimeout = DEFAULT_TIMEOUT;
074: private String fProtocol = PROTOCOL_HTTP;
075: private boolean fSaveResponse;
076: private boolean fEasyAjax = false;
077:
078: private String fSavePrefix = "response";
079: private String fAutoRefresh = "false";
080: private String fResultFile = "WebTestReport.xml";
081:
082: private boolean fShowHtmlParserOutput;
083: private boolean fHaltOnError;
084: private boolean fHaltOnFailure;
085: private String fErrorProperty;
086: private String fFailureProperty;
087: private String fDefaultPropertyType;
088: private File fSummaryFile;
089:
090: private String fHost = DEFAULT_HOST;
091: private String fBasePath;
092: private File fResultPath;
093: private File fWebtestResultDir;
094:
095: private IPropertyHandler fPropertyHandler;
096: private final List fHeaderList = new LinkedList();
097: private final List fOptionList = new LinkedList();
098: private Context fContext;
099: private int resultFolderIndex = -1;
100:
101: /**
102: * The task properties that may take default values from the corresponding wt.config.* project properties
103: * (when not configured explicitely on the task).
104: */
105: private final static String[] PROPERTIES = { "autorefresh",
106: "basepath", "defaultpropertytype", "errorproperty",
107: "failureproperty", "haltonerror", "haltonfailure", "host",
108: "port", "protocol", "resultpath", "saveprefix",
109: "saveresponse", "showhtmlparseroutput", "summary",
110: "timeout" };
111:
112: /**
113: * Configuration constructor used by instance creation as nested element in ant.<p>
114: */
115: public Configuration() {
116: fHaltOnError = true;
117: fHaltOnFailure = true;
118: }
119:
120: /**
121: * Get's called from Ant once the project and target references have been set
122: * but before the properties are configured
123: */
124: public void init() {
125: // read values from wt.config.* properties
126: configureDefaultFromProjectProperties();
127: }
128:
129: /**
130: * Configuration constructor used to generate a default configuration when the user omit it.<p>
131: */
132: public Configuration(final WebtestTask testSpec) {
133: // default properties
134: fSummary = true;
135: fSaveResponse = true;
136: fErrorProperty = "webtest.error";
137: fFailureProperty = "webtest.failure";
138: fShowHtmlParserOutput = true;
139: setProject(testSpec.getProject());
140: setOwningTarget(testSpec.getOwningTarget());
141: init(); // as ant does, here to configure from wt.config.* properties
142: }
143:
144: /* Must be done in execute not init as task attributes not available in init */
145: public void execute() throws BuildException {
146: configureDefaultFromProjectProperties();
147:
148: if (getResultpath() == null) {
149: setResultpath(getProject().resolveFile("webtest-results"));
150: }
151: prepareResultDir(getResultpath());
152: if (isSummary()) {
153: Checker.assertTrue(getResultFile() != null,
154: "Result file cannot be null when writing summary");
155: fSummaryFile = new File(getWebTestResultDir(),
156: getResultFile());
157: LOG.debug("Result file: " + fSummaryFile.getAbsolutePath());
158: }
159:
160: setupWebClient();
161: }
162:
163: protected void setupWebClient() {
164: fContext.setWebClient(createWebClient());
165: }
166:
167: private void configureDefaultFromProjectProperties() {
168: // read the properties that have been configured, they should not be replaced!
169: final Set existingProps = new HashSet();
170: if (getRuntimeConfigurableWrapper() != null) // null when no config is used
171: {
172: for (final Iterator iter = getRuntimeConfigurableWrapper()
173: .getAttributeMap().keySet().iterator(); iter
174: .hasNext();) {
175: existingProps.add(((String) iter.next()).toLowerCase());
176: }
177: }
178:
179: final IntrospectionHelper ih = IntrospectionHelper.getHelper(
180: getProject(), getClass());
181: for (int i = 0; i < PROPERTIES.length; ++i) {
182: final String propName = PROPERTIES[i];
183: if (!existingProps.contains(propName)) {
184: final String propValue = getProject().getProperty(
185: "wt.config." + propName);
186: if (propValue != null) {
187: LOG.info("Using " + propName
188: + " from project property wt.config."
189: + propName + ": " + propValue);
190: ih.setAttribute(getProject(), this , propName,
191: propValue);
192: }
193: }
194: }
195: }
196:
197: // package protection for testing purposes
198: void prepareResultDir(final File resultDir) {
199: if (isSummary() || isSaveResponse()) {
200: if (resultDir.exists() && !resultDir.isDirectory()) {
201: throw new BuildException(
202: "Result dir is not a directory: "
203: + resultDir.getAbsolutePath());
204: }
205:
206: // compute subdir for this test
207: fWebtestResultDir = computeSubFolder(resultDir);
208: LOG.info("Creating result directory: "
209: + fWebtestResultDir.getAbsolutePath());
210:
211: if (!fWebtestResultDir.mkdirs()) {
212: throw new BuildException(
213: "Failed to create result dir: "
214: + fWebtestResultDir.getAbsolutePath());
215: }
216: } else {
217: LOG
218: .warn("Result dir '"
219: + resultDir.getName()
220: + "' not created (may not be needed), may cause problems if individual steps set save attribute");
221: }
222: }
223:
224: /**
225: * Compute the name of the subfolder for this test
226: * @param _resultDir the "main" result dir
227: * @return the name of the subfolder
228: */
229: protected File computeSubFolder(final File _resultDir) {
230: if (resultFolderIndex == -1)
231: resultFolderIndex = getResultFolderIndex(_resultDir);
232:
233: final String prefix = StringUtils.leftPad(String
234: .valueOf(resultFolderIndex), 3, '0');
235: final String fixedTestName = WordUtils.capitalize(
236: fContext.getWebtest().getName()).replaceAll("\\W", "");
237: final String name = prefix + "_" + fixedTestName;
238:
239: final int dirNameMaxLength = 20;
240: return new File(_resultDir, StringUtils.left(name,
241: dirNameMaxLength));
242: }
243:
244: /**
245: * Sets the index of the result folder. Normally this shouldn't be set from
246: * outside but the new experimental feature "WebTest parallel" currently needs it.
247: * @param index the index
248: */
249: public void setResultFolderIndex(final int index) {
250: resultFolderIndex = index;
251: }
252:
253: /**
254: * Get the index to use as prefix for the dedicated result folder of this test
255: * @param _resultDir the base result directory
256: * @return the index
257: */
258: protected int getResultFolderIndex(final File _resultDir) {
259: int lastIndex = 0;
260: final File[] children = _resultDir.listFiles();
261: if (children != null) // null when _resultDir is not (yet?) a directory
262: {
263: for (int i = 0; i < children.length; i++) {
264: final File f = children[i];
265: if (f.getName().matches("\\d{3}_.*")) {
266: final int index = Integer.parseInt(f.getName()
267: .substring(0, 3));
268: lastIndex = Math.max(lastIndex, index);
269: }
270: }
271: }
272: return lastIndex + 1;
273: }
274:
275: /**
276: * Gets the file where the summary should be written.<p>
277: *
278: * @return <code>null</code> if no resultfile was specified
279: */
280: public File getSummaryFile() {
281: // TODO: remove it!
282: return fSummaryFile;
283: }
284:
285: /**
286: *
287: * @param header
288: * @webtest.nested.parameter
289: * required="no"
290: * description="Specify http headers by name and value"
291: */
292: public void addHeader(final Header header) {
293: fHeaderList.add(header);
294: }
295:
296: public List getHeaderList() {
297: return fHeaderList;
298: }
299:
300: /**
301: *
302: * @param option
303: * @webtest.nested.parameter
304: * required="no"
305: * description="Tweak the underlying web client options/settings"
306: */
307: public void addOption(final Option option) {
308: fOptionList.add(option);
309: }
310:
311: public List getOptionList() {
312: return fOptionList;
313: }
314:
315: public String getBasePath() {
316: return fBasePath;
317: }
318:
319: /**
320: * Gets the User-Agent header to be sent.<p>
321: *
322: * @return <code>null</code> if none has been configured
323: */
324: public String getUserAgent() {
325: LOG.debug("Headers: " + getHeaderList());
326: for (final Iterator iter = getHeaderList().iterator(); iter
327: .hasNext();) {
328: final Header elt = (Header) iter.next();
329: if ("User-Agent".equals(elt.getName())) {
330: LOG.debug("Found User-Agent header: " + elt.getValue());
331: return elt.getValue();
332: }
333: LOG.debug("Not User-Agent header: " + elt.getName());
334: }
335: return null;
336: }
337:
338: /**
339: * Indicates if the default port for the protocol is used (that is port is not needed in the url).<p>
340: */
341: private boolean isDefaultPort() {
342: return (PROTOCOL_HTTP.equals(getProtocol()) && PORT_HTTP == getPort())
343: || (PROTOCOL_HTTPS.equals(getProtocol()) && PORT_HTTPS == getPort());
344: }
345:
346: public String getHost() {
347: return fHost;
348: }
349:
350: public int getPort() {
351: return fPort;
352: }
353:
354: public String getProtocol() {
355: return fProtocol;
356: }
357:
358: /**
359: * This is the configured general result dir.
360: * This value should not be used directly as results will be placed in a sub folder of it in the future
361: * @return the configured result path
362: */
363: File getResultpath() {
364: return fResultPath;
365: }
366:
367: /**
368: * Gets the directory where all artifacts of this specific test should be saved.
369: * This is currently the same than {@link #getResultpath()} but this may change in the future.
370: * @return the folder
371: */
372: public File getWebTestResultDir() {
373: return fWebtestResultDir;
374: }
375:
376: public String getSavePrefix() {
377: return fSavePrefix;
378: }
379:
380: /**
381: * Completes a URL based on configuration values by expanding
382: * where needed from a base URL (protocol and optionally
383: * host:port). If the provided page is complete (already with protocol), it is returned as is.<p>
384: *
385: * @param page the maybe relative target URL
386: * @return the assembled URL
387: */
388: public String getUrlForPage(final String page) {
389: // first test if the page starts with a protocol like "http://", "https://", "file:/"
390: final int index = StringUtils.indexOf(page, "://");
391: if (index > -1
392: && index < 6
393: || (page != null && page.toLowerCase().startsWith(
394: "file:/"))) {
395: return page;
396: } else if (PROTOCOL_FILE.equals(getProtocol())) {
397: return createFileBasedUrl(page);
398: } else {
399: return createNetworkBasedUrl(page);
400: }
401: }
402:
403: private String createFileBasedUrl(final String page) {
404: return getProtocol() + ":"
405: + combineBasePathAndPage(getBasePath(), page);
406: }
407:
408: private String createNetworkBasedUrl(final String page) {
409: final StringBuffer url = new StringBuffer(getProtocol());
410: url.append("://");
411: url.append(getHost());
412: if (!isDefaultPort()) {
413: url.append(":");
414: url.append(getPort());
415: }
416: url.append(combineBasePathAndPage(getBasePath(), page));
417: return url.toString();
418: }
419:
420: private static String combineBasePathAndPage(final String basePath,
421: final String page) {
422: String basePathClean = StringUtils.strip(basePath,
423: URL_SEPARATOR);
424: basePathClean = StringUtils.isEmpty(basePathClean) ? ""
425: : (URL_SEPARATOR + basePathClean);
426: String pageClean = StringUtils.stripStart(page, URL_SEPARATOR);
427: pageClean = StringUtils.isEmpty(pageClean) ? ""
428: : (URL_SEPARATOR + pageClean);
429: return basePathClean + pageClean;
430: }
431:
432: public boolean isSaveResponse() {
433: return fSaveResponse;
434: }
435:
436: public boolean isSummary() {
437: return fSummary;
438: }
439:
440: public void setSavePrefix(String savePrefix) {
441: fSavePrefix = savePrefix;
442: }
443:
444: /**
445: * Defines the constant base path used to construct request URLs,
446: * e.g. "shop" can be considered a basepath in "http://www.myhost.com/shop/productlist"
447: * and "http://www.myhost.com/shop/checkout".<p>
448: *
449: * @param newBasePath the new value
450: */
451: public void setBasepath(final String newBasePath) {
452: fBasePath = newBasePath;
453: }
454:
455: public void setHost(final String newHost) {
456: fHost = newHost;
457: }
458:
459: public void setPort(final int newPort) {
460: fPort = newPort;
461: }
462:
463: public void setProtocol(final String newProtocol) {
464: fProtocol = newProtocol;
465: }
466:
467: public void setResultpath(final File newResultPath) {
468: fResultPath = newResultPath;
469: }
470:
471: public void setSaveresponse(final boolean newSaveResponse) {
472: fSaveResponse = newSaveResponse;
473: }
474:
475: public void setSummary(final boolean newSummary) {
476: fSummary = newSummary;
477: }
478:
479: public void setHaltonerror(final boolean haltOnError) {
480: fHaltOnError = haltOnError;
481: }
482:
483: public void setHaltonfailure(final boolean haltOnFailure) {
484: fHaltOnFailure = haltOnFailure;
485: }
486:
487: public void setErrorProperty(final String errorProperty) {
488: fErrorProperty = errorProperty;
489: }
490:
491: public void setFailureProperty(final String failureProperty) {
492: fFailureProperty = failureProperty;
493: }
494:
495: public String getFailureProperty() {
496: return fFailureProperty;
497: }
498:
499: public String getErrorProperty() {
500: return fErrorProperty;
501: }
502:
503: public String getDefaultPropertyType() {
504: return fDefaultPropertyType;
505: }
506:
507: public void setDefaultPropertyType(final String type) {
508: fDefaultPropertyType = type;
509: }
510:
511: public void setShowhtmlparseroutput(final boolean showParserOutput) {
512: fShowHtmlParserOutput = showParserOutput;
513: }
514:
515: public boolean isShowHtmlParserOutput() {
516: return fShowHtmlParserOutput;
517: }
518:
519: public boolean isHaltOnFailure() {
520: return fHaltOnFailure;
521: }
522:
523: public boolean isHaltOnError() {
524: return fHaltOnError;
525: }
526:
527: public Map getParameterDictionary() {
528: final Map map = new HashMap();
529: map.put("host", getHost());
530: map.put("protocol", getProtocol());
531: map.put("port", String.valueOf(getPort()));
532: map.put("timeout", String.valueOf(getTimeout()));
533: map.put("basepath", getBasePath());
534: map.put("resultpath", getResultpath());
535: map.put("summary", isSummary() ? "yes" : "no");
536: map.put("saveresponse", isSaveResponse() ? "yes" : "no");
537: map.put("saveprefix", getSavePrefix());
538: map.put("haltonerror", isHaltOnError() ? "yes" : "no");
539: map.put("haltonfailure", isHaltOnFailure() ? "yes" : "no");
540: MapUtil.putIfNotNull(map, "errorproperty", getErrorProperty());
541: MapUtil.putIfNotNull(map, "failureproperty",
542: getFailureProperty());
543: MapUtil.putIfNotNull(map, "defaultpropertytype",
544: getDefaultPropertyType());
545: map.put("autorefresh", getAutoRefresh());
546: map.put("showhtmlparseroutput",
547: isShowHtmlParserOutput() ? "yes" : "no");
548: map.put("resultfile", getResultFile());
549:
550: return map;
551: }
552:
553: public String getResultFile() {
554: return fResultFile;
555: }
556:
557: public void setResultfile(final String resultFile) {
558: // fResultFile = resultFile;
559: }
560:
561: public void setPropertyHandler(
562: final IPropertyHandler propertyHandler) {
563: fPropertyHandler = propertyHandler;
564: }
565:
566: public String getExternalProperty(final String name) {
567: if (fPropertyHandler == null) {
568: throw new IllegalStateException(
569: "No property handler configured!");
570: }
571: return fPropertyHandler.getProperty(name);
572: }
573:
574: /**
575: * Returns true if the client should automatically follow page refresh requests.<p>
576: */
577: public String getAutoRefresh() {
578: return fAutoRefresh;
579: }
580:
581: /**
582: * Determines if the client should automatically follow page refresh requests.<p>
583: *
584: * @param str the new refresh value
585: */
586: public void setAutoRefresh(final String str) {
587: fAutoRefresh = str;
588: }
589:
590: /**
591: * Defines the context. This is called to set the context on the task before {@link #execute()}
592: * is called
593: * @param context the context
594: */
595: public void setContext(final Context context) {
596: fContext = context;
597: }
598:
599: /**
600: * Configures the webclient used for the test
601: *
602: */
603: public WebClient createWebClient() {
604: final Configuration cfg = this ;
605: final String strUserAgent = cfg.getUserAgent();
606: final BrowserVersion browserVersion = setupBrowserVersion(strUserAgent);
607: final WebClient webClient = setupWebClient(browserVersion);
608:
609: webClient.setTimeout(getTimeout() * 1000);
610:
611: setupHtmlParser(webClient, cfg);
612: setupRefreshHandler(webClient, cfg); // auto refresh settings
613: webClient.setThrowExceptionOnScriptError(true); // option for this and report js errors?
614: WebClient.setIgnoreOutsideContent(true);
615: setupHttpHeaders(webClient, cfg);
616: setupOptions(webClient, cfg);
617:
618: configurePageCreator(webClient);
619:
620: return webClient;
621: }
622:
623: /**
624: * Configures WebTest's custom page creator able to create PDFPage (as long as this is not integrated
625: * directly into HtmlUnit)
626: */
627: protected void configurePageCreator(final WebClient webClient) {
628: final DefaultPageCreator pageCreator = new DefaultPageCreator() {
629: public Page createPage(final WebResponse webResponse,
630: final WebWindow webWindow) throws IOException {
631: final String contentType = webResponse.getContentType()
632: .toLowerCase();
633:
634: if ("application/pdf".equals(contentType)) {
635: final PDFPage newPage = new PdfBoxPDFPage(
636: webResponse, webWindow);
637: webWindow.setEnclosedPage(newPage);
638: return newPage;
639: } else
640: return super .createPage(webResponse, webWindow);
641: }
642: };
643: webClient.setPageCreator(pageCreator);
644: }
645:
646: private static BrowserVersion setupBrowserVersion(
647: final String strUserAgent) {
648: final BrowserVersion browserVersion;
649:
650: if (strUserAgent == null) {
651: browserVersion = BrowserVersion.INTERNET_EXPLORER_6_0;
652: LOG.info("Surfing with browser "
653: + browserVersion.getUserAgent());
654: } else {
655: // as long as all browser properties are not configurable from the task,
656: // use a "base" browser
657: final BrowserVersion baseBrowser;
658:
659: if (strUserAgent.indexOf("Gecko") != -1) {
660: baseBrowser = BrowserVersion.FIREFOX_2;
661: } else if (strUserAgent.indexOf(BrowserVersion.NETSCAPE) != -1) {
662: baseBrowser = new BrowserVersion(
663: BrowserVersion.NETSCAPE,
664: "5.0 (Windows; en-US)",
665: "Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US;rv:0.9.4.1) Gecko/20020508 Netscape6/6.2.3",
666: "1.2", 6);
667: } else {
668: baseBrowser = BrowserVersion.INTERNET_EXPLORER_6_0;
669: }
670:
671: browserVersion = new BrowserVersion(baseBrowser
672: .getApplicationName(), baseBrowser
673: .getApplicationVersion(), strUserAgent, baseBrowser
674: .getJavaScriptVersion(), baseBrowser
675: .getBrowserVersionNumeric());
676: LOG
677: .info("Using browser version ("
678: + browserVersion.getApplicationName()
679: + ", "
680: + browserVersion.getApplicationVersion()
681: + ", "
682: + strUserAgent
683: + ", "
684: + browserVersion.getJavaScriptVersion()
685: + ", "
686: + browserVersion.getBrowserVersionNumeric()
687: + "). If the javascript support is not as expected, then it's time to go into the sources");
688: }
689: return browserVersion;
690: }
691:
692: // Paul Devine 4/12/2005: adding proxy support (for now only UsernamePasswordCredentials. For a proxy
693: // that does not require authentication the credentials will not be consulted anyway, so it's ok to
694: // leave user/password null or blank in those situations.)
695: // package protected for testing purposes
696: static WebClient setupWebClient(final BrowserVersion browserVersion) {
697: final WebClient webClient;
698: final DefaultCredentialsProvider credentialProvider = new DefaultCredentialsProvider();
699: String proxyHost = System.getProperty("http.proxyHost");
700: if (proxyHost != null && proxyHost.length() > 0) {
701: // the properties are set for instance by org.apache.tools.ant.taskdefs.optional.net.SetProxy
702:
703: // the proxy setting
704: final int proxyPort = Integer.parseInt(System.getProperty(
705: "http.proxyPort", "80"));
706: LOG
707: .info("Configuring proxy from http.proxyHost* system properties: "
708: + proxyHost + ":" + proxyPort);
709: webClient = new WebClient(browserVersion, proxyHost,
710: proxyPort);
711:
712: configureProxy(webClient, credentialProvider);
713: } else {
714: webClient = new WebClient(browserVersion);
715: }
716:
717: webClient.setCredentialsProvider(credentialProvider);
718:
719: return webClient;
720: }
721:
722: /**
723: * Configures the proxy settings from the system properties
724: */
725: static void configureProxy(final WebClient webClient,
726: final DefaultCredentialsProvider credentialProvider) {
727: // the non proxy hosts if any
728: final String nonProxyHostsSetting = System
729: .getProperty("http.nonProxyHosts");
730: LOG
731: .info("Configuring proxy from http.nonProxyHosts system property: "
732: + nonProxyHostsSetting);
733: if (nonProxyHostsSetting != null) {
734: final String[] nonProxyHosts = nonProxyHostsSetting
735: .split("\\|");
736: for (int i = 0; i < nonProxyHosts.length; ++i) {
737: String nonProxyHost = nonProxyHosts[i];
738: nonProxyHost = nonProxyHost.replaceAll("\\.", "\\\\."); // escape "."
739: nonProxyHost = nonProxyHost.replaceAll("\\*", ".*"); // give regex meaning to *
740: LOG.debug("addHostsToProxyBypass: >" + nonProxyHost
741: + "<");
742: webClient.addHostsToProxyBypass(nonProxyHost);
743: }
744: }
745:
746: // does the proxy need authentification?
747: if (System.getProperty("http.proxyUser") != null) {
748: final String proxyUser = System
749: .getProperty("http.proxyUser");
750: final String proxyPassword = System
751: .getProperty("http.proxyPassword");
752: LOG
753: .info("Configuring proxy credentials from http.proxyHost* system properties: "
754: + proxyUser + ", " + proxyPassword);
755: credentialProvider.addProxyCredentials(proxyUser,
756: proxyPassword);
757: }
758: }
759:
760: private static void setupHtmlParser(final WebClient webClient,
761: final Configuration cfg) {// Sets a collector to catch parser warnings in order to report
762: // misformed/invalid HTML in responses
763: if (cfg.isShowHtmlParserOutput()) {
764: webClient
765: .setHTMLParserListener(new HtmlParserMessage.MessageCollector());
766: LOG
767: .debug("Configured a parser listener to collect messages generated while parsing html");
768: } else {
769: LOG
770: .debug("showHtmlParserOutput is off, no listener configured");
771: }
772: }
773:
774: private static void setupRefreshHandler(final WebClient webClient,
775: final Configuration cfg) {
776: final boolean bUseDelay;
777: final boolean bRefreshAll;
778: final int acceptedRefreshDelay;
779: if (cfg.getAutoRefresh().matches("\\d+")) {
780: bUseDelay = true;
781: bRefreshAll = false;
782: acceptedRefreshDelay = Integer.parseInt(cfg
783: .getAutoRefresh());
784: } else {
785: bUseDelay = false;
786: bRefreshAll = Project.toBoolean(cfg.getAutoRefresh());
787: acceptedRefreshDelay = 0;
788: }
789:
790: LOG.debug("Configuring RefreshHandler (refreshAll: "
791: + bRefreshAll + ", useDelay: " + bUseDelay
792: + ", refreshDelay: " + acceptedRefreshDelay);
793: final RefreshHandler refreshHandler = new RefreshHandler() {
794: public void handleRefresh(final Page page, final URL url,
795: final int iTimeBeforeRefresh) throws IOException {
796: final boolean bRefresh = bRefreshAll
797: || (bUseDelay && iTimeBeforeRefresh <= acceptedRefreshDelay);
798: if (bRefresh) {
799: LOG.info("Performing refresh to " + url
800: + " (delay: " + iTimeBeforeRefresh
801: + ") according to configuration");
802: final WebWindow window = page.getEnclosingWindow();
803: if (window == null) {
804: return;
805: }
806: final WebClient client = window.getWebClient();
807: client.getPage(window, new WebRequestSettings(url));
808: } else {
809: LOG.info("no refresh performed to " + url
810: + " (delay: " + iTimeBeforeRefresh
811: + ") according to configuration");
812: }
813: }
814:
815: };
816:
817: webClient.setRefreshHandler(refreshHandler);
818: }
819:
820: /**
821: * Sets the http headers configured through the <header/> subelements of
822: * <config>
823: */
824: private static void setupHttpHeaders(final WebClient webClient,
825: final Configuration cfg) {
826: if (cfg.getHeaderList().size() > 0) {
827: LOG.info("Configuring " + cfg.getHeaderList().size()
828: + " HTTP header field(s)");
829: }
830: for (final Iterator iter = cfg.getHeaderList().iterator(); iter
831: .hasNext();) {
832: final Header header = (Header) iter.next();
833:
834: if ("User-Agent".equals(header.getName())) {
835: LOG
836: .info("Skipped User-Agent header as it has already been configured in the BrowserVersion");
837: } else {
838: webClient.addRequestHeader(header.getName(), header
839: .getValue());
840: LOG.info("Configured header \"" + header.getName()
841: + "\": " + header.getValue());
842: }
843: }
844: }
845:
846: /**
847: * Prepares the underlying browser client using option subelements
848: * in the config. Currently calls setXXX methods in the WebClient
849: * API. See the HtmlUnit JavaDocs for more details.
850: */
851: private static void setupOptions(final WebClient webClient,
852: final Configuration cfg) {
853: final List options = cfg.getOptionList();
854: for (Iterator iter = options.iterator(); iter.hasNext();) {
855: final Option option = (Option) iter.next();
856: boolean found = tryBooleanCallingMethod(option, webClient);
857: if (!found) {
858: found = tryIntCallingMethod(option, webClient);
859: }
860: if (!found) {
861: LOG.warn("Unknown option <" + option.getName()
862: + ">. Ignored.");
863: }
864: }
865: }
866:
867: /**
868: * Invokes a setXXX method in response to an option subelement
869: * in the config named 'XXX'. Currently only boolean values are supported.
870: *
871: * @param option
872: * @param optionObject
873: * @return <code>true</code> if option has been successfully set
874: */
875: private static boolean tryBooleanCallingMethod(final Option option,
876: final Object optionObject) {
877: final Class[] booleanClass = { boolean.class };
878: try {
879: tryCallMethod(optionObject, option, booleanClass,
880: new Object[] { Boolean.valueOf(option.getValue()) });
881: return true;
882: } catch (Exception e) {
883: LOG.info("Exception while trying to set boolean option: "
884: + e.getMessage());
885: return false;
886: }
887: }
888:
889: /**
890: * Invokes a setXXX method in response to an option subelement
891: * in the config named 'XXX'. Currently only boolean values are supported.
892: *
893: * @param option
894: * @param optionObject
895: * @return <code>true</code> if option has been successfully set
896: */
897: private static boolean tryIntCallingMethod(final Option option,
898: final Object optionObject) {
899: final Class[] intClass = { int.class };
900: try {
901: tryCallMethod(optionObject, option, intClass,
902: new Object[] { Integer.valueOf(option.getValue()) });
903: return true;
904: } catch (Exception e) {
905: LOG.info("Exception while trying to set integer option: "
906: + e.getMessage());
907: return false;
908: }
909: }
910:
911: private static void tryCallMethod(final Object optionObject,
912: final Option option, final Class[] typeSpec,
913: final Object[] params) throws Exception {
914: final Method method = optionObject.getClass()
915: .getDeclaredMethod("set" + option.getName(), typeSpec);
916: method.invoke(optionObject, params);
917: LOG.info("set option <" + option.getName() + "> to value <"
918: + option.getValue() + ">");
919: }
920:
921: /**
922: * Gets the timeout in seconds
923: * @return the timeout (default value is {@link #DEFAULT_TIMEOUT}).
924: */
925: public int getTimeout() {
926: return fTimeout;
927: }
928:
929: /**
930: * Sets the timeout
931: * @param timeout the new value (in seconds), set <code>0</code> for no timeout
932: */
933: public void setTimeout(final int timeout) {
934: fTimeout = timeout;
935: }
936:
937: /**
938: * Indicates if WebTest should wait for completion of background js tasks after each step.
939: * This should become true per default once HtmlUnit has the necessary functionality to
940: * control this which is not yet the case with HtmlUnit 1.14.
941: * This feature is intentionally undocumented.
942: * @param b the new value
943: */
944: public void setEasyAjax(final boolean b) {
945: fEasyAjax = b;
946: }
947:
948: public boolean isEasyAjax() {
949: return fEasyAjax;
950: }
951: }
|