Skip to content

Commit ba849af

Browse files
committed
ReqnrollFormatters.CustomizedHtml: Cleanup and document different customization options
1 parent 0251615 commit ba849af

File tree

9 files changed

+337
-134
lines changed

9 files changed

+337
-134
lines changed

ReqnrollFormatters/README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Reqnroll Formatters
2+
3+
This solution demonstrates different formatters for Reqnroll test output.
4+
5+
## Projects
6+
7+
### ReqnrollFormatters.Standard
8+
9+
This project demonstrates the standard formatters provided by Reqnroll.
10+
11+
### ReqnrollFormatters.Custom
12+
13+
This project demonstrates how to create a custom text-based formatter.
14+
15+
### ReqnrollFormatters.CustomizedHtml
16+
17+
This project demonstrates how to customize the HTML report formatter in three different ways:
18+
19+
1. **Theming**: Customize colors and overall look and feel
20+
2. **Custom Styles**: Apply custom styles to specific elements
21+
3. **Custom Rendering**: Completely change how elements are rendered
22+
23+
See the [CustomizedHtml README](ReqnrollFormatters.CustomizedHtml/README.md) for more details.
24+
25+
## References
26+
27+
- [Reqnroll Documentation](https://docs.reqnroll.net/)
28+
- [Cucumber React Components](https://github.com/cucumber/react-components) - Used for HTML report customization
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
using Cucumber.HtmlFormatter;
2+
using Io.Cucumber.Messages.Types;
3+
using Reqnroll.Formatters.Configuration;
4+
using Reqnroll.Formatters.Html;
5+
using Reqnroll.Formatters.RuntimeSupport;
6+
using Reqnroll.Utils;
7+
using System.Text.RegularExpressions;
8+
9+
namespace ReqnrollFormatters.CustomizedHtml;
10+
11+
/// <summary>
12+
/// HTML formatter that uses custom rendering for elements in the report.
13+
/// Based on https://github.com/cucumber/react-components?tab=readme-ov-file#custom-rendering
14+
/// </summary>
15+
public class CustomRenderingHtmlFormatter(IFormattersConfigurationProvider configurationProvider, IFormatterLog logger, IFileSystem fileSystem)
16+
: HtmlFormatter(configurationProvider, logger, fileSystem, "customRenderingHtml")
17+
{
18+
private class CustomRenderingResourceProvider : IResourceProvider
19+
{
20+
private readonly IResourceProvider _baseResourceProvider = new DefaultResourceProvider();
21+
22+
public string GetTemplateResource()
23+
{
24+
// No template customization needed for custom rendering
25+
return _baseResourceProvider.GetTemplateResource();
26+
}
27+
28+
public string GetCssResource()
29+
{
30+
// No CSS customization needed for custom rendering
31+
return _baseResourceProvider.GetCssResource();
32+
}
33+
34+
public string GetJavaScriptResource()
35+
{
36+
string originalResource = _baseResourceProvider.GetJavaScriptResource();
37+
var globalVarsMatch = Regex.Match(originalResource, @"\.render\((?<reactObj>[\w\.]+)\.createElement\((?<cucComps>[\w\.]+)\.EnvelopesProvider");
38+
if (!globalVarsMatch.Success)
39+
throw new InvalidOperationException("Could not find global variables in main.js resource. The regex did not match: " + originalResource);
40+
var reactObj = globalVarsMatch.Groups["reactObj"].Value;
41+
var cucumberReactComponents = globalVarsMatch.Groups["cucComps"].Value;
42+
43+
// Use customRender which completely customizes the rendering of DocString
44+
return
45+
"""
46+
function customRender(reactObj, cucumberReactComponents, rootObj, renderArg) {
47+
var customRenderArg =
48+
reactObj.createElement(cucumberReactComponents.CustomRendering, {
49+
overrides: {
50+
DocString: (props) => (
51+
reactObj.createElement(
52+
reactObj.Fragment,
53+
null,
54+
reactObj.createElement(
55+
"p",
56+
null,
57+
"I am going to render this doc string in a textarea:"
58+
),
59+
reactObj.createElement(
60+
"textarea",
61+
null,
62+
props.docString.content
63+
)
64+
)
65+
)
66+
}
67+
},
68+
renderArg
69+
);
70+
rootObj.render(customRenderArg);
71+
}
72+
73+
""" +
74+
Regex.Replace(originalResource, @"(?<rootObj>\(0,\w+\(\d+\).createRoot\)\(document.getElementById\(""content""\)\)).render\(", "customRender(" + reactObj + "," + cucumberReactComponents + ", ${rootObj},");
75+
}
76+
}
77+
78+
protected override MessagesToHtmlWriter CreateMessagesToHtmlWriter(Stream stream, Func<StreamWriter, Envelope, Task> asyncStreamSerializer)
79+
{
80+
var customResourceProvider = new CustomRenderingResourceProvider();
81+
return new MessagesToHtmlWriter(stream, asyncStreamSerializer, customResourceProvider);
82+
}
83+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
using Cucumber.HtmlFormatter;
2+
using Io.Cucumber.Messages.Types;
3+
using Reqnroll.Formatters.Configuration;
4+
using Reqnroll.Formatters.Html;
5+
using Reqnroll.Formatters.RuntimeSupport;
6+
using Reqnroll.Utils;
7+
using System.Text.RegularExpressions;
8+
9+
namespace ReqnrollFormatters.CustomizedHtml;
10+
11+
/// <summary>
12+
/// HTML formatter that applies custom styles to elements in the report.
13+
/// Based on https://github.com/cucumber/react-components?tab=readme-ov-file#custom-styles
14+
/// </summary>
15+
public class CustomStyledHtmlFormatter(IFormattersConfigurationProvider configurationProvider, IFormatterLog logger, IFileSystem fileSystem)
16+
: HtmlFormatter(configurationProvider, logger, fileSystem, "customStyledHtml")
17+
{
18+
private class CustomStyledResourceProvider : IResourceProvider
19+
{
20+
private readonly IResourceProvider _baseResourceProvider = new DefaultResourceProvider();
21+
22+
public string GetTemplateResource()
23+
{
24+
// No template customization needed for custom styles
25+
return _baseResourceProvider.GetTemplateResource();
26+
}
27+
28+
public string GetCssResource()
29+
{
30+
string originalResource = _baseResourceProvider.GetCssResource();
31+
// Add custom style for docstring
32+
return originalResource + "\n" +
33+
"""
34+
/* Custom styles, see https://github.com/cucumber/react-components?tab=readme-ov-file#custom-styles */
35+
.acme-docstring {
36+
font-weight: bold;
37+
font-style: italic;
38+
background-color: black;
39+
color: hotpink;
40+
text-shadow: 1px 1px 2px white;
41+
padding: 10px;
42+
}
43+
""";
44+
}
45+
46+
public string GetJavaScriptResource()
47+
{
48+
string originalResource = _baseResourceProvider.GetJavaScriptResource();
49+
var globalVarsMatch = Regex.Match(originalResource, @"\.render\((?<reactObj>[\w\.]+)\.createElement\((?<cucComps>[\w\.]+)\.EnvelopesProvider");
50+
if (!globalVarsMatch.Success)
51+
throw new InvalidOperationException("Could not find global variables in main.js resource. The regex did not match: " + originalResource);
52+
var reactObj = globalVarsMatch.Groups["reactObj"].Value;
53+
var cucumberReactComponents = globalVarsMatch.Groups["cucComps"].Value;
54+
55+
// Use customRender which applies a custom style class
56+
return
57+
"""
58+
function customRender(reactObj, cucumberReactComponents, rootObj, renderArg) {
59+
var customRenderArg =
60+
reactObj.createElement(cucumberReactComponents.CustomRendering, {
61+
overrides: {
62+
DocString: {
63+
docString: 'acme-docstring'
64+
}
65+
}
66+
},
67+
renderArg
68+
);
69+
rootObj.render(customRenderArg);
70+
}
71+
72+
""" +
73+
Regex.Replace(originalResource, @"(?<rootObj>\(0,\w+\(\d+\).createRoot\)\(document.getElementById\(""content""\)\)).render\(", "customRender(" + reactObj + "," + cucumberReactComponents + ", ${rootObj},");
74+
}
75+
}
76+
77+
protected override MessagesToHtmlWriter CreateMessagesToHtmlWriter(Stream stream, Func<StreamWriter, Envelope, Task> asyncStreamSerializer)
78+
{
79+
var customResourceProvider = new CustomStyledResourceProvider();
80+
return new MessagesToHtmlWriter(stream, asyncStreamSerializer, customResourceProvider);
81+
}
82+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using Cucumber.HtmlFormatter;
2+
using Io.Cucumber.Messages.Types;
3+
using Reqnroll.Formatters.Configuration;
4+
using Reqnroll.Formatters.Html;
5+
using Reqnroll.Formatters.RuntimeSupport;
6+
using Reqnroll.Utils;
7+
8+
namespace ReqnrollFormatters.CustomizedHtml;
9+
10+
/// <summary>
11+
/// HTML formatter that customizes the theme of the report.
12+
/// Based on https://github.com/cucumber/react-components?tab=readme-ov-file#theming
13+
/// </summary>
14+
public class CustomThemedHtmlFormatter(IFormattersConfigurationProvider configurationProvider, IFormatterLog logger, IFileSystem fileSystem)
15+
: HtmlFormatter(configurationProvider, logger, fileSystem, "customThemedHtml")
16+
{
17+
private class ThemedResourceProvider : IResourceProvider
18+
{
19+
private readonly IResourceProvider _baseResourceProvider = new DefaultResourceProvider();
20+
21+
public string GetTemplateResource()
22+
{
23+
string originalResource = _baseResourceProvider.GetTemplateResource();
24+
// Add dark-theme class to the content div
25+
return originalResource.Replace("<div id=\"content\">", "<div id='content' class='dark-theme'>");
26+
}
27+
28+
public string GetCssResource()
29+
{
30+
string originalResource = _baseResourceProvider.GetCssResource();
31+
// Add dark theme CSS customization
32+
return originalResource + "\n" +
33+
"""
34+
/* Custom theme, see https://github.com/cucumber/react-components?tab=readme-ov-file#theming */
35+
.dark-theme {
36+
background-color: #1d1d26;
37+
color: #c9c9d1;
38+
--cucumber-anchor-color: #4caaee;
39+
--cucumber-keyword-color: #d89077;
40+
--cucumber-parameter-color: #4caaee;
41+
--cucumber-tag-color: #85a658;
42+
--cucumber-docstring-color: #66a565;
43+
--cucumber-error-background-color: #cf6679;
44+
--cucumber-error-text-color: #222;
45+
--cucumber-code-background-color: #282a36;
46+
--cucumber-code-text-color: #f8f8f2;
47+
--cucumber-panel-background-color: #282a36;
48+
--cucumber-panel-accent-color: #313442;
49+
--cucumber-panel-text-color: #f8f8f2;
50+
}
51+
""";
52+
}
53+
54+
public string GetJavaScriptResource()
55+
{
56+
// No JavaScript customization needed for theming
57+
return _baseResourceProvider.GetJavaScriptResource();
58+
}
59+
}
60+
61+
protected override MessagesToHtmlWriter CreateMessagesToHtmlWriter(Stream stream, Func<StreamWriter, Envelope, Task> asyncStreamSerializer)
62+
{
63+
var customResourceProvider = new ThemedResourceProvider();
64+
return new MessagesToHtmlWriter(stream, asyncStreamSerializer, customResourceProvider);
65+
}
66+
}

ReqnrollFormatters/ReqnrollFormatters.CustomizedHtml/CustomizedHtmlFormatter.cs

Lines changed: 0 additions & 131 deletions
This file was deleted.

0 commit comments

Comments
 (0)