diff --git a/bin/jmeter.properties b/bin/jmeter.properties index dd4dd7eaacd..0c8fcd712e2 100644 --- a/bin/jmeter.properties +++ b/bin/jmeter.properties @@ -1217,6 +1217,15 @@ view.results.tree.renderers_order=.RenderAsText,.RenderAsRegexp,.RenderAsBoundar # Set to 0 to store all results (might consume a lot of memory) #view.results.tree.max_results=500 +# Set to true to enable by default the checkbox for auto scrolling. +#view.results.tree.autoscroll=true + +# Set to true to enable by default the checkbox for stop auto scrolling on 1st error. +#view.results.tree.autostop=true + +# Default width of the scroll view +#view.results.tree.width=250 + # Maximum size of Document that can be parsed by Tika engine; default=10 * 1024 * 1024 (10 MB) # Set to 0 to disable the size check #document.max_size=0 diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/SamplerResultTab.java b/src/components/src/main/java/org/apache/jmeter/visualizers/SamplerResultTab.java index edb81c9cdf8..f9528d53c13 100644 --- a/src/components/src/main/java/org/apache/jmeter/visualizers/SamplerResultTab.java +++ b/src/components/src/main/java/org/apache/jmeter/visualizers/SamplerResultTab.java @@ -87,7 +87,7 @@ public abstract class SamplerResultTab implements ResultRenderer { private static final Logger LOGGER = LoggerFactory.getLogger(SamplerResultTab.class); // N.B. these are not multi-threaded, so don't make it static private final DateTimeFormatter dateFormat = DateTimeFormatter - .ofPattern("yyyy-MM-dd HH:mm:ss z") // ISO format $NON-NLS-1$ + .ofPattern("yyyy-MM-dd HH:mm:ss.SSS O") // ISO format $NON-NLS-1$ .withZone(ZoneId.systemDefault()); private static final String NL = "\n"; // $NON-NLS-1$ @@ -265,6 +265,10 @@ public void setupTabPane() { .append(JMeterUtils .getResString("view_results_thread_name")).append(SPACE) //$NON-NLS-1$ .append(sampleResult.getThreadName()).append(NL); + statsBuff + .append(JMeterUtils + .getResString("view_results_sample_name")) //$NON-NLS-1$ + .append(sampleResult.getSampleLabel()).append(NL); String startTime = dateFormat .format(Instant.ofEpochMilli(sampleResult.getStartTime())); statsBuff @@ -363,6 +367,10 @@ public void setupTabPane() { resultModel.addRow(new RowResult( JMeterUtils.getParsedLabel("view_results_thread_name"), //$NON-NLS-1$ sampleResult.getThreadName())); + resultModel.addRow(new RowResult( + JMeterUtils.getParsedLabel( + "view_results_sample_name"), //$NON-NLS-1$ + sampleResult.getSampleLabel())); resultModel.addRow(new RowResult( JMeterUtils.getParsedLabel("view_results_sample_start"), //$NON-NLS-1$ startTime)); diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/ViewResultsFullVisualizer.java b/src/components/src/main/java/org/apache/jmeter/visualizers/ViewResultsFullVisualizer.java index dd65bc648c1..37fdc7ca1b7 100644 --- a/src/components/src/main/java/org/apache/jmeter/visualizers/ViewResultsFullVisualizer.java +++ b/src/components/src/main/java/org/apache/jmeter/visualizers/ViewResultsFullVisualizer.java @@ -21,6 +21,8 @@ import java.awt.Color; import java.awt.Component; import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; @@ -43,8 +45,10 @@ import javax.swing.ComboBoxModel; import javax.swing.DefaultComboBoxModel; import javax.swing.ImageIcon; +import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; +import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTabbedPane; @@ -116,6 +120,15 @@ public class ViewResultsFullVisualizer extends AbstractVisualizer private static final String VIEWERS_ORDER = JMeterUtils.getPropDefault("view.results.tree.renderers_order", ""); // $NON-NLS-1$ //$NON-NLS-2$ + //default scroll checkbox status + private static final boolean SCROLL_CHECKBOX = JMeterUtils.getPropDefault("view.results.tree.autoscroll", true); + + //default uncheck if failed scroll checkbox status + private static final boolean SCROLL_STOP_CHECKBOX = JMeterUtils.getPropDefault("view.results.tree.autostop", true); + + // default tree scroll width + private static final int SCROLL_WIDTH = JMeterUtils.getPropDefault("view.results.tree.width", 300); // $NON-NLS-1$ + private static final int REFRESH_PERIOD = JMeterUtils.getPropDefault("jmeter.gui.refresh_period", 500); private static final ImageIcon imageSuccess = JMeterUtils.getImage( @@ -138,6 +151,7 @@ public class ViewResultsFullVisualizer extends AbstractVisualizer private Object resultsObject = null; private TreeSelectionEvent lastSelectionEvent; private JCheckBox autoScrollCB; + private JCheckBox scrollStopCB; private final Queue buffer = new ArrayDeque<>(); private final int maxResults; private boolean dataChanged; @@ -161,6 +175,11 @@ public void add(final SampleResult sample) { } buffer.add(sample); dataChanged = true; + if (!"true".equals(System.getProperty("java.awt.headless"))) { + if (!sample.isSuccessful() && autoScrollCB.isSelected() && scrollStopCB.isSelected()) { + autoScrollCB.setSelected(false); + } + } } } @@ -219,6 +238,7 @@ private void updateGui() { if (selectedPath != null) { jTree.setSelectionPath(selectedPath); } + if (autoScrollCB.isSelected() && root.getChildCount() > 1) { jTree.scrollPathToVisible(new TreePath(new Object[] { root, treeModel.getChild(root, root.getChildCount() - 1) })); @@ -328,6 +348,8 @@ public void clearData() { } resultsRender.clearData(); resultsObject = null; + autoScrollCB.setSelected(SCROLL_CHECKBOX); + scrollStopCB.setSelected(SCROLL_STOP_CHECKBOX); } /** {@inheritDoc} */ @@ -390,6 +412,44 @@ private void valueChanged(TreeSelectionEvent e, boolean forceRendering) { } Object userObject = node.getUserObject(); resultsRender.setSamplerResult(userObject); + + // Add Show Current Node button to the tab pane + if (rightSide.getTabCount() > 0) { + Component firstTab = rightSide.getComponentAt(0); + if (firstTab instanceof JPanel samplerResultTab) { + samplerResultTab = (JPanel) firstTab; + // Add button at the top of the sampler result panel + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + JButton nodeInfoButton = new JButton(JMeterUtils.getResString("show_current_node")); // $NON-NLS-1$ + nodeInfoButton.addActionListener(e2 -> { + // Get the currently selected node when button is clicked + DefaultMutableTreeNode currentNode = (DefaultMutableTreeNode) jTree.getLastSelectedPathComponent(); + if (currentNode != null) { + TreePath path = new TreePath(currentNode.getPath()); + // First expand the path to make sure all parent nodes are visible + jTree.expandPath(path); + // Get the bounds before scrolling + Rectangle bounds = jTree.getPathBounds(path); + if (bounds != null) { + // Get the visible rectangle + Rectangle visible = jTree.getVisibleRect(); + // Calculate the new viewport position to center the node + int centerY = Math.max(0, bounds.y - (visible.height - bounds.height) / 2); + // Create rectangle that will center the node + Rectangle scrollTo = new Rectangle(0, centerY, 1, visible.height); + jTree.scrollRectToVisible(scrollTo); + // Ensure the node is selected and visible + jTree.setSelectionPath(path); + jTree.repaint(); + } + } + }); + buttonPanel.add(nodeInfoButton); + samplerResultTab.add(buttonPanel, BorderLayout.NORTH); + samplerResultTab.revalidate(); + } + } + resultsRender.setupTabPane(); // Processes Assertions // display a SampleResult if (userObject instanceof SampleResult sampleResult) { @@ -429,15 +489,40 @@ private synchronized Component createLeftPanel() { jTree.setRootVisible(false); jTree.setShowsRootHandles(true); JScrollPane treePane = new JScrollPane(jTree); - treePane.setPreferredSize(new Dimension(200, 300)); VerticalPanel leftPane = new VerticalPanel(); - leftPane.add(treePane, BorderLayout.CENTER); - leftPane.add(createComboRender(), BorderLayout.NORTH); + + // Add controls panel at the top + JPanel controlsPane = new JPanel(new FlowLayout(FlowLayout.LEFT)); + controlsPane.add(createComboRender()); + leftPane.add(controlsPane, BorderLayout.NORTH); + + // Add tree panel in the center with proper sizing + JPanel treePanel = new JPanel(new BorderLayout()); + treePanel.add(treePane, BorderLayout.CENTER); + leftPane.add(treePanel, BorderLayout.CENTER); + + // Create bottom panel with checkbox + JPanel bottomPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 0)); + + // Add autoscroll checkbox autoScrollCB = new JCheckBox(JMeterUtils.getResString("view_results_autoscroll")); // $NON-NLS-1$ - autoScrollCB.setSelected(false); + autoScrollCB.setSelected(SCROLL_CHECKBOX); autoScrollCB.addItemListener(this); - leftPane.add(autoScrollCB, BorderLayout.SOUTH); + bottomPanel.add(autoScrollCB); + + // Add autoscroll checkbox + scrollStopCB = new JCheckBox(JMeterUtils.getResString("view_results_scrollstop")); // $NON-NLS-1$ + scrollStopCB.setSelected(SCROLL_STOP_CHECKBOX); + scrollStopCB.addItemListener(this); + bottomPanel.add(scrollStopCB); + + leftPane.add(bottomPanel, BorderLayout.SOUTH); + + // Ensure the left pane maintains its size during window resizing + leftPane.setMinimumSize(new Dimension(SCROLL_WIDTH, 0)); + leftPane.setPreferredSize(new Dimension(SCROLL_WIDTH, 0)); + return leftPane; } @@ -615,4 +700,9 @@ public Component getTreeCellRendererComponent(JTree tree, Object value, public void itemStateChanged(ItemEvent e) { // NOOP state is held by component } + + @Override + public String[] getFileExts() { + return new String[] { ".jtl" }; + } } diff --git a/src/core/src/main/java/org/apache/jmeter/visualizers/gui/AbstractVisualizer.java b/src/core/src/main/java/org/apache/jmeter/visualizers/gui/AbstractVisualizer.java index c44dd9d58ca..645faf4a7ea 100644 --- a/src/core/src/main/java/org/apache/jmeter/visualizers/gui/AbstractVisualizer.java +++ b/src/core/src/main/java/org/apache/jmeter/visualizers/gui/AbstractVisualizer.java @@ -104,7 +104,7 @@ public abstract class AbstractVisualizer /** Logging. */ private static final Logger log = LoggerFactory.getLogger(AbstractVisualizer.class); - /** File Extensions */ + /** Default File Extensions */ private static final String[] EXTS = { ".xml", ".jtl", ".csv" }; // $NON-NLS-1$ $NON-NLS-2$ $NON-NLS-3$ /** A panel allowing results to be saved. */ @@ -150,7 +150,7 @@ public void actionPerformed(ActionEvent e) { d.setVisible(true); }); - filePanel = new FilePanel(JMeterUtils.getResString("file_visualizer_output_file"), EXTS); // $NON-NLS-1$ + filePanel = new FilePanel(JMeterUtils.getResString("file_visualizer_output_file"), getFileExts()); // $NON-NLS-1$ filePanel.addChangeListener(this); filePanel.add(new JLabel(JMeterUtils.getResString("log_only"))); // $NON-NLS-1$ filePanel.add(errorLogging); @@ -345,4 +345,8 @@ public void clearGui(){ super.clearGui(); filePanel.clearGui(); } + + public String[] getFileExts() { + return EXTS; + } } diff --git a/src/core/src/main/resources/org/apache/jmeter/resources/messages.properties b/src/core/src/main/resources/org/apache/jmeter/resources/messages.properties index 3137d879e74..5233f87dca1 100644 --- a/src/core/src/main/resources/org/apache/jmeter/resources/messages.properties +++ b/src/core/src/main/resources/org/apache/jmeter/resources/messages.properties @@ -1401,8 +1401,9 @@ view_graph_tree_title=View Graph Tree view_results_assertion_error=Assertion error: view_results_assertion_failure=Assertion failure: view_results_assertion_failure_message=Assertion failure message: -view_results_autoscroll=Scroll automatically -view_results_childsamples=Child samples +view_results_autoscroll=Auto scroll +view_results_scrollstop=Stop scroll on error +view_results_childsamples=Child samples? view_results_datatype=Data type ("text"|"bin"|""): view_results_desc=Shows the text results of sampling in tree form view_results_error_count=Error Count: @@ -1461,6 +1462,7 @@ view_results_table_request_tab_raw=Raw view_results_table_result_tab_parsed=Parsed view_results_table_result_tab_raw=Raw view_results_thread_name=Thread Name: +view_results_sample_name=Sample label: view_results_title=View Results view_results_tree_title=View Results Tree warning=Warning! @@ -1545,3 +1547,4 @@ xpath2_extractor_match_number_failure=MatchNumber out of bonds \: you_must_enter_a_valid_number=You must enter a valid number zh_cn=Chinese (Simplified) zh_tw=Chinese (Traditional) +show_current_node=Focus on selected node diff --git a/xdocs/usermanual/properties_reference.xml b/xdocs/usermanual/properties_reference.xml index a40ae5027a6..74057d7ed19 100644 --- a/xdocs/usermanual/properties_reference.xml +++ b/xdocs/usermanual/properties_reference.xml @@ -1552,6 +1552,18 @@ JMETER-SERVER Can be switched off by setting the value to -1.
Defaults to: 10000 + + Set to true to enable by default the checkbox for auto scrolling.
+ Defaults to: true +
+ + Set to true to enable by default the checkbox for stop auto scrolling on 1st error.
+ Defaults to: true +
+ + Default width of the scroll view.
+ Defaults to: 250 +
Maximum size (in bytes) of Document that can be parsed by Tika engine
Set to zero to disable the size check.