Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: cucumber refactor and capture steps #77

Merged
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
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "test-results-parser",
"version": "0.1.19",
"version": "0.1.20",
"description": "Parse test results from JUnit, TestNG, xUnit, cucumber and many more",
"main": "src/index.js",
"types": "./src/index.d.ts",
Expand Down
48 changes: 48 additions & 0 deletions src/parsers/base.parser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
const { unescape } = require('html-escaper');


class BaseParser {

/**
* @param {string} value
* @returns
*/
parseText(value) {
return value ? unescape(value) : value;
}

/**
*
* @param {string[]} parent_tags
* @param {string[]} child_tags
*/
mergeTags(parent_tags, child_tags) {
if (!parent_tags) {
parent_tags = [];
}
if (!child_tags) {
child_tags = [];
}
for (const tag of parent_tags) {
if (child_tags.indexOf(tag) === -1) {
child_tags.push(tag);
}
}
}

mergeMetadata(parent_metadata, child_metadata) {
if (!parent_metadata) {
parent_metadata = {};
}
if (!child_metadata) {
child_metadata = {};
}
for (const [key, value] of Object.entries(parent_metadata)) {
if (!child_metadata[key]) {
child_metadata[key] = value;
}
}
}
}

module.exports = { BaseParser }
234 changes: 125 additions & 109 deletions src/parsers/cucumber.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,139 +3,155 @@ const { resolveFilePath } = require('../helpers/helper');
const TestResult = require('../models/TestResult');
const TestSuite = require('../models/TestSuite');
const TestCase = require('../models/TestCase');
const TestStep = require('../models/TestStep');
const { BaseParser } = require('./base.parser');

function getTestCase(rawCase) {
const test_case = new TestCase();
test_case.name = rawCase["name"];
test_case.duration = rawCase["duration"];
setMetaData(rawCase, test_case);
if (rawCase.state && rawCase.state === "failed") {
test_case.status = 'FAIL';
setErrorAndStackTrace(test_case, rawCase.errorStack);
class CucumberParser extends BaseParser {

constructor(file) {
super();
this.result = new TestResult();
this.raw_result = this.#getCucumberResult(file);
}
else {
test_case.status = 'PASS';

/**
* @returns {import('./cucumber.result').CucumberJsonResult}
*/
#getCucumberResult(file) {
return require(resolveFilePath(file));
}
return test_case;
}

/**
* @param {TestCase} test_case
* @param {string?} message
*/
function setErrorAndStackTrace(test_case, message) {
if (message) {
const stack_trace_start_index = message.indexOf(' at ');
if (stack_trace_start_index) {
const failure = message.slice(0, stack_trace_start_index);
const stack_trace = message.slice(stack_trace_start_index);
test_case.setFailure(failure);
test_case.stack_trace = stack_trace;
} else {
test_case.setFailure(message);
}
parse() {
this.#setTestResults();
return this.result;
}
}

/**
*
* @param {import('./cucumber.result').CucumberElement} element
* @param {TestCase | TestSuite} test_element
*/
function setMetaData(element, test_element) {
const tags = element.tags;
if (tags && tags.length > 0) {
for (const tag of tags) {
if (tag["name"].includes("=")) {
const [name, value] = tag["name"].substring(1).split("=");
test_element.metadata[name] = value;
} else {
test_element.tags.push(tag["name"]);
#setTestResults() {
this.result.name = '';
this.#setTestSuites();
this.result.status = this.result.suites.every(suite => suite.status === "PASS") ? "PASS" : "FAIL";
this.result.total = this.result.suites.reduce((total, suite) => total + suite.total, 0);
this.result.passed = this.result.suites.reduce((total, suite) => total + suite.passed, 0);
this.result.failed = this.result.suites.reduce((total, suite) => total + suite.failed, 0);
this.result.duration = this.result.suites.reduce((total, suite) => total + suite.duration, 0);
this.result.duration = parseFloat(this.result.duration.toFixed(2));
}

#setTestSuites() {
for (const feature of this.raw_result) {
const test_suite = new TestSuite();
test_suite.name = feature.name;
test_suite.total = feature.elements.length;
for (const scenario of feature.elements) {
test_suite.cases.push(this.#getTestCase(scenario));
}
test_suite.total = test_suite.cases.length;
test_suite.passed = test_suite.cases.filter(_ => _.status === "PASS").length;
test_suite.failed = test_suite.cases.filter(_ => _.status === "FAIL").length;
test_suite.duration = test_suite.cases.reduce((total, _) => total + _.duration, 0);
test_suite.duration = parseFloat(test_suite.duration.toFixed(2));
test_suite.status = test_suite.total === test_suite.passed ? 'PASS' : 'FAIL';
const { tags, metadata } = this.#getTagsAndMetadata(feature.tags);
test_suite.tags = tags;
test_suite.metadata = metadata;
for (const test_case of test_suite.cases) {
this.mergeTags(test_suite.tags, test_case.tags);
this.mergeMetadata(test_suite.metadata, test_case.metadata);
}

this.result.suites.push(test_suite);
}
}
}

function getTestSuite(rawSuite) {
const suite = new TestSuite();
suite.name = rawSuite["name"];
suite.total = rawSuite["tests"];
suite.passed = rawSuite["passes"];
suite.failed = rawSuite["failures"];
suite.duration = rawSuite["duration"];
suite.status = suite.total === suite.passed ? 'PASS' : 'FAIL';
setMetaData(rawSuite, suite);
const raw_test_cases = rawSuite.elements;
if (raw_test_cases) {
for (let i = 0; i < raw_test_cases.length; i++) {
suite.cases.push(getTestCase(raw_test_cases[i]));
/**
*
* @param {import('./cucumber.result').CucumberElement} scenario
*/
#getTestCase(scenario) {
const test_case = new TestCase();
test_case.name = scenario.name;
for (const step of scenario.steps) {
test_case.steps.push(this.#getTestStep(step));
}
test_case.total = test_case.steps.length;
test_case.passed = test_case.steps.filter(step => step.status === "PASS").length;
test_case.failed = test_case.steps.filter(step => step.status === "FAIL").length;
test_case.duration = test_case.steps.reduce((total, _) => total + _.duration, 0);
test_case.duration = parseFloat((test_case.duration).toFixed(2));
test_case.status = test_case.total === test_case.passed ? 'PASS' : 'FAIL';
if (test_case.status === "FAIL") {
const failed_step = test_case.steps.find(step => step.status === "FAIL");
test_case.failure = failed_step.failure;
test_case.stack_trace = failed_step.stack_trace
}
const { tags, metadata } = this.#getTagsAndMetadata(scenario.tags);
test_case.tags = tags;
test_case.metadata = metadata;
return test_case;
}
return suite;
}

/**
* @param {import("./cucumber.result").CucumberJsonResult} json
*/
function getTestResult(json) {
const result = new TestResult();
const { stats, suites } = preprocess(json);
result.name = suites["name"] || "";
result.total = stats["tests"];
result.passed = stats["passes"];
result.failed = stats["failures"];
const errors = stats["errors"];
if (errors) {
result.errors = errors;
/**
*
* @param {import('./cucumber.result').CucumberStep} step
*/
#getTestStep(step) {
const test_step = new TestStep();
test_step.name = step.name;
test_step.status = step.result.status === "passed" ? "PASS" : "FAIL";
test_step.duration = step.result.duration ? parseFloat((step.result.duration / 1000000).toFixed(2)) : 0;
if (test_step.status === "FAIL") {
const { failure, stack_trace } = this.#getFailureAndStackTrace(step.result.error_message);
test_step.failure = failure;
test_step.stack_trace = stack_trace;
}
return test_step;
}
result.duration = stats["duration"] || 0;

if (suites.length > 0) {
for (let i = 0; i < suites.length; i++) {
result.suites.push(getTestSuite(suites[i]));
/**
*
* @param {string} message
*/
#getFailureAndStackTrace(message) {
if (message) {
const stack_trace_start_index = message.indexOf(' at ');
if (stack_trace_start_index) {
const failure = this.parseText(message.slice(0, stack_trace_start_index));
const stack_trace = message.slice(stack_trace_start_index);
return { failure, stack_trace };
} else {
return { failure: message, stack_trace: '' };
}
}
return { failure: '', stack_trace: '' };
}
result.status = result.total === result.passed ? 'PASS' : 'FAIL';
return result;
}

/**
* Function to format the raw json report
* @param {import("./cucumber.result").CucumberJsonResult} json
* @returns formatted json object
*/
function preprocess(json) {
const formattedResult = { stats: {}, suites: [] };

json.forEach(testSuite => {
testSuite.elements.forEach(testCase => {
testCase.state = testCase.steps.every(step => step.result.status === "passed") ? "passed" : "failed";
testCase.duration = testCase.steps.map(step => step.result.duration).reduce((total, currVal) => total + currVal, 0) / 1000000;
testCase.duration = parseFloat(testCase.duration.toFixed(2));
testCase.errorStack = testCase.steps.filter(step => step.result.status === "failed").map(step => step.result.error_message)[0] || "";
})
testSuite.tests = testSuite.elements.length;

if (testSuite.tests) {
testSuite.failures = testSuite.elements.filter(testCase => testCase.state === "failed").length;
testSuite.passes = testSuite.elements.filter(testCase => testCase.state === "passed").length;
testSuite.duration = testSuite.elements.map(testCase => testCase.duration).reduce((total, currVal) => total + currVal, 0);
/**
*
* @param {import('./cucumber.result').CucumberTag[]} cucumber_tags
*/
#getTagsAndMetadata(cucumber_tags) {
const metadata = {};
const tags = [];
if (cucumber_tags) {
for (const tag of cucumber_tags) {
if (tag["name"].includes("=")) {
const [name, value] = tag["name"].substring(1).split("=");
metadata[name] = value;
} else {
tags.push(tag["name"]);
}
}
}
formattedResult.suites.push(testSuite);
});

formattedResult.stats.suites = formattedResult.suites.length;
for (const statsType of ["tests", "passes", "failures", "errors", "duration"]) {
formattedResult.stats[statsType] = formattedResult.suites.map(testSuite => testSuite[statsType]).reduce((total, currVal) => total + currVal, 0) || 0;
return { tags, metadata };
}
return formattedResult;

}

function parse(file) {
const json = require(resolveFilePath(file));
return getTestResult(json);
const parser = new CucumberParser(file);
return parser.parse();
}

module.exports = {
parse
}
}
Loading
Loading