Skip to content
Merged
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
2 changes: 1 addition & 1 deletion core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>org.jsmart</groupId>
<artifactId>zerocode-tdd-parent</artifactId>
<version>1.4.2</version>
<version>1.4.3</version>
</parent>

<artifactId>zerocode-tdd</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public interface ZeroCodeReportConstants {
String TARGET_FULL_REPORT_DIR = "target/";
String TARGET_REPORT_DIR = "target/zerocode-test-reports/";
String TARGET_FULL_REPORT_CSV_FILE_NAME = "zerocode-junit-granular-report.csv";
String TARGET_FULL_REPORT_TXT_FILE_NAME = "zerocode-junit-granular-report.txt";
String TARGET_FILE_NAME = "target/zerocode-junit-interactive-fuzzy-search.html";
String HIGH_CHART_HTML_FILE_NAME = "zerocode_results_chart";
String AUTHOR_MARKER_OLD = "@@"; //Deprecated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public void runPostFinished() {
private void generateChartsAndReports() {

reportGenerator.generateCsvReport();
reportGenerator.generateTableReport();

/**
* Not compatible with open source license i.e. why not activated. But if it has to be used inside intranet,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ public interface ZeroCodeReportGenerator {
void generateHighChartReport();

void generateExtentReport();

void generateTableReport();
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
Expand Down Expand Up @@ -119,9 +120,11 @@ public void generateExtentReport() {
thisReport.getResults().forEach(thisScenario -> {
ExtentTest test = extentReports.createTest(thisScenario.getScenarioName());

/**This code checks if the scenario has meta data.
If it does, it iterates through each meta data entry and adds it to
the Extent report as an info label.**/
/**
* This code checks if the scenario has meta data.
* If it does, it iterates through each meta data entry and adds it to
* the Extent report as an info label.
*/
if (thisScenario.getMeta() != null) {
for (Map.Entry<String, List<String>> entry : thisScenario.getMeta().entrySet()) {
String key = entry.getKey();
Expand All @@ -133,14 +136,14 @@ public void generateExtentReport() {
// Assign Category
test.assignCategory(DEFAULT_REGRESSION_CATEGORY); //Super set
String[] hashTagsArray = optionalCategories(thisScenario.getScenarioName()).toArray(new String[0]);
if(hashTagsArray.length > 0) {
if (hashTagsArray.length > 0) {
test.assignCategory(hashTagsArray); //Sub categories
}

// Assign Authors
test.assignAuthor(DEFAULT_REGRESSION_AUTHOR); //Super set
String[] authorsArray = optionalAuthors(thisScenario.getScenarioName()).toArray(new String[0]);
if(authorsArray.length > 0) {
if (authorsArray.length > 0) {
test.assignAuthor(authorsArray); //Sub authors
}

Expand Down Expand Up @@ -207,12 +210,12 @@ protected List<String> optionalCategories(String scenarioName) {

private List<String> deriveNames(String scenarioName, String marker) {
List<String> nameList = new ArrayList<>();
for(String thisName : scenarioName.trim().split(" ")){
if(thisName.startsWith(marker) && !thisName.startsWith(AUTHOR_MARKER_OLD)){
for (String thisName : scenarioName.trim().split(" ")) {
if (thisName.startsWith(marker) && !thisName.startsWith(AUTHOR_MARKER_OLD)) {
nameList.add(thisName);
}
// Depreciated, but still supports. Remove this via a new ticket
if(thisName.startsWith(AUTHOR_MARKER_OLD)){
if (thisName.startsWith(AUTHOR_MARKER_OLD)) {
nameList.add(thisName);
}
}
Expand Down Expand Up @@ -448,6 +451,129 @@ protected void validateReportsFolderAndTheFilesExists(String reportsFolder) {

}

@Override
public void generateTableReport() {
if (zeroCodeCsvFlattenedRows == null || zeroCodeCsvFlattenedRows.isEmpty()) {
LOGGER.warn("No CSV rows available — skipping table report generation.");
return;
}

String table = buildTableReportContent(zeroCodeCsvFlattenedRows);

// --------------------------------------------------------------------------------------
// This is intentionally commented out here.
// Let the end-users control/print/log via runPostFinished() lifecycle method [OPTIONAL].
// Also, at this point, end-users can download or view the .txt report file in "target/"
// --------------------------------------------------------------------------------------
// LOGGER.info("\n{}", table);

String txtFileName = resolveCsvReportName().replace(".csv", ".txt");
File txtFile = new File(TARGET_FULL_REPORT_DIR + txtFileName);
try (PrintWriter pw = new PrintWriter(txtFile)) {
pw.print(table);
} catch (IOException e) {
LOGGER.error("Failed to write table report to {}: {}", txtFile.getPath(), e.getMessage());
}

LOGGER.info("Tabular .txt report written to: {}", txtFile.getPath());
}

String buildTableReportContent(List<ZeroCodeCsvReport> rows) {
final int FIELDS_COUNT = 5;
final int PADDING = 2;
final int SCEN_WIDTH = 48,
STEP_WIDTH = 25,
METH_WIDTH = 22,
RES_WIDTH = 8,
DELAY_WIDTH = 10;

String colSepr = "+" + repeat('-', SCEN_WIDTH + PADDING)
+ "+" + repeat('-', STEP_WIDTH + PADDING)
+ "+" + repeat('-', METH_WIDTH + PADDING)
+ "+" + repeat('-', RES_WIDTH + PADDING)
+ "+" + repeat('-', DELAY_WIDTH + PADDING)
+ "+";

int allFieldWidth = SCEN_WIDTH + STEP_WIDTH + METH_WIDTH + RES_WIDTH + DELAY_WIDTH;
int innerPlusCount = FIELDS_COUNT - 1;
int footRepeat = (FIELDS_COUNT * PADDING) + allFieldWidth + innerPlusCount;

String footSep = "+" + repeat('-', footRepeat) + "+";

StringBuilder sb = new StringBuilder();
sb.append(colSepr).append('\n');
sb.append("| ").append(pad("SCENARIO", SCEN_WIDTH)).append(" | ")
.append(pad("STEP", STEP_WIDTH)).append(" | ")
.append(pad("METHOD", METH_WIDTH)).append(" | ")
.append(pad("RESULT ", RES_WIDTH)).append(" | ")
.append(pad("DELAY (ms)", DELAY_WIDTH)).append(" |\n");
sb.append(colSepr).append('\n');

int passed = 0, failed = 0;
double min = Double.MAX_VALUE, max = Double.MIN_VALUE;

for (ZeroCodeCsvReport row : rows) {
String scen = trunc(row.getScenarioName(), SCEN_WIDTH);
String step = pad(row.getStepName(), STEP_WIDTH);
String method = pad(row.getMethod(), METH_WIDTH);
boolean isPass = RESULT_PASS.equals(row.getResult());
String resCell = isPass ? "PASSED ✅" : "FAILED ❌";
double delay = row.getResponseDelayMilliSec() != null ? row.getResponseDelayMilliSec() : 0.0;

if (isPass) passed++;
else failed++;
if (delay < min) min = delay;
if (delay > max) max = delay;

sb.append("| ").append(scen).append(" | ")
.append(step).append(" | ")
.append(method).append(" | ")
.append(resCell).append("| ")
.append(rpad(delay, DELAY_WIDTH)).append(" |\n");
}

String summary = String.format(
"Total: %d | PASSED: %d | FAILED: %d | Min delay: %s ms | Max delay: %s ms",
rows.size(), passed, failed, fmt(min), fmt(max));

sb.append(footSep).append('\n');
sb.append("| ").append(pad(summary, footRepeat - PADDING)).append(" |\n");
sb.append(footSep).append('\n');

return sb.toString();
}

private static String repeat(char fillChar, int count) {
char[] arr = new char[count];
Arrays.fill(arr, fillChar);
return new String(arr);
}

private static String pad(String text, int width) {
if (text == null) text = "";
if (text.length() >= width) return text.substring(0, width);
return text + repeat(' ', width - text.length());
}

private static String trunc(String text, int width) {
if (text == null) text = "";
text = text.trim();
if (text.length() <= width) return pad(text, width);
return text.substring(0, width - 2) + "..";
}

private static String rpad(double value, int width) {
String text = fmt(value);
if (text.length() >= width) return text;
return repeat(' ', width - text.length()) + text;
}

private static String fmt(double value) {
return (value == Math.floor(value) && !Double.isInfinite(value))
? String.valueOf((long) value) + ".0"
: String.valueOf(value);
}

private static Date utilDateOf(LocalDateTime localDateTime) {
return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,15 @@ public static List<String> getAllEndPointFiles(String packagePath) {
);
}

endpointFiles.sort(null);
return endpointFiles;
// endpointFiles.sort(null);
// return endpointFiles;

List<String> deduplicatedFiles = endpointFiles.stream()
.distinct()
.sorted()
.collect(Collectors.toList());
return deduplicatedFiles;

}

private static List<String> collectJsonFilesFromDirectory(URL resourceUrl,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,21 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import org.jsmart.zerocode.core.di.provider.ObjectMapperProvider;
import org.jsmart.zerocode.core.domain.reports.ZeroCodeReportStep;
import org.jsmart.zerocode.core.domain.reports.csv.ZeroCodeCsvReport;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import java.util.Properties;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.core.Is.is;
import static org.jsmart.zerocode.core.constants.ZeroCodeReportConstants.RESULT_FAIL;
import static org.jsmart.zerocode.core.constants.ZeroCodeReportConstants.RESULT_PASS;
Expand Down Expand Up @@ -199,4 +202,100 @@ public void resolveCsvReportName_returnsDefault_whenZerocodePropertiesIsEmpty()
assertThat(zeroCodeReportGenerator.resolveCsvReportName(), is(TARGET_FULL_REPORT_CSV_FILE_NAME));
}

// -------------------------------------------------------------------------
// buildTableReportContent() unit tests
// -------------------------------------------------------------------------

private static ZeroCodeCsvReport csvRow(String scenario, String step, String method,
String result, double delay) {
return new ZeroCodeCsvReport(scenario, 0, step, 0, "corr-id",
result, method, "2026-01-01T00:00:00", "2026-01-01T00:00:01", delay);
}

@Test
public void buildTableReport_allRowsHaveSameLineLength() {
List<ZeroCodeCsvReport> rows = Arrays.asList(
csvRow("Scenario One", "step_one", "GET", RESULT_PASS, 100.0),
csvRow("Scenario Two", "step_two", "POST", RESULT_FAIL, 200.0)
);

String table = zeroCodeReportGenerator.buildTableReportContent(rows);
String[] lines = table.split("\n");

// Every line must be the same display-string length
// (emoji rows are 1 string-char shorter but emoji is 2-display-wide — check string length)
int separatorLen = lines[0].length();
for (String line : lines) {
assertThat("Line not same width as separator: [" + line + "]",
line.length() == separatorLen || line.length() == separatorLen - 1, is(true));
}
}

@Test
public void buildTableReport_truncatesLongScenarioAt48Chars() {
String longScenario = "GIVEN the very long scenario name that exceeds the column width limit set for the table";
List<ZeroCodeCsvReport> rows = Arrays.asList(
csvRow(longScenario, "step", "GET", RESULT_PASS, 50.0)
);

String table = zeroCodeReportGenerator.buildTableReportContent(rows);
// truncated text ends with ".." and is exactly 48 chars (46 base + "..")
assertThat(table, containsString("GIVEN the very long scenario name that exceeds.."));
}

@Test
public void buildTableReport_passedRowContainsCheckEmoji() {
List<ZeroCodeCsvReport> rows = Arrays.asList(
csvRow("My Scenario", "my_step", "GET", RESULT_PASS, 75.0)
);
assertThat(zeroCodeReportGenerator.buildTableReportContent(rows), containsString("PASSED ✅"));
}

@Test
public void buildTableReport_failedRowContainsCrossEmoji() {
List<ZeroCodeCsvReport> rows = Arrays.asList(
csvRow("My Scenario", "my_step", "POST", RESULT_FAIL, 0.0)
);
assertThat(zeroCodeReportGenerator.buildTableReportContent(rows), containsString("FAILED ❌"));
}

@Test
public void buildTableReport_footerContainsCorrectCounts() {
List<ZeroCodeCsvReport> rows = Arrays.asList(
csvRow("S1", "step1", "GET", RESULT_PASS, 10.0),
csvRow("S2", "step2", "POST", RESULT_PASS, 20.0),
csvRow("S3", "step3", "PUT", RESULT_FAIL, 5.0)
);

String table = zeroCodeReportGenerator.buildTableReportContent(rows);
assertThat(table, containsString("Total: 3"));
assertThat(table, containsString("PASSED: 2"));
assertThat(table, containsString("FAILED: 1"));
}

@Test
public void buildTableReport_footerContainsMinMaxDelayWithPipeSeparator() {
List<ZeroCodeCsvReport> rows = Arrays.asList(
csvRow("S1", "step1", "GET", RESULT_PASS, 410.0),
csvRow("S2", "step2", "POST", RESULT_PASS, 1.0),
csvRow("S3", "step3", "DELETE", RESULT_FAIL, 0.0)
);

String table = zeroCodeReportGenerator.buildTableReportContent(rows);
assertThat(table, containsString("Min delay: 0.0 ms | Max delay: 410.0 ms"));
}

@Test
public void buildTableReport_delayValuesAreRightAligned() {
List<ZeroCodeCsvReport> rows = Arrays.asList(
csvRow("S1", "step1", "GET", RESULT_PASS, 1000.0),
csvRow("S2", "step2", "GET", RESULT_PASS, 1.0)
);

String table = zeroCodeReportGenerator.buildTableReportContent(rows);
// Both delay values right-padded to same field width: " 1000.0" and " 1.0"
assertThat(table, containsString(" 1000.0 |"));
assertThat(table, containsString(" 1.0 |"));
}

}
Loading
Loading