Saturday, April 9, 2016

Custom Reporting For Cucumber JVM

On our previous post we understand cucumber can generate default html report. But report is not so cool.

Here we learn how to generate a better looking and informative report. We can get this report from this github URL. This report consume the cucumber json report and create a beautiful html report.

Now we learn how to use this report in our normal Java project.

Step 1:
 We need to add all the jar files as depicted on below picture in our project.


We can download all the above mentioned jar files from Maven Repository .

Step 2:
 Now we need to write custom JUnit AfterSuite annotation because if we call the Custom Report  builder class from cucumber runner within the JUnit @After annotation then it cannot create the report, reason is cucumber report json file created after all method executed and custom report builder consume this json file and generate the report.

So we create a custom annotation class like below:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD })
public @interface AfterSuite {

}

Step 3:

We create a another class CustomCucumberRunner which will control the workflow of the Cucumber class and also the calling methods when find the expected annotation. This class extends the JUnit runner class also override the two methods

  1. getDescription()
  2. run(RunNotifier notifier)

On getDescription() method we call the cucumber getDescription() method and on run(RunNotifier notifier) method we call run method of cucumber class and then call the method which have the @After method annotation.

So code looks like below :

private void runAnnotatedMethods(Class<?> annotation) throws Exception {
if (!annotation.isAnnotation()) {
return;
}
Method[] methods = this.classValue.getMethods();
for (Method method : methods) {
Annotation[] annotations = method.getAnnotations();
for (Annotation item : annotations) {
if (item.annotationType().equals(annotation)) {
method.invoke(null);
break;
}
}
}
}

@Override
public void run(RunNotifier notifier) {
cucumber.run(notifier);
try {
runAnnotatedMethods(AfterSuite.class);
} catch (Exception e) {
e.printStackTrace();
}
}

Step 4:
Now we call this CustomCucumberRunner class from Our original Runner class like @RunWith(CustomCucumberRunner .class). We also call the Report builder class under the @AfterSuite annotation.

So code looks like:

@RunWith(ExtendedCucumberRunner.class)
public class CucumberRunner {
@AfterSuite
public static void generateReport(){
ReportBuilder reportBuilder = new ReportBuilder(jsonFiles, configuration);
reportBuilder.generateReports();
     }
}


So our complete code looks like 

Sample feature File :

Feature: This is a sample feature file
@test1
Scenario: This is a scenario to test DataTable on Cucumber JVM.
Given scenario data
When executed from Runner Class
Then UserName and Password Like below.
| userName | password |
| TestUser1 | TestPassword1 |
| TestUser2 | TestPassword2 |

Glue Code :

public class SumFeatureTest {

@Given("^scenario data$")
public void scenarioData() throws Throwable {
System.out.println("Scenario Have Some Data");
}

@When("^executed from Runner Class$")
public void executedFromRunnerClass() throws Throwable {
System.out.println("Executed From Runner Class");
}

@Then("^UserName and Password Like below\\.$")
public void usernameAndPasswordLikeBelow(DataTable dataTable) throws Throwable {
System.out.println(dataTable.asMap(String.class, String.class));
System.out.println(dataTable.asMaps(String.class, String.class));
System.out.println(dataTable.asList(UserDetailsDTO.class).get(0).getUserName()+ "  " +dataTable.asList(UserDetailsDTO.class).get(0).getPassword());
}

}

AfterSuite annotation :

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD })
public @interface AfterSuite {


}

CustomCucumberRunner Class :

public class CustomCucumberRunner extends Runner {

private Class<?> classValue;
private Cucumber cucumber;

public CustomCucumberRunner(Class<?> classValue) throws Exception {
this.classValue = classValue;
cucumber = new Cucumber(classValue);
}

@Override
public Description getDescription() {
return cucumber.getDescription();
}

private void runAnnotatedMethods(Class<?> annotation) throws Exception {
if (!annotation.isAnnotation()) {
return;
}
Method[] methods = this.classValue.getMethods();
for (Method method : methods) {
Annotation[] annotations = method.getAnnotations();
for (Annotation item : annotations) {
if (item.annotationType().equals(annotation)) {
method.invoke(null);
break;
}
}
}
}

@Override
public void run(RunNotifier notifier) {
cucumber.run(notifier);
try {
runAnnotatedMethods(AfterSuite.class);
} catch (Exception e) {
e.printStackTrace();
}
}

}

CucumberRunner Class :

@CucumberOptions(
plugin = { 
"html:target/cucumber-html-report",
"json:target/cucumber.json"
        },features ={"./sample.feature"},tags={"@test,@test1"},
glue ={"com/automation/steps"},strict = true,
dryRun= false,monochrome = true, snippets= SnippetType.CAMELCASE)

@RunWith(CustomCucumberRunner.class)
public class CucumberRunner {
@AfterSuite
public static void generateReport(){

/** * Report Generated Under "Custom-Report". */
File reportOutputDirectory = new File("./Custom-Report");
List<String> jsonFiles = new ArrayList<>();
jsonFiles.add("target/cucumber.json");
// jsonFiles.add("target/cucumberusage.json");

String jenkinsBasePath = "";
String buildNumber = "1";
String projectName = "cucumber-jvm";
boolean skippedFails = true;
boolean pendingFails = false;
boolean undefinedFails = true;
boolean missingFails = true;
boolean runWithJenkins = false;
boolean parallelTesting = false;

Configuration configuration = new Configuration(reportOutputDirectory, projectName);

// optional only if you need

configuration.setStatusFlags(skippedFails, pendingFails, undefinedFails, missingFails);
configuration.setParallelTesting(parallelTesting);
configuration.setJenkinsBasePath(jenkinsBasePath);
configuration.setRunWithJenkins(runWithJenkins);
configuration.setBuildNumber(buildNumber);

ReportBuilder reportBuilder = new ReportBuilder(jsonFiles, configuration);
reportBuilder.generateReports();
}

}