Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions NOTICE.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@ freemarker
* Homepage: https://freemarker.apache.org/
* More information in folder: doc/3rd-party-licenses/freemarker

flexmark-java

* License: BSD 2-Clause License
* Homepage: https://github.com/vsch/flexmark-java
* More information in folder: doc/3rd-party-licenses/flexmark-java

google-api-services-compute

* License: Apache License 2.0
Expand Down
105 changes: 105 additions & 0 deletions config/testreport/css/default.css
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,11 @@ template {
--web-vital-color-poor: rgb(200,120,120);

--load-bar-color: #8aa4c1;

--markdown-code-background-color: #f0f0f0;
--markdown-pre-background-color: #f5f5f5;
--markdown-table-header-background-color: #f0f0f0;
--markdown-blockquote-color: #666;
}

/*
Expand Down Expand Up @@ -1375,6 +1380,106 @@ table * {
list-style-position: inside;
}

/*
* ----------------------------------------------------
* Markdown-rendered comment content
* ----------------------------------------------------
*/
.markdown {
color: var(--description-color);
}
.markdown h1, .markdown h2, .markdown h3, .markdown h4,
.markdown h5, .markdown h6 {
margin: 0.8em 0 0.4em 0;
color: var(--section-headline-color);
border-bottom: none;
}
.markdown h1 {
font-size: 1.4rem;
border-bottom: 2px solid var(--section-headline-color);
}
.markdown h2 {
font-size: 1.25rem;
}
.markdown h3 {
font-size: 1.1rem;
border-bottom: 1px solid var(--section-headline-color);
}
.markdown h4 {
font-size: 1.05rem;
border-bottom: 1px dotted var(--section-headline-color);
}
.markdown h5 {
font-size: 1.02rem;
font-weight: normal;
}
.markdown h6 {
font-size: 1.00rem;
font-weight: normal;
font-style: italic;
}

.markdown p {
margin: 0.5em 0;
}
.markdown ul, .markdown ol {
margin: 0.5em 0 0.5em 1.5em;
padding: 0;
}
.markdown li {
margin: 0.2em 0;
}
.markdown code {
background-color: var(--markdown-code-background-color);
padding: 0.15em 0.4em;
border-radius: 3px;
font-size: 0.9em;
}
.markdown pre {
background-color: var(--markdown-pre-background-color);
border: 1px solid var(--main-border-color);
Comment on lines +1432 to +1440
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These new Markdown styles hardcode colors (#f0f0f0, #f5f5f5, and elsewhere #666), which may conflict with theming and contradict the doc’s claim that styling uses existing CSS custom properties for consistent appearance. Consider switching these to existing CSS variables (or introduce dedicated variables) so Markdown blocks adapt consistently across themes.

Copilot uses AI. Check for mistakes.
border-radius: 4px;
padding: 0.75em 1em;
overflow-x: auto;
}
.markdown pre code {
background: none;
padding: 0;
border-radius: 0;
}
.markdown table {
border-collapse: collapse;
margin: 0.5em 0;
}
.markdown th, .markdown td {
border: 1px solid var(--main-border-color);
padding: 0.3em 0.6em;
text-align: left;
}
.markdown th {
background-color: var(--markdown-table-header-background-color);
font-weight: bold;
padding: 0.5em 0.75em;
text-align: center;
}
.markdown blockquote {
border-left: 3px solid var(--main-border-color);
margin: 0.5em 0;
padding: 0.25em 1em;
color: var(--markdown-blockquote-color);
}
.markdown hr {
border: none;
border-top: 1px solid var(--main-border-color);
margin: 1em 0;
}
.markdown strong {
font-weight: bold;
}
.markdown em {
font-style: italic;
}


.subsection
{
Expand Down
26 changes: 26 additions & 0 deletions doc/3rd-party-licenses/flexmark-java/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
Copyright (c) 2015-2016, Atlassian Pty Ltd
All rights reserved.

Copyright (c) 2016-2018, Vladimir Schneider,
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
7 changes: 7 additions & 0 deletions doc/3rd-party-licenses/flexmark-java/NOTICE
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
flexmark-java
https://github.com/vsch/flexmark-java

Copyright (c) 2015-2016, Atlassian Pty Ltd
Copyright (c) 2016-2018, Vladimir Schneider

Licensed under the BSD 2-Clause License.
54 changes: 54 additions & 0 deletions doc/feature-doc/markdown-comments.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Markdown Comments in Reports

XLT reports support optional Markdown formatting for test comments. When a comment
value is prefixed with `::markdown::`, the content after the prefix is converted from
Markdown to HTML and embedded in the report inside a styled `<div class="markdown">`.

## Usage

Set the comment property in your test configuration:

```properties
# Plain text (unchanged, raw HTML also accepted)
com.xceptance.xlt.loadtests.comment = This is a plain comment

# Markdown-formatted comment
com.xceptance.xlt.loadtests.comment = ::markdown::## Test Run Notes\n\n- **Environment**: staging\n- **Build**: 1.2.3\n- **Duration**: 2 hours
```

Multiple numbered comments are also supported:

```properties
com.xceptance.xlt.loadtests.comment.1 = ::markdown::## Summary\nAll scenarios passed.
com.xceptance.xlt.loadtests.comment.2 = ::markdown::## Known Issues\n- Slow DNS on agent 3
```

## Behavior

| Input | Output |
|-------|--------|
| No prefix | Passed through as-is (raw HTML allowed) |
| `::markdown::` + content | Markdown → HTML, wrapped in `<div class="markdown">` |
| `::markdown::` only (no content) | Returned unchanged |
| `null` | Returned as `null` |
Comment on lines +28 to +33
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This table uses || at line starts, which typically renders as an extra empty column or malformed table in common Markdown renderers (including GFM). Replace with standard pipe table formatting (single leading/trailing |) so the “Behavior” table renders correctly.

Copilot uses AI. Check for mistakes.

The `::markdown::` prefix is **case-insensitive** — `::Markdown::`, `::MARKDOWN::`, etc. all work.

## Supported Markdown Features

Powered by [flexmark-java](https://github.com/vsch/flexmark-java) with these extensions:

- **Headings** (`# H1` through `###### H6`)
- **Bold** / *Italic* / ~~Strikethrough~~
- **Lists** (ordered and unordered)
- **Tables** (GitHub-flavored)
- **Code** (inline and fenced code blocks)
- **Blockquotes**
- **Links** (including auto-linking of URLs)
- **Horizontal rules**

## Styling

The rendered Markdown is wrapped in `<div class="markdown">` and styled via the report's
`default.css`. The styles use the report's existing CSS custom properties for consistent
appearance. Headings, tables, code blocks, blockquotes, and lists are all covered.
16 changes: 16 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,22 @@
<artifactId>parboiled-java</artifactId>
<version>1.4.1</version>
</dependency>
<!-- flexmark-java for Markdown-to-HTML conversion (report comments) -->
<dependency>
<groupId>com.vladsch.flexmark</groupId>
<artifactId>flexmark</artifactId>
<version>0.64.8</version>
</dependency>
<dependency>
<groupId>com.vladsch.flexmark</groupId>
<artifactId>flexmark-ext-tables</artifactId>
<version>0.64.8</version>
</dependency>
<dependency>
<groupId>com.vladsch.flexmark</groupId>
<artifactId>flexmark-ext-autolink</artifactId>
<version>0.64.8</version>
Comment on lines +539 to +553
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The flexmark version is duplicated across multiple dependencies, which makes upgrades/error-prone edits more likely. Consider extracting 0.64.8 into a Maven property (or dependencyManagement) and reference it from each dependency entry.

Copilot uses AI. Check for mistakes.
</dependency>
<!-- WebSocket-Server needed for running client-performance tests with Chrome and FF -->
<dependency>
<groupId>org.glassfish.tyrus</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
Expand All @@ -32,6 +33,11 @@
import org.apache.commons.vfs2.FileSystemManager;
import org.apache.commons.vfs2.VFS;

import com.vladsch.flexmark.ext.autolink.AutolinkExtension;
import com.vladsch.flexmark.ext.tables.TablesExtension;
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.util.data.MutableDataSet;
import com.xceptance.common.util.ProductInformation;
import com.xceptance.common.util.RegExUtils;
import com.xceptance.xlt.api.engine.Data;
Expand All @@ -58,6 +64,24 @@ public class ConfigurationReportProvider extends AbstractReportProvider

private static final String MASK_PROPERTIES_REGEX_DEFAULT = "(?i)password";

private static final String MARKDOWN_PREFIX = "::markdown::";

/** Flexmark parser with tables and autolink extensions. */
private static final Parser MARKDOWN_PARSER;

/** Flexmark HTML renderer. */
private static final HtmlRenderer MARKDOWN_RENDERER;

static
{
// we want to support tables and autolinks
final MutableDataSet options = new MutableDataSet();
options.set(Parser.EXTENSIONS, Arrays.asList(TablesExtension.create(), AutolinkExtension.create()));

MARKDOWN_PARSER = Parser.builder(options).build();
MARKDOWN_RENDERER = HtmlRenderer.builder(options).build();
}

/**
* {@inheritDoc}
*/
Expand Down Expand Up @@ -111,7 +135,7 @@ public Object createReportFragment()

for (final Map.Entry<String, String> entry : sortedLoadtestProps.entrySet())
{
report.comments.add(entry.getValue());
report.comments.add(processComment(entry.getValue()));
}

// add project name
Expand Down Expand Up @@ -224,6 +248,35 @@ public boolean wantsDataRecords()
return false;
}

/**
* Processes a comment string. If the comment starts with the marker
* {@value #MARKDOWN_PREFIX} (case-insensitive), the remainder is treated
* as Markdown and converted to HTML, wrapped in a div with class "markdown".
* Otherwise the raw string is returned unchanged.
*
* @param comment
* the comment string to process
* @return the processed comment
*/
static String processComment(final String comment)
{
if (comment == null)
{
return null;
}

final String cleanedString = comment.strip();

if (cleanedString.length() >= MARKDOWN_PREFIX.length() && cleanedString.substring(0, MARKDOWN_PREFIX.length()).equalsIgnoreCase(MARKDOWN_PREFIX))
{
final String markdown = cleanedString.substring(MARKDOWN_PREFIX.length());
final String html = MARKDOWN_RENDERER.render(MARKDOWN_PARSER.parse(markdown));
return "<div class=\"markdown\">" + html + "</div>";
}

return comment;
}

/**
* Returns the custom JVM arguments stored in the file "jvmargs.cfg". If no such file can be found, the returned
* list is empty.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,61 @@ public void testSecretPropertiesAreMaskedInTheOutput() throws IOException
FileUtils.deleteDirectoryRelaxed(testDir.toFile());
}
}

// --- processComment tests ---

@Test
public void testProcessComment_plainString()
{
Assert.assertEquals("Hello World", ConfigurationReportProvider.processComment("Hello World"));
}

@Test
public void testProcessComment_rawHtml()
{
Assert.assertEquals("<b>Bold</b>", ConfigurationReportProvider.processComment("<b>Bold</b>"));
}

@Test
public void testProcessComment_markdown()
{
final String result = ConfigurationReportProvider.processComment("::markdown::**bold** text");
Assert.assertTrue("Should start with markdown div", result.startsWith("<div class=\"markdown\">"));
Assert.assertTrue("Should end with closing div", result.endsWith("</div>"));
Assert.assertTrue("Should contain <strong>", result.contains("<strong>bold</strong>"));
}

@Test
public void testProcessComment_caseInsensitive()
{
final String result = ConfigurationReportProvider.processComment("::Markdown::**bold** text");
Assert.assertTrue("Should start with markdown div", result.startsWith("<div class=\"markdown\">"));
Assert.assertTrue("Should contain <strong>", result.contains("<strong>bold</strong>"));

final String result2 = ConfigurationReportProvider.processComment("::MARKDOWN::**bold** text");
Assert.assertTrue("Upper case should also work", result2.startsWith("<div class=\"markdown\">"));
}

@Test
public void testProcessComment_markdownTable()
{
final String table = "::markdown::| A | B |\n|---|---|\n| 1 | 2 |";
final String result = ConfigurationReportProvider.processComment(table);
Assert.assertTrue("Should start with markdown div", result.startsWith("<div class=\"markdown\">"));
Assert.assertTrue("Should contain table element", result.contains("<table>"));
}

@Test
public void testProcessComment_null()
{
Assert.assertNull(ConfigurationReportProvider.processComment(null));
}

@Test
public void testProcessComment_prefixOnly()
{
// prefix only with no content should be returned unchanged
Assert.assertEquals("<div class=\"markdown\"></div>", ConfigurationReportProvider.processComment("::markdown::"));
}
}