cypress-sonarqube-reporter
Version:
A SonarQube XML reporter for Cypress
181 lines (169 loc) • 5.75 kB
JavaScript
const chalk = require('chalk');
const fse = require('fs-extra');
const path = require('path');
const pkg = require('../package.json');
/**
* Log 'info' message to the console
*
* @param {string} message the info message to log
*/
const info = (message) => {
// eslint-disable-next-line no-console
console.info(`[${chalk.grey(pkg.name)}] ${message}`);
};
/**
* Log 'warning' message to the console
*
* @param {string} message the warn message to log
*/
const warn = (message) => {
// eslint-disable-next-line no-console
console.warn(`[${chalk.yellow(pkg.name)}] ${message}`);
};
/**
* Log 'error' message to the console, and throw an Error
*
* @param {string} message the error message
*/
const throwError = (message) => {
// eslint-disable-next-line no-console
console.error(`[${chalk.red(pkg.name)}] ${message}`);
throw new Error(message);
};
/**
* Extract the spec file path from the suite.
* May ignore the options.useAbsoluteSpecPath if the requested is not available.
* Raise an error if the spec could not be extracted.
*
* @param {object} suite the Mocha suite
* @param {object} options the Reporter Options
* @returns {string} the spec file path
*/
const extractSpecFromSuite = (suite, options) => {
const title = suite.title;
const tag = '[@spec: ';
const index = title?.indexOf(tag);
let spec;
if (index > -1) {
spec = JSON.parse(title.substring(index + tag.length, title.lastIndexOf(']')));
spec = options.useAbsoluteSpecPath ? spec.absolute : spec.relative;
} else if (suite.file && options.preferFileProp) {
spec = suite.file;
} else if (suite.invocationDetails?.absoluteFile || suite.invocationDetails?.relativeFile) {
spec = options.useAbsoluteSpecPath ? suite.invocationDetails.absoluteFile : suite.invocationDetails.relativeFile;
} else if (suite.parent?.file) {
spec = suite.parent.file;
} else {
throwError(`could not find spec filename from title: ${title} or from 'suite.invocationDetails'`);
}
return spec.replace(/\\/g, '/');
};
/**
* Extract the suite title from the suite.
*
* @param {object} suite the Mocha suite
* @returns {string} the original suite title
*/
const extractTitleFromSuite = (suite) => {
const title = suite.title;
const index = title.indexOf('[@spec:');
if (index > -1) {
return title.substring(0, index).trim();
} else {
return title;
}
};
/**
* Format the suite title(s)
*
* @param {object} suite the Mocha suite
* @returns {string} the aggregation of title suite(s)
*/
const formatSuiteTitle = (suite, options) => {
if (suite.parent && suite.parent.title !== '') {
return `${formatSuiteTitle(suite.parent, options)}${options.titleSeparator}${extractTitleFromSuite(suite)}`;
} else {
return extractTitleFromSuite(suite);
}
};
/**
* Format the test title
*
* @param {object} test the Mocha test
* @returns {string} the formatted test title
*/
const formatTestTitle = (test, options) => {
if (test.parent && options.useFullTitle) {
return `${formatSuiteTitle(test.parent, options)}${options.titleSeparator}${test.title}`;
} else {
return test.title;
}
};
/**
* Build <testCase> node according to specified test
*
* @param {object} node the XML <file> node
* @param {object} test the Mocha test
*/
const formatTest = (node, test, options) => {
const testNode = node.element('testCase')
.attribute('name', formatTestTitle(test, options))
.attribute('duration', test.duration || 0);
switch (test.state) {
case 'failed':
if (test.err.name === 'AssertionError') {
testNode.element('failure')
.attribute('message', `${test.err.name}: ${test.err.message}`)
.cdata(test.err.stack);
} else {
testNode.element('error')
.attribute('message', `${test.err.name}: ${test.err.message}`)
.cdata(test.err.stack);
}
break;
case 'pending':
testNode.element('skipped')
.attribute('message', 'skipped test');
break;
case 'skipped':
testNode.element('skipped')
.attribute('message', 'An error occurred during a hook and remaining tests in the current suite are skipped');
break;
case 'passed':
// nothing to do...
break;
default:
throwError(`unknown test state: ${test.state}`);
}
};
/**
* Write the report
*
* @param {string} data
*/
const writeFile = (specFilename, data, options) => {
const specFilePath = (options.preserveSpecsDir) ? specFilename : path.basename(specFilename);
const file = path.resolve(options.outputDir, path.dirname(specFilePath), `${options.prefix}${path.basename(specFilePath)}.xml`);
if (!options.overwrite && fse.existsSync(file)) {
throwError(`the reporter '${file}' already exists`);
} else {
try {
fse.outputFileSync(file, data, 'utf8');
info(`report saved to '${file}'`);
} catch(err) {
throwError(`could not write file '${file}': ${err}`);
}
}
};
module.exports = {
DEFAULT_MERGED_FILE_NAME: 'cypress-sonarqube-reports.all.xml',
DEFAULT_OUTPUT_DIR: './dist',
extractSpecFromSuite,
extractTitleFromSuite,
formatTest,
formatTestTitle,
formatSuiteTitle,
info,
warn,
writeFile
};