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

Feature/add mocha report parser #10

Merged
merged 6 commits into from
Jun 11, 2022
Merged
Show file tree
Hide file tree
Changes from 5 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
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# test-results-parser

Parse test results from JUnit, TestNG, xUnit and many more
Parse test results from JUnit, TestNG, xUnit, Mocha and many more

## Support

| Result Type | Support |
|-------------|---------|
| TestNG | ✅ |
| JUnit | ✅ |
| xUnit | ✅ |
| Result Type | Support |
|-------------------------------|---------|
| TestNG | ✅ |
| JUnit | ✅ |
| xUnit | ✅ |
| Mocha (json & mochawesome) | ✅ |
9 changes: 8 additions & 1 deletion src/helpers/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ function getJsonFromXMLFile(filePath) {
return parser.parse(xml, { arrayMode: true, ignoreAttributes: false, parseAttributeValue: true });
}

function getJsonFromFile(filePath) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can directly read a json file using require statement. Anyway we can keep it for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the PR to require the json instead of fs.readSync

const cwd = process.cwd();
const jsonFile = fs.readFileSync(path.join(cwd, filePath));
return JSON.parse(jsonFile);
}

module.exports = {
getJsonFromXMLFile
getJsonFromXMLFile,
getJsonFromFile
}
3 changes: 3 additions & 0 deletions src/parsers/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const testng = require('./testng');
const junit = require('./junit');
const xunit = require('./xunit');
const mocha = require('./mocha');

function parse(options) {
switch (options.type) {
Expand All @@ -10,6 +11,8 @@ function parse(options) {
return junit.parse(options);
case 'xunit':
return xunit.parse(options);
case 'mocha':
return mocha.parse(options);
default:
throw `UnSupported Result Type - ${options.type}`;
}
Expand Down
118 changes: 118 additions & 0 deletions src/parsers/mocha.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* Parser for both Mocha Json report and Mochawesome json
*/
const { getJsonFromFile } = require('../helpers/helper');

const TestResult = require('../models/TestResult');
const TestSuite = require('../models/TestSuite');
const TestCase = require('../models/TestCase');

function getTestCase(rawCase) {
const test_case = new TestCase();
test_case.name = rawCase["title"];
test_case.duration = rawCase["duration"] * 1000;
if (rawCase["state"] == "pending") {
test_case.status = 'SKIP';
}
else if (rawCase.state && rawCase.state === "failed") {
test_case.status = 'FAIL';
test_case.failure = rawCase.err["message"];
}
else {
test_case.status = 'PASS';
}
return test_case;
}

function getTestSuite(rawSuite) {
const suite = new TestSuite();
suite.name = rawSuite["title"];
suite.total = rawSuite["tests"].length;
suite.passed = rawSuite["passes"].length;
suite.failed = rawSuite["failures"].length;
suite.duration = rawSuite["duration"] * 1000;
suite.skipped = rawSuite["pending"].length;
suite.status = suite.total === (suite.passed + suite.skipped) ? 'PASS' : 'FAIL';
const raw_test_cases = rawSuite.tests;
if (raw_test_cases) {
for (let i = 0; i < raw_test_cases.length; i++) {
suite.cases.push(getTestCase(raw_test_cases[i]));
}
}
return suite;
}

function getTestResult(json) {
const result = new TestResult();
const { stats, results } = formatMochaJsonReport(json);
const formattedResult = results[0] || {};
const suites = formattedResult["suites"] || [];

result.name = formattedResult["title"] || "";
result.total = stats["tests"];
result.passed = stats["passes"];
result.failed = stats["failures"];
const errors = formattedResult["errors"];
if (errors) {
result.errors = errors;
}
const skipped = stats["pending"];
if (skipped) {
result.skipped = skipped;
}
result.duration = (stats["duration"] || 0) * 1000;

if (suites.length > 0) {
for (let i = 0; i < suites.length; i++) {
result.suites.push(getTestSuite(suites[i]));
}
}
result.status = (result.total - result.skipped) === result.passed ? 'PASS' : 'FAIL';
return result;
}

/**
* Function to format the mocha raw json report
* @param {*} rawjson
* @returns formatted json object
*/
function formatMochaJsonReport(rawjson) {
if (rawjson.hasOwnProperty('meta')) {
return rawjson
}
const formattedJson = { stats: rawjson.stats, results: [] };
const suites = [];
rawjson.failures.forEach(test => test.state = "failed");
rawjson.passes.forEach(test => test.state = "passed");
rawjson.pending.forEach( test => {
test.state = "pending";
test.duration = 0;
});

const rawTests = [...rawjson.passes, ...rawjson.failures, ...rawjson.pending];
const testSuites = [...new Set(rawTests.map(test => test.fullTitle.split(' ' + test.title)[0]))];

for (const testSuite of testSuites) {
const suite = {
title: testSuite,
tests: rawTests.filter(test => test.fullTitle.startsWith(testSuite))
}
suite.passes = suite.tests.filter(test => test.state === "passed");
suite.failures = suite.tests.filter(test => test.state === "failed");
suite.pending = suite.tests.filter(test => test.state === "pending");
suite.duration = suite.tests.map(test => test.duration).reduce((total, currVal) => total + currVal, 0);
suite.fullFile = suite.tests[0].file || "";
suites.push(suite);
}
formattedJson.results.push({ suites: suites });
return formattedJson;
}

function parse(options) {
const json = getJsonFromFile(options.files[0]);
return getTestResult(json);
}

module.exports = {
parse
}
43 changes: 43 additions & 0 deletions tests/data/mocha/awesome/empty-suite.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"stats": {
"suites": 0,
"tests": 0,
"passes": 0,
"pending": 0,
"failures": 0,
"start": "2022-06-11T05:20:39.076Z",
"end": "2022-06-11T05:20:39.077Z",
"duration": 0,
"testsRegistered": 0,
"passPercent": null,
"pendingPercent": null,
"other": 0,
"hasOther": false,
"skipped": 0,
"hasSkipped": false
},
"results": [
false
],
"meta": {
"mocha": {
"version": "10.0.0"
},
"mochawesome": {
"options": {
"quiet": false,
"reportFilename": "mochawesome",
"saveHtml": true,
"saveJson": true,
"consoleReporter": "spec",
"useInlineDiffs": false,
"code": true
},
"version": "7.1.3"
},
"marge": {
"options": null,
"version": "6.2.0"
}
}
}
162 changes: 162 additions & 0 deletions tests/data/mocha/awesome/multiple-suites-multiple-tests.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
{
"stats": {
"suites": 2,
"tests": 3,
"passes": 2,
"pending": 0,
"failures": 1,
"start": "2022-06-11T05:28:11.204Z",
"end": "2022-06-11T05:28:11.227Z",
"duration": 7,
"testsRegistered": 3,
"passPercent": 63.333,
"pendingPercent": 0,
"other": 0,
"hasOther": false,
"skipped": 0,
"hasSkipped": false
},
"results": [
{
"uuid": "c3036d47-45a3-4c59-a114-fc90f4ad0532",
"title": "",
"fullFile": "",
"file": "",
"beforeHooks": [],
"afterHooks": [],
"tests": [],
"suites": [
{
"uuid": "544d6fcc-7849-4a32-aff8-b0be8e076fae",
"title": "Example Suite 1",
"fullFile": "",
"file": "",
"beforeHooks": [],
"afterHooks": [],
"tests": [
{
"title": "sample test case",
"fullTitle": "Example Suite 1 sample test case",
"timedOut": false,
"duration": 3,
"state": "passed",
"speed": "fast",
"pass": true,
"fail": false,
"pending": false,
"context": null,
"code": "",
"err": {},
"uuid": "47fa7bb9-a77f-43b3-abd0-59e2afa0ef0e",
"parentUUID": "544d6fcc-7849-4a32-aff8-b0be8e076fae",
"isHook": false,
"skipped": false
},
{
"title": "sample test case 2",
"fullTitle": "Example Suite 1 sample test case 2",
"timedOut": false,
"duration": 1,
"state": "passed",
"speed": "fast",
"pass": true,
"fail": false,
"pending": false,
"context": null,
"code": "",
"err": {},
"uuid": "14a946f8-b19e-43db-bc31-d75337ab55b3",
"parentUUID": "544d6fcc-7849-4a32-aff8-b0be8e076fae",
"isHook": false,
"skipped": false
}
],
"suites": [],
"passes": [
"47fa7bb9-a77f-43b3-abd0-59e2afa0ef0e",
"14a946f8-b19e-43db-bc31-d75337ab55b3"
],
"failures": [],
"pending": [],
"skipped": [],
"duration": 4,
"root": false,
"rootEmpty": false,
"_timeout": 2000
},
{
"uuid": "2fc0097a-29a4-4cc0-b045-007da8367164",
"title": "Example Suite 2",
"fullFile": "",
"file": "",
"beforeHooks": [],
"afterHooks": [],
"tests": [
{
"title": "sample test case",
"fullTitle": "Example Suite 2 sample test case",
"timedOut": false,
"duration": 1,
"state": "failed",
"speed": null,
"pass": false,
"fail": true,
"pending": false,
"context": null,
"code": "",
"err": {
"message": "Dummy reason",
"estack": "AssertionError [ERR_ASSERTION]: Dummy reason\n at Context.<anonymous> (tests\\parser.mocha.json.spec.js:7:12)\n at processImmediate (node:internal/timers:466:21)",
"diff": null
},
"uuid": "6bcc8efe-94ba-45c9-85cd-2cbe5ba777b1",
"parentUUID": "2fc0097a-29a4-4cc0-b045-007da8367164",
"isHook": false,
"skipped": false
}
],
"suites": [],
"passes": [],
"failures": [
"6bcc8efe-94ba-45c9-85cd-2cbe5ba777b1"
],
"pending": [],
"skipped": [],
"duration": 1,
"root": false,
"rootEmpty": false,
"_timeout": 2000
}
],
"passes": [],
"failures": [],
"pending": [],
"skipped": [],
"duration": 0,
"root": true,
"rootEmpty": true,
"_timeout": 2000
}
],
"meta": {
"mocha": {
"version": "10.0.0"
},
"mochawesome": {
"options": {
"quiet": false,
"reportFilename": "mochawesome",
"saveHtml": true,
"saveJson": true,
"consoleReporter": "spec",
"useInlineDiffs": false,
"code": true
},
"version": "7.1.3"
},
"marge": {
"options": null,
"version": "6.2.0"
}
}
}
Loading