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 super {java.util.List%0}>)
+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());