View on GitHub

Cucumber Reports

Customizing Report Format

Download this project as a .zip file Download this project as a tar.gz file

What is it?

Library provides a number of various reports out of the box. But those reports are generated internally and the user cannot change or add anything which is not supposed to be there by the report design. Sometimes it is acceptable when it requires some data processing logic.

But there are cases when we simply need to change elements style, table layout or add additional value which is calculated based on known values (e.g. total number of features/scenarios/steps can be calculated as the sum of provided elements with different status). So, it would be good to provide some ability to customize standard reports with different style, layout or data representation.

For this purpose there is functionality of reports customization.

The template document format

Each report is generated based on Freemarker templates. Generally, it’s the HTML format with special directives injection. Also, each basic report accepts some data which can be processed within the report. These data items are called data beans. These are pre-defined data-structures which are populated during each specific report processing. Thus, the major goal of report templates is just to display all this information.

All available data beans are explained in the Javadoc documentation page.

Necessary configuration

Extended Cucumber Runner operates with customTemplatesPath field of the ExtendedCucumberOptions annotation to point to customized templates. Generally, it defines the path to the templates based on the following logic:

Uploading custom templates from the configuration file

If we point customTemplatesPath to some file it should be the configuration file of JSON format. Mainly it stores the map where the key is logical resource name and the value is the path to the template file associated with the resource name. Here is the sample JSON configuration file:

{
  "benchmark": "/templates/default/benchmark.ftlh", 
  "breakdown": "/templates/default/breakdown.ftlh", 
  "consolidated": "/templates/default/consolidated.ftlh", 
  "coverage": "/templates/default/coverage.ftlh", 
  "detailed": "/templates/default/detailed.ftlh", 
  "feature_map": "/templates/default/feature_map.ftlh", 
  "feature_overview": "/templates/default/feature_overview.ftlh", 
  "known_errors": "/templates/default/known_errors.ftlh", 
  "overview": "/templates/default/overview.ftlh", 
  "overview_chart": "/templates/default/overview_chart.ftlh", 
  "pie_chart": "/templates/default/pie_chart.ftlh", 
  "retrospective": "/templates/default/retrospective.ftlh", 
  "system_info": "/templates/default/system_info.ftlh", 
  "tables": "/templates/default/tables.ftlh", 
  "usage": "/templates/default/usage.ftlh"
}

The above JSON actually replicates the default set of templates.

Uploading custom templates from the folder

If we point customTemplatesPath to some folder, it should contain files with ftlh extension in this folder or it’s sub-folders. Other files will be ignored. In this case, resource names will be associated to relative file names without an extension. E.g. if we want to load templates from specific folder (e.g. test_template) and we need some template associated with the overview resource name, we need to make sure the test_template folder contains overview.ftlh file. If this folder has sample sub-folder with the test.ftlh template file, this file will be associated with the sample/test resource.

Naming conventions for each specific report

Each report generation template is associated to some resource name. Mainly, resource names are taken from file names templates are loaded from. At the same time the library itself generates basic reports from templates associated with some specific resources. The following table shows association between reserved resource name and actual report:

Resource Name Report type Associated Data Bean
benchmark Benchmark Report BenchmarkDataBean
breakdown Breakdown Report BreakdownDataBean
consolidated Consolidated Report ConsolidatedDataBean
coverage Coverage Report CoverageDataBean
detailed Detailed Results Report DetailedReportingDataBean
feature_map Feature Map Report FeatureMapDataBean
feature_overview Overview Chart Report FeatureOverviewDataBean
known_errors Known Errors Report KnownErrorsDataBean
overview Results Overview Report OverviewDataBean
overview_chart Charts Report OverviewChartDataBean
retrospective Retrospective Report RetrospectiveDataBean
system_info System Info Report SystemInfoDataBean
usage Steps Usage Report UsageDataBean

Rules how to override existing names and adding new entries for templates

To customize basic report override existing resource names

As it was mentioned before, there are reserved resource names which are used for report generation. If you define custom report and associate it with existing resource name, it will be overridden.

Any custom resource name can be used as inclusion

The Freemarker templates support templates inclusions. This is useful when we would like to involve some re-usable components (e.g. some common macro/functions for drawing charts or tables). As soon as we define resource with custom name we always can include it in the Freemarker template. Just like this:

<#include "custom_template">
<!-- Here we can use some instructions from included template -->

This way we may create multiple templates sharing some common re-usable functionality.

If some resource names are not overridden the default templates will be picked up for this name

In most of the cases we just need to override several templates only for reports we mainly use. At the same time, all supported report types have default template. So, if we don’t override some specific resource name there is always some default resource associated with some specific report.

Sample use

For the sample purposes let’s override Results Overview Report to display just summary table like this:

Overview Table

To do so, let’s follow the next steps:

Create some folder with template files

Let’s create our custom folder where we put our custom template files. Let’s name it templates.

Create template for the report to override

For overview report we need to add the template which will be associated with the overview resource name. The most universal way is to create templates/overview.ftlh file. Here we populate it with the following sample content:

<#assign Math=statics['java.lang.Math'] >
<html>
<head>
	<title>${title}</title>
	<style type="text/css">
.passed {background-color:lightgreen;font-weight:bold;color:darkgreen}
.skipped {background-color:silver;font-weight:bold;color:darkgray}
.failed {background-color:tomato;font-weight:bold;color:darkred}
.undefined {background-color:gold;font-weight:bold;color:goldenrod}
.known {background-color:goldenrod;font-weight:bold;color:darkred}
	</style>
</head>
<body>
<table>
	<tr><th></th><th>Passed</th><th>Failed</th><th>Known</th><th>Undefined</th><th>Total</th><th>%Passed</th></tr>
	<tr><th>Features</th>
		<td class="passed" id="features_passed">${overallStats.getFeaturesPassed()}</td>
		<td class="failed" id="features_failed">${overallStats.getFeaturesFailed()}</td>
		<td class="known" id="features_known">${overallStats.getFeaturesKnown()}</td>
		<td class="undefined" id="features_undefined">${overallStats.getFeaturesUndefined()}</td>
		<td id="features_total">${overallStats.getFeaturesTotal()}</td>
		<td id="features_rate">
			<#if stats.getFeaturesTotal() == 0>
			NaN
			<#else>
				#{100 * (stats.getFeaturesPassed() + stats.getFeaturesKnown()) / stats.getFeaturesTotal() ;M0}%
			</#if>
		</td>
	</tr>
	<tr><th>Scenarios</th>
		<td class="passed" id="scenarios_passed">${overallStats.getScenariosPassed()}</td>
		<td class="failed" id="scenarios_failed">${overallStats.getScenariosFailed()}</td>
		<td class="known" id="scenarios_known">${overallStats.getScenariosKnown()}</td>
		<td class="undefined" id="scenarios_undefined">${overallStats.getScenariosUndefined()}</td>
		<td id="scenarios_total">${overallStats.getScenariosTotal()}</td>
		<td id="scenarios_rate">
			<#if stats.getScenariosTotal() == 0>
			NaN
			<#else>
				#{100 * (stats.getScenariosPassed() + stats.getScenariosKnown()) / stats.getScenariosTotal() ;M0}%
			</#if>
		</td>
	</tr>
	<tr><th>Steps</th>
		<td class="passed" id="steps_passed">${overallStats.getStepsPassed()}</td>
		<td class="failed" id="steps_failed">${overallStats.getStepsFailed()}</td>
		<td class="known" id="steps_known">${overallStats.getStepsKnown()}</td>
		<td class="undefined" id="steps_undefined">${overallStats.getStepsUndefined()}</td>
		<td id="steps_total">${overallStats.getStepsTotal()}</td>
		<td id="steps_rate">
			<#if stats.getStepsTotal() == 0>
			NaN
			<#else>
				#{100 * (overallStats.getStepsPassed() + overallStats.getStepsKnown()) / overallStats.getStepsTotal() ;M0}%
			</#if>
		</td>
	</tr>
</table>
<div><b>Overall Duration: ${(overallStats.overallDuration/3600)?string["0"]}h ${((overallStats.overallDuration % 3600) / 60)?string["00"]}m ${((overallStats.overallDuration % 3600) % 60)?string["00"]}s</b></div>

</body>
</html>

Point Extended Cucumber Runner to custom template

Once we have the template and the folder we already can play with loading templates from folder.

This is the code we can use for this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.github.mkolisnyk.cucumber.reporting;

import org.junit.runner.RunWith;

import com.github.mkolisnyk.cucumber.runner.ExtendedCucumber;
import com.github.mkolisnyk.cucumber.runner.ExtendedCucumberOptions;

import cucumber.api.CucumberOptions;

@RunWith(ExtendedCucumber.class)
@ExtendedCucumberOptions(jsonReport = "target/cucumber.json",
        overviewReport = true,
        customTemplatesPath = "templates", // Point to local folder with custom templates
        outputFolder = "target")
@CucumberOptions(plugin = { "html:target/cucumber-html-report",
        "json:target/cucumber.json", "pretty:target/cucumber-pretty.txt",
        "usage:target/cucumber-usage.json", "junit:target/cucumber-results.xml" },
        features = { "./src/test/java/com/github/mkolisnyk/cucumber/features" },
        glue = { "com/github/mkolisnyk/cucumber/steps" })
public class SampleCucumberTest {
}

Since we named our template as overview and placed it in the templates root directory the overview resource name will be overridden and results overview will be generated using our custom template.

Point Extended Cucumber Runner to custom template via configuration file

If we want to use configuration file (typically in case we associate specific resource with customized name), we need to create the configuration file. Let’s create templates.json file with the following content.

{
  "overview": "templates/overview.ftlh" 
}

In this case we can point out test runner to our custom templates configuration file like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.github.mkolisnyk.cucumber.reporting;

import org.junit.runner.RunWith;

import com.github.mkolisnyk.cucumber.runner.ExtendedCucumber;
import com.github.mkolisnyk.cucumber.runner.ExtendedCucumberOptions;

import cucumber.api.CucumberOptions;

@RunWith(ExtendedCucumber.class)
@ExtendedCucumberOptions(jsonReport = "target/cucumber.json",
        overviewReport = true,
        customTemplatesPath = "templates.json", // Point to local configuration file with custom templates
        outputFolder = "target")
@CucumberOptions(plugin = { "html:target/cucumber-html-report",
        "json:target/cucumber.json", "pretty:target/cucumber-pretty.txt",
        "usage:target/cucumber-usage.json", "junit:target/cucumber-results.xml" },
        features = { "./src/test/java/com/github/mkolisnyk/cucumber/features" },
        glue = { "com/github/mkolisnyk/cucumber/steps" })
public class SampleCucumberTest {
}

News