diff --git a/config/xsl/loadreport/sections/response-codes.xsl b/config/xsl/loadreport/sections/response-codes.xsl index 250307581..28b528cf2 100644 --- a/config/xsl/loadreport/sections/response-codes.xsl +++ b/config/xsl/loadreport/sections/response-codes.xsl @@ -73,6 +73,14 @@ +
+
+ + charts/ResponseCodesPerSecond.webp + Response Codes Per Second + +
+
diff --git a/doc/internal-doc/api.sig b/doc/internal-doc/api.sig index 114b22145..d73b997a4 100644 --- a/doc/internal-doc/api.sig +++ b/doc/internal-doc/api.sig @@ -1,5 +1,5 @@ #Signature file v4.1 -#Version 8.0.0 +#Version 8.5.0 CLSS public abstract com.xceptance.xlt.api.actions.AbstractAction cons protected init(com.xceptance.xlt.api.actions.AbstractAction,java.lang.String) @@ -2349,6 +2349,12 @@ meth public static <%0 extends java.lang.Object> java.util.List<{%%0}> of({%%0}, meth public void replaceAll(java.util.function.UnaryOperator<{java.util.List%0}>) meth public void sort(java.util.Comparator) +CLSS public abstract interface !annotation org.jspecify.annotations.NullMarked + anno 0 java.lang.annotation.Documented() + anno 0 java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy value=RUNTIME) + anno 0 java.lang.annotation.Target(java.lang.annotation.ElementType[] value=[MODULE, PACKAGE, TYPE, METHOD, CONSTRUCTOR]) +intf java.lang.annotation.Annotation + CLSS public abstract interface !annotation org.junit.runner.RunWith anno 0 java.lang.annotation.Inherited() anno 0 java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy value=RUNTIME) @@ -2376,43 +2382,21 @@ meth public abstract void downloadFile(java.lang.String,java.nio.file.Path) thro meth public void requireDownloadsEnabled(org.openqa.selenium.Capabilities) CLSS public abstract interface org.openqa.selenium.JavascriptExecutor -meth public !varargs java.lang.Object executeScript(org.openqa.selenium.ScriptKey,java.lang.Object[]) -meth public abstract !varargs java.lang.Object executeAsyncScript(java.lang.String,java.lang.Object[]) -meth public abstract !varargs java.lang.Object executeScript(java.lang.String,java.lang.Object[]) -meth public java.util.Set getPinnedScripts() -meth public org.openqa.selenium.ScriptKey pin(java.lang.String) -meth public void unpin(org.openqa.selenium.ScriptKey) CLSS public abstract interface org.openqa.selenium.PrintsPage meth public abstract org.openqa.selenium.Pdf print(org.openqa.selenium.print.PrintOptions) CLSS public abstract interface org.openqa.selenium.SearchContext + anno 0 org.jspecify.annotations.NullMarked() meth public abstract java.util.List findElements(org.openqa.selenium.By) meth public abstract org.openqa.selenium.WebElement findElement(org.openqa.selenium.By) CLSS public abstract interface org.openqa.selenium.TakesScreenshot + anno 0 org.jspecify.annotations.NullMarked() meth public abstract <%0 extends java.lang.Object> {%%0} getScreenshotAs(org.openqa.selenium.OutputType<{%%0}>) CLSS public abstract interface org.openqa.selenium.WebDriver -innr public abstract interface static Navigation -innr public abstract interface static Options -innr public abstract interface static TargetLocator -innr public abstract interface static Timeouts -innr public abstract interface static Window intf org.openqa.selenium.SearchContext -meth public abstract java.lang.String getCurrentUrl() -meth public abstract java.lang.String getPageSource() -meth public abstract java.lang.String getTitle() -meth public abstract java.lang.String getWindowHandle() -meth public abstract java.util.List findElements(org.openqa.selenium.By) -meth public abstract java.util.Set getWindowHandles() -meth public abstract org.openqa.selenium.WebDriver$Navigation navigate() -meth public abstract org.openqa.selenium.WebDriver$Options manage() -meth public abstract org.openqa.selenium.WebDriver$TargetLocator switchTo() -meth public abstract org.openqa.selenium.WebElement findElement(org.openqa.selenium.By) -meth public abstract void close() -meth public abstract void get(java.lang.String) -meth public abstract void quit() CLSS public abstract interface org.openqa.selenium.bidi.HasBiDi meth public abstract java.util.Optional maybeGetBiDi() @@ -2609,6 +2593,7 @@ cons public init(java.net.URL,org.openqa.selenium.Capabilities,boolean) cons public init(org.openqa.selenium.Capabilities) cons public init(org.openqa.selenium.Capabilities,boolean) cons public init(org.openqa.selenium.remote.CommandExecutor,org.openqa.selenium.Capabilities) +fld protected org.openqa.selenium.Capabilities capabilities innr protected RemoteTargetLocator innr protected RemoteWebDriverOptions innr public final static !enum When @@ -2654,6 +2639,8 @@ meth public org.openqa.selenium.federatedcredentialmanagement.FederatedCredentia meth public org.openqa.selenium.remote.CommandExecutor getCommandExecutor() meth public org.openqa.selenium.remote.ErrorHandler getErrorHandler() meth public org.openqa.selenium.remote.FileDetector getFileDetector() +meth public org.openqa.selenium.remote.Network network() +meth public org.openqa.selenium.remote.Script script() meth public org.openqa.selenium.remote.SessionId getSessionId() meth public org.openqa.selenium.virtualauthenticator.VirtualAuthenticator addVirtualAuthenticator(org.openqa.selenium.virtualauthenticator.VirtualAuthenticatorOptions) meth public static org.openqa.selenium.remote.RemoteWebDriverBuilder builder() @@ -2672,7 +2659,7 @@ meth public void setErrorHandler(org.openqa.selenium.remote.ErrorHandler) meth public void setFileDetector(org.openqa.selenium.remote.FileDetector) meth public void setLogLevel(java.util.logging.Level) supr java.lang.Object -hfds LOG,capabilities,converter,elementLocation,errorHandler,executeMethod,executor,fileDetector,level,localLogs,remoteLogs,sessionId +hfds LOG,WEBDRIVER_REMOTE_ENABLE_TRACING,converter,elementLocation,errorHandler,executeMethod,executor,fileDetector,level,localLogs,remoteLogs,remoteNetwork,remoteScript,sessionId hcls RemoteAlert,RemoteNavigation,RemoteVirtualAuthenticator CLSS public abstract interface org.openqa.selenium.virtualauthenticator.HasVirtualAuthenticator diff --git a/pom.xml b/pom.xml index a05b1dcde..17cf159be 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.xceptance xlt - 8.4.1 + 8.5.0 jar XLT @@ -71,9 +71,9 @@ 9.4.53.v20231009 2.22.1 2.0.9 - 4.17.0 + 4.26.0 4.17.0 - 1.40.0 + 1.43.0 Copyright (c) ${project.inceptionYear}-2024 ${project.organization.name} @@ -402,13 +402,13 @@ net.bytebuddy byte-buddy - 1.14.11 + 1.15.7 com.google.guava guava - 33.0.0-jre + 33.3.1-jre diff --git a/src/main/java/com/xceptance/xlt/engine/XltWebClient.java b/src/main/java/com/xceptance/xlt/engine/XltWebClient.java index 49c0f10be..08cc65307 100644 --- a/src/main/java/com/xceptance/xlt/engine/XltWebClient.java +++ b/src/main/java/com/xceptance/xlt/engine/XltWebClient.java @@ -314,38 +314,9 @@ public XltWebClient(final BrowserVersion browserVersion, final boolean javaScrip cssMode = CssMode.getMode(props.getProperty("com.xceptance.xlt.css.download.images")); // JavaScript - if (javaScriptEngineEnabled) - { - // setup JavaScript engine - int optimizationLevel = props.getProperty("com.xceptance.xlt.js.compiler.optimizationLevel", -1); - if (optimizationLevel < -1 || optimizationLevel > 9) - { - XltLogger.runTimeLogger.warn("Property 'com.xceptance.xlt.js.compiler.optimizationLevel' is set to an invalid value. Will use -1 instead."); - optimizationLevel = -1; - } - - final boolean takeMeasurements = props.getProperty("com.xceptance.xlt.js.takeMeasurements", false); - - setJavaScriptEngine(new XltJavaScriptEngine(this, optimizationLevel, takeMeasurements)); - getOptions().setJavaScriptEnabled(props.getProperty("com.xceptance.xlt.javaScriptEnabled", false)); - getOptions().setThrowExceptionOnScriptError(props.getProperty("com.xceptance.xlt.stopTestOnJavaScriptErrors", false)); - - // setup JavaScript debugger - if (props.getProperty("com.xceptance.xlt.js.debugger.enabled", false)) - { - setJavaScriptDebuggerEnabled(true); - - // create JS beautifying response processor only when needed - if (props.getProperty("com.xceptance.xlt.js.debugger.beautifyDownloadedJavaScript", true)) - { - jsBeautifier = new JSBeautifingResponseProcessor(); - } - } - } - else - { - getOptions().setJavaScriptEnabled(false); - } + getOptions().setJavaScriptEnabled(props.getProperty("com.xceptance.xlt.javaScriptEnabled", false)); + getOptions().setThrowExceptionOnScriptError(props.getProperty("com.xceptance.xlt.stopTestOnJavaScriptErrors", false)); + configureJavaScriptEngine(props); // default user authentication final String userName = props.getProperty("com.xceptance.xlt.auth.userName"); @@ -448,36 +419,30 @@ public XltWebClient(final BrowserVersion browserVersion, final boolean javaScrip getOptions().setHistorySizeLimit(historySizeLimit); // set web connection - final WebConnection underlyingWebConnection; - if (props.getProperty("com.xceptance.xlt.http.offline", false)) - { - // we are in offline mode and return fixed responses - underlyingWebConnection = new XltOfflineWebConnection(); - } - else - { - final String client = props.getProperty("com.xceptance.xlt.http.client"); - final boolean collectTargetIpAddress = ((XltPropertiesImpl) props).collectUsedIpAddress(); - if ("okhttp3".equals(client)) - { - final boolean http2Enabled = props.getProperty("com.xceptance.xlt.http.client.okhttp3.http2Enabled", true); - underlyingWebConnection = new OkHttp3WebConnection(this, http2Enabled, collectTargetIpAddress); - } - else - { - // the default connection - underlyingWebConnection = new XltApacheHttpWebConnection(this, collectTargetIpAddress); - } - } + configureWebConnection(props); - XltLogger.runTimeLogger.debug("Using web connection class: " + underlyingWebConnection.getClass().getName()); + // load key/trust material for client/server authentication + configureKeyStore(props); + configureTrustStore(props); + } - final XltHttpWebConnection connection = new XltHttpWebConnection(this, underlyingWebConnection); - setWebConnection(connection); + /** + * {@inheritDoc} + */ + @Override + public void reset() + { + super.reset(); - // load key/trust material for client/server authentication - setUpKeyStore(props); - setUpTrustStore(props); + final XltProperties props = XltProperties.getInstance(); + + // create a new instance of "our" JavaScript engine if needed + configureJavaScriptEngine(props); + + // create a new instance of "our" web connection + configureWebConnection(props); + + pageLocalCache.clear(); } /** @@ -2059,6 +2024,78 @@ else if (s.equals("resync")) } } + /** + * Sets up the JavaScript engine to be used by this web client. + * + * @param props + * the configuration + */ + private void configureJavaScriptEngine(final XltProperties props) + { + if (isJavaScriptEngineEnabled()) + { + // setup JavaScript engine + int optimizationLevel = props.getProperty("com.xceptance.xlt.js.compiler.optimizationLevel", -1); + if (optimizationLevel < -1 || optimizationLevel > 9) + { + XltLogger.runTimeLogger.warn("Property 'com.xceptance.xlt.js.compiler.optimizationLevel' is set to an invalid value. Will use -1 instead."); + optimizationLevel = -1; + } + + final boolean takeMeasurements = props.getProperty("com.xceptance.xlt.js.takeMeasurements", false); + + setJavaScriptEngine(new XltJavaScriptEngine(this, optimizationLevel, takeMeasurements)); + + // setup JavaScript debugger + if (props.getProperty("com.xceptance.xlt.js.debugger.enabled", false)) + { + setJavaScriptDebuggerEnabled(true); + + // create JS beautifying response processor only when needed + if (props.getProperty("com.xceptance.xlt.js.debugger.beautifyDownloadedJavaScript", true)) + { + jsBeautifier = new JSBeautifingResponseProcessor(); + } + } + } + } + + /** + * Sets up the web connection to be used by this web client. + * + * @param props + * the configuration + */ + private void configureWebConnection(final XltProperties props) + { + final WebConnection underlyingWebConnection; + if (props.getProperty("com.xceptance.xlt.http.offline", false)) + { + // we are in offline mode and return fixed responses + underlyingWebConnection = new XltOfflineWebConnection(); + } + else + { + final String client = props.getProperty("com.xceptance.xlt.http.client"); + final boolean collectTargetIpAddress = ((XltPropertiesImpl) props).collectUsedIpAddress(); + if ("okhttp3".equals(client)) + { + final boolean http2Enabled = props.getProperty("com.xceptance.xlt.http.client.okhttp3.http2Enabled", true); + underlyingWebConnection = new OkHttp3WebConnection(this, http2Enabled, collectTargetIpAddress); + } + else + { + // the default connection + underlyingWebConnection = new XltApacheHttpWebConnection(this, collectTargetIpAddress); + } + } + + XltLogger.runTimeLogger.debug("Using web connection class: " + underlyingWebConnection.getClass().getName()); + + final XltHttpWebConnection connection = new XltHttpWebConnection(this, underlyingWebConnection); + setWebConnection(connection); + } + /** * Sets up the key store with the client key to be used by this web client. The store is read from a file specified * in the test suite configuration. @@ -2066,7 +2103,7 @@ else if (s.equals("resync")) * @param props * the configuration */ - private void setUpKeyStore(final XltProperties props) + private void configureKeyStore(final XltProperties props) { final String storeFilePath = props.getProperty("com.xceptance.xlt.tls.keyStore.file"); if (StringUtils.isNotBlank(storeFilePath)) @@ -2086,7 +2123,7 @@ private void setUpKeyStore(final XltProperties props) * @param props * the configuration */ - private void setUpTrustStore(final XltProperties props) + private void configureTrustStore(final XltProperties props) { final String storeFilePath = props.getProperty("com.xceptance.xlt.tls.trustStore.file"); if (StringUtils.isNotBlank(storeFilePath)) diff --git a/src/main/java/com/xceptance/xlt/report/providers/ResponseCodesReportProvider.java b/src/main/java/com/xceptance/xlt/report/providers/ResponseCodesReportProvider.java index 0015d5563..15f178a83 100644 --- a/src/main/java/com/xceptance/xlt/report/providers/ResponseCodesReportProvider.java +++ b/src/main/java/com/xceptance/xlt/report/providers/ResponseCodesReportProvider.java @@ -15,50 +15,71 @@ */ package com.xceptance.xlt.report.providers; +import java.awt.Color; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; +import java.util.Map.Entry; +import java.util.TreeMap; import org.apache.http.impl.EnglishReasonPhraseCatalog; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.LegendItem; +import org.jfree.chart.LegendItemCollection; +import org.jfree.chart.axis.NumberAxis; +import org.jfree.chart.axis.ValueAxis; +import org.jfree.chart.plot.CombinedDomainXYPlot; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.ui.RectangleInsets; +import org.jfree.data.time.TimeSeries; +import org.jfree.data.time.TimeSeriesCollection; import com.xceptance.xlt.api.engine.Data; import com.xceptance.xlt.api.engine.RequestData; import com.xceptance.xlt.api.report.AbstractReportProvider; +import com.xceptance.xlt.report.util.JFreeChartUtils; +import com.xceptance.xlt.report.util.TaskManager; +import com.xceptance.xlt.report.util.ValueSet; /** - * + * */ public class ResponseCodesReportProvider extends AbstractReportProvider { /** - * A mapping from response codes to their corresponding {@link ResponseCodeReport} objects. + * The colors for each response code group (0xx, 1xx, 2xx, 3xx, 4xx, 5xx). */ - private final Map responseCodeReports = new HashMap(); + private static final Color[] COLORS = + { + new Color(0xEE5A2A), // 0xx - red-orange + new Color(0x3BACF0), // 1xx - blue + new Color(0x3BB44A), // 2xx - green + new Color(0xD0D0D0), // 3xx - gray + JFreeChartUtils.COLOR_EVENT, // 4xx - orange + JFreeChartUtils.COLOR_ERROR // 5xx - red + }; /** - * {@inheritDoc} + * A mapping from response codes to their corresponding {@link ResponseCodeReport} objects. */ - @Override - public Object createReportFragment() - { - final ResponseCodesReport report = new ResponseCodesReport(); - - report.responseCodes = new ArrayList(responseCodeReports.values()); + private final Map responseCodeReports = new HashMap<>(); - return report; - } + /** + * A mapping from response codes to their corresponding {@link ValueSet} objects. + */ + private final Map responseCodeValueSets = new TreeMap<>(); /** * {@inheritDoc} */ @Override - public void processDataRecord(final Data stat) + public void processDataRecord(final Data data) { - if (stat instanceof RequestData) + if (data instanceof RequestData) { - final RequestData reqStats = (RequestData) stat; + final RequestData reqData = (RequestData) data; - final int code = reqStats.getResponseCode(); + final int code = reqData.getResponseCode(); ResponseCodeReport responseCodeReport = responseCodeReports.get(code); if (responseCodeReport == null) @@ -71,12 +92,19 @@ public void processDataRecord(final Data stat) } responseCodeReport.count++; + + // track response code occurrences over time, but only in the HTTP response code range plus 0xx + if (code >= 0 && code <= 599) + { + final ValueSet responseCodeValueSet = responseCodeValueSets.computeIfAbsent(code, (__) -> new ValueSet()); + responseCodeValueSet.addOrUpdateValue(reqData.getEndTime(), 1); + } } } /** * Returns the corresponding status text for the given HTTP status code. - * + * * @param statusCode * the status code * @return the status text @@ -107,4 +135,130 @@ private String getStatusText(final int statusCode) return statusText; } + + /** + * {@inheritDoc} + */ + @Override + public Object createReportFragment() + { + final ResponseCodesReport report = new ResponseCodesReport(); + + report.responseCodes = new ArrayList<>(responseCodeReports.values()); + + // create the charts if needed + if (getConfiguration().shouldChartsGenerated()) + { + // create charts asynchronously + final TaskManager taskManager = TaskManager.getInstance(); + + taskManager.addTask(() -> saveResponseCodesChart("Response Codes Per Second", "ResponseCodesPerSecond", responseCodeValueSets)); + } + + return report; + } + + /** + * Creates a chart from the encountered response codes. The chart contains a separate plot for each response code + * that shows how the occurrences of that code are distributed over time. The chart is generated to the charts + * directory. + */ + private void saveResponseCodesChart(final String chartTitle, final String chartFileName, + final Map responseCodeValueSets) + { + final JFreeChart chart = createResponseCodesChart(chartTitle, responseCodeValueSets); + + // size the response codes chart according to the number of response codes encountered + final int height = responseCodeValueSets.size() * 75 + 100; + + JFreeChartUtils.saveChart(chart, chartFileName, getConfiguration().getChartDirectory(), getConfiguration().getChartWidth(), height); + } + + /** + * Creates a chart from the encountered response codes. The chart contains a separate plot for each response code + * that shows how the occurrences of that code are distributed over time. + */ + private JFreeChart createResponseCodesChart(final String chartTitle, final Map responseCodeValueSets) + { + final Map responseCodeGroups = new TreeMap<>(); + + // create the combined plot + final CombinedDomainXYPlot combinedPlot = JFreeChartUtils.createCombinedPlot(getConfiguration().getChartStartTime(), + getConfiguration().getChartEndTime()); + + // add a sub plot for each response code to the combined plot + for (final Entry entry : responseCodeValueSets.entrySet()) + { + final int responseCode = entry.getKey(); + final ValueSet responseCodeValueSet = entry.getValue(); + + // determine response code group and color + final String responseCodeGroup = determineResponseCodeGroup(responseCode); + final Color responseCodeColor = determineResponseCodeColor(responseCode); + + // remember group/color for creating the legend later on + responseCodeGroups.put(responseCodeGroup, responseCodeColor); + + // create a standard bar sub plot + final TimeSeries responseCodeTimeSeries = JFreeChartUtils.toStandardTimeSeries(responseCodeValueSet.toMinMaxValueSet(getConfiguration().getChartWidth()), + responseCodeGroup); + final XYPlot responseCodePlot = JFreeChartUtils.createBarPlot(new TimeSeriesCollection(responseCodeTimeSeries), null, "", + responseCodeColor); + + // extend the standard bar plot with a 2nd y-axis to the right that only serves to display the response + // code, which this plot represents, prominently + final NumberAxis fakeAxis = new NumberAxis(String.valueOf(responseCode)); + fakeAxis.setTickMarksVisible(false); + fakeAxis.setTickLabelsVisible(false); + fakeAxis.setLabelAngle(4.71); // 270° + fakeAxis.setLabelInsets(new RectangleInsets(0.0, 8.0, 0.0, 0.0)); + + final ValueAxis defaultAxis = responseCodePlot.getRangeAxis(); + + responseCodePlot.setRangeAxes(new ValueAxis[] + { + defaultAxis, fakeAxis + }); + + // add the sub plot to the combined plot + combinedPlot.add(responseCodePlot, 1); + } + + // overwrite the default legend (one legend item per series) with a custom legend (one legend item per group) + final LegendItemCollection legendItems = new LegendItemCollection(); + for (final Entry responseCodeGroupEntry : responseCodeGroups.entrySet()) + { + final String responseCodeGroup = responseCodeGroupEntry.getKey(); + final Color responseCodeColor = responseCodeGroupEntry.getValue(); + + legendItems.add(new LegendItem(responseCodeGroup, responseCodeColor)); + } + + combinedPlot.setFixedLegendItems(legendItems); + + // finally create the chart + final JFreeChart jfreechart = JFreeChartUtils.createChart(chartTitle, combinedPlot); + + return jfreechart; + } + + /** + * Derives the response code color from the given response code. + */ + private static Color determineResponseCodeColor(final int responseCode) + { + final int group = responseCode / 100; + + return (group >= 0 && group <= 5) ? COLORS[group] : null; + } + + /** + * Derives the response code group (e.g. "5xx") from the given response code. + */ + private static String determineResponseCodeGroup(final int responseCode) + { + final int group = responseCode / 100; + + return (group >= 0 && group <= 5) ? String.valueOf(group) + "xx" : "Other"; + } } diff --git a/src/main/java/com/xceptance/xlt/report/util/JFreeChartUtils.java b/src/main/java/com/xceptance/xlt/report/util/JFreeChartUtils.java index e675bc708..229d00c4e 100644 --- a/src/main/java/com/xceptance/xlt/report/util/JFreeChartUtils.java +++ b/src/main/java/com/xceptance/xlt/report/util/JFreeChartUtils.java @@ -955,7 +955,7 @@ public static TimeSeries createMovingAverageTimeSeries(final TimeSeries series, */ public static NumberAxis createNumberAxis(String axisTitle) { - if (StringUtils.isBlank(axisTitle)) + if (axisTitle == null) { axisTitle = DEFAULT_VALUE_AXIS_TITLE; } diff --git a/src/test/java/com/xceptance/xlt/engine/XltWebClientTest.java b/src/test/java/com/xceptance/xlt/engine/XltWebClientTest.java index bbf5660ce..74939ca05 100644 --- a/src/test/java/com/xceptance/xlt/engine/XltWebClientTest.java +++ b/src/test/java/com/xceptance/xlt/engine/XltWebClientTest.java @@ -22,7 +22,9 @@ import org.htmlunit.BrowserVersion; import org.htmlunit.MockWebConnection; +import org.htmlunit.WebConnection; import org.htmlunit.WebResponse; +import org.htmlunit.javascript.AbstractJavaScriptEngine; import org.junit.AfterClass; import org.junit.Assert; import org.junit.Before; @@ -203,16 +205,42 @@ private void checkBrowserVersion(final String key, final BrowserVersion expected } } + @Test + public void testReset() + { + try (final XltWebClient webClient = new XltWebClient(BrowserVersion.BEST_SUPPORTED, true)) + { + // prevalidation + final WebConnection webConnection = webClient.getWebConnection(); + final AbstractJavaScriptEngine javaScriptEngine = webClient.getJavaScriptEngine(); + + Assert.assertEquals("Unexpected WebConnection class", XltHttpWebConnection.class, webConnection.getClass()); + Assert.assertEquals("Unexpected JavaScriptEngine class", XltJavaScriptEngine.class, javaScriptEngine.getClass()); + + // reset + webClient.reset(); + + // validate that web connection and javascript engine are still XLT classes, but different objects + final WebConnection newWebConnection = webClient.getWebConnection(); + final AbstractJavaScriptEngine newJavaScriptEngine = webClient.getJavaScriptEngine(); + + Assert.assertEquals("Unexpected WebConnection class", XltHttpWebConnection.class, newWebConnection.getClass()); + Assert.assertEquals("Unexpected JavaScriptEngine class", XltJavaScriptEngine.class, newJavaScriptEngine.getClass()); + + Assert.assertNotEquals("WebConnection is the same as before", webConnection, newWebConnection); + Assert.assertNotEquals("JavaScriptEngine is the same as before", javaScriptEngine, newJavaScriptEngine); + } + } + /** * Response processor which simple stores the request URLs. */ static class URLCollector implements ResponseProcessor { /** - * Collected URLs. - * This has to be a synchronized set because some of the processing runs in another thread and hence - * we might experiencene false sharing otherwise, mainly because a response processor is not designed - * to be a data collector + * Collected URLs. This has to be a synchronized set because some of the processing runs in another thread and + * hence we might experiencene false sharing otherwise, mainly because a response processor is not designed to + * be a data collector */ private final Set urls = Collections.synchronizedSet(new HashSet());