2022-11-29 03:59:12 -06:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
const Mocha = require('mocha');
|
|
|
|
const { EVENT_TEST_END, EVENT_RUN_END, EVENT_TEST_FAIL, EVENT_TEST_PASS } = Mocha.Runner.constants;
|
|
|
|
|
2022-12-20 12:36:18 -06:00
|
|
|
class LogReporter extends Mocha.reporters.Spec {
|
2022-11-29 03:59:12 -06:00
|
|
|
constructor(runner, options = {}) {
|
|
|
|
super(runner, options);
|
|
|
|
|
2022-12-20 12:36:18 -06:00
|
|
|
this._testsResults = [];
|
|
|
|
this._testFailures = [];
|
|
|
|
this._testPasses = [];
|
2022-11-29 03:59:12 -06:00
|
|
|
|
|
|
|
runner.on(EVENT_TEST_END, (test) => {
|
2022-12-20 12:36:18 -06:00
|
|
|
this._testsResults.push(test);
|
2022-11-29 03:59:12 -06:00
|
|
|
});
|
|
|
|
|
|
|
|
runner.on(EVENT_TEST_PASS, (test) => {
|
2022-12-20 12:36:18 -06:00
|
|
|
this._testPasses.push(test);
|
2022-11-29 03:59:12 -06:00
|
|
|
});
|
|
|
|
|
|
|
|
runner.on(EVENT_TEST_FAIL, (test) => {
|
2022-12-20 12:36:18 -06:00
|
|
|
this._testFailures.push(test);
|
2022-11-29 03:59:12 -06:00
|
|
|
});
|
|
|
|
|
|
|
|
runner.once(EVENT_RUN_END, () => {
|
|
|
|
this.reportStats();
|
|
|
|
this.reportResults();
|
|
|
|
this.reportErrors();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
reportStats() {
|
|
|
|
const stats = {
|
|
|
|
...this.stats,
|
|
|
|
// Format time in epoch
|
|
|
|
start: this.stats.start.getTime(),
|
|
|
|
end: this.stats.end.getTime(),
|
|
|
|
};
|
|
|
|
|
|
|
|
// Example
|
2022-12-20 12:36:18 -06:00
|
|
|
// CypressStats suites=1 tests=2 testPasses=1 pending=0 failures=1
|
2022-11-29 03:59:12 -06:00
|
|
|
// start=1668783563731 end=1668783645198 duration=81467
|
|
|
|
console.log(`CypressStats ${objToLogAttributes(stats)}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
reportResults() {
|
2022-12-20 12:36:18 -06:00
|
|
|
this._testsResults.map(cleanTest).map((test) => {
|
2022-11-29 03:59:12 -06:00
|
|
|
// Example
|
|
|
|
// CypressTestResult title="Login scenario, create test data source, dashboard, panel, and export scenario"
|
|
|
|
// suite="Smoke tests" file=../../e2e/smoke-tests-suite/1-smoketests.spec.ts duration=68694
|
|
|
|
// currentRetry=0 speed=undefined err=false
|
|
|
|
console.log(`CypressTestResult ${objToLogAttributes(test)}`);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
reportErrors() {
|
2022-12-20 12:36:18 -06:00
|
|
|
this._testFailures.map(cleanTest).forEach((failure) => {
|
2022-11-29 03:59:12 -06:00
|
|
|
const suite = failure.suite;
|
|
|
|
const test = failure.title;
|
|
|
|
const error = failure.err;
|
|
|
|
|
|
|
|
// Example
|
|
|
|
// CypressError suite="Smoke tests" test="Login scenario, create test data source, dashboard,
|
|
|
|
// panel, and export scenario" error=false
|
|
|
|
console.error(`CypressError ${objToLogAttributes({ suite, test, error })}`);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Stringify object to be log friendly
|
|
|
|
* @param {Object} obj
|
|
|
|
* @returns {String}
|
|
|
|
*/
|
|
|
|
function objToLogAttributes(obj) {
|
|
|
|
return Object.entries(obj)
|
|
|
|
.map(([key, value]) => `${key}=${formatValue(value)}`)
|
|
|
|
.join(' ');
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Escape double quotes
|
|
|
|
* @param {String} str
|
|
|
|
* @returns
|
|
|
|
*/
|
|
|
|
function escapeQuotes(str) {
|
|
|
|
return String(str).replaceAll('"', '\\"');
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Wrap the value within double quote if needed
|
|
|
|
* @param {*} value
|
|
|
|
* @returns
|
|
|
|
*/
|
|
|
|
function formatValue(value) {
|
|
|
|
const hasWhiteSpaces = /\s/g.test(value);
|
|
|
|
|
|
|
|
return hasWhiteSpaces ? `"${escapeQuotes(value)}"` : value;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Simplify the Mocha test object to get the information we need want to report
|
|
|
|
* @param {Mocha.Test} test
|
|
|
|
* @returns {Object}
|
|
|
|
*/
|
|
|
|
function cleanTest(test) {
|
|
|
|
const err = test.err instanceof Error ? test.err.toString() : false;
|
|
|
|
const testLocation = getTestLocation(test);
|
|
|
|
// Remove the test title
|
|
|
|
const suite = testLocation.slice(0, testLocation.length - 1).join(' > ');
|
|
|
|
|
|
|
|
return {
|
|
|
|
currentRetry: test.currentRetry(),
|
|
|
|
duration: test.duration,
|
|
|
|
speed: test.speed,
|
|
|
|
file: getTestFile(test),
|
|
|
|
suite,
|
|
|
|
title: test.title,
|
|
|
|
err,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the test path in the suite herarchy
|
|
|
|
* @example
|
|
|
|
* ['Root Suite in a file', 'Nested suite', 'test title']
|
|
|
|
*
|
|
|
|
* @param {Mocha.Test} test
|
|
|
|
* @returns {Array<String>}
|
|
|
|
*/
|
|
|
|
function getTestLocation(test) {
|
|
|
|
let path = test.title ? [test.title] : [];
|
|
|
|
|
|
|
|
if (test.parent) {
|
|
|
|
path = getTestLocation(test.parent).concat(path);
|
|
|
|
}
|
|
|
|
|
|
|
|
return path;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the relative path to the executed spec file
|
|
|
|
* @param {Mocha.Test} test
|
|
|
|
* @returns {String | null}
|
|
|
|
*/
|
|
|
|
function getTestFile(test) {
|
|
|
|
if (test?.file) {
|
|
|
|
return test?.file;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (test?.parent) {
|
|
|
|
return getTestFile(test.parent);
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = LogReporter;
|