terra-toolkit
Version:
Utilities to help when developing terra modules.
336 lines (314 loc) • 10.3 kB
JavaScript
const events = require('events');
const path = require('path');
const fs = require('fs');
const Logger = require('../../scripts/utils/logger');
const LOG_CONTEXT = '[Terra-Toolkit:terra-wdio-spec-details-reporter]';
const detailEvents = {
latestScreenshot: 'terra-wdio:latest-screenshot',
runnerStart: 'runner:start',
suitStart: 'suite:start',
testStart: 'test:start',
testPass: 'test:pass',
testFail: 'test:fail',
testEnd: 'test:end',
runnerEnd: 'runner:end',
};
class TerraWDIOTestDetailsReporter extends events.EventEmitter {
constructor(globalConfig, options) {
super(globalConfig);
this.options = options;
this.fileName = '';
this.resultJsonObject = {
locale: '',
theme: '',
formFactor: '',
capabilities: {
browserName: '',
},
specs: {},
};
this.moduleName = '';
this.setResultsDir.bind(this);
this.hasResultsDir.bind(this);
this.writeToFile.bind(this);
this.setTestModule = this.setTestModule.bind(this);
this.title = '';
this.state = '';
this.error = {};
this.screenshots = [];
this.specHashData = {};
this.nonMonoRepoResult = [];
this.resultsDir = '';
this.registerListeners = this.registerListeners.bind(this);
this.registerListeners();
}
registerListeners() {
this.on(detailEvents.runnerStart, this.runnerStart.bind(this));
this.on(detailEvents.latestScreenshot, this.latestScreenshot.bind(this));
this.on(detailEvents.suitStart, this.suitStart.bind(this));
this.on(detailEvents.testStart, this.testStart.bind(this));
this.on(detailEvents.testPass, this.testPass.bind(this));
this.on(detailEvents.testFail, this.testFail.bind(this));
this.on(detailEvents.testEnd, this.testEnd.bind(this));
this.on(detailEvents.runnerEnd, this.runnerEnd.bind(this));
}
latestScreenshot(screenshotPath) {
if (!this.screenshots.includes(screenshotPath.screenshotPath)) {
this.screenshots.push(screenshotPath.screenshotPath);
}
}
runnerStart(runner) {
this.setTestModule(runner.specs[0]);
this.setResultsDir();
this.hasResultsDir();
this.resultJsonObject.locale = runner.config.locale;
this.resultJsonObject.capabilities.browserName = runner.config.browserName;
this.resultJsonObject.formFactor = runner.config.formFactor;
this.resultJsonObject.theme = runner.capabilities.theme || 'default-theme';
this.fileNameCheck(runner.config, runner.capabilities, this.moduleName);
}
/**
* Format class member specHashData with runner's specHash. If its monorepo formatting the specHash inside the moduleName
* @param {Object} params
*/
suitStart(params) {
const {
specHash, title, parent, fullTitle, cid,
} = params;
const { specHashData, moduleName } = this;
const browser = params.runner[cid].browserName;
if (moduleName) {
if (!specHashData[moduleName]) {
specHashData[moduleName] = {};
}
if (!specHashData[moduleName][specHash]) {
specHashData[moduleName][specHash] = {};
}
if (!specHashData[moduleName][specHash][fullTitle]) {
specHashData[moduleName][specHash][fullTitle] = {
parent: fullTitle === title ? title : parent,
title,
browser,
tests: [],
};
}
} else {
if (!specHashData[specHash]) {
specHashData[specHash] = {};
}
if (!specHashData[specHash][fullTitle]) {
specHashData[specHash][fullTitle] = {
parent: fullTitle === title ? title : parent,
title,
browser,
tests: [],
};
}
}
}
testStart(test) {
this.title = test.title;
}
testPass() {
this.state = 'passed';
}
testFail(test) {
this.error = {
name: 'Error',
message: test.err.message,
stack: test.err.stack,
type: test.err.type,
};
this.state = 'fail';
}
/**
* update specHashData with description, success, screenshots
* @param {Object} test
* @return null
*/
testEnd(test) {
const {
specHash, fullTitle, title,
} = test;
const {
specHashData, moduleName, state, title: testTitle,
} = this;
const specParent = fullTitle.replace(` ${title}`, '');
const testInfo = {
title: testTitle,
state,
screenshots: this.screenshots,
};
if (state === 'fail') {
testInfo.error = this.error;
}
if (
moduleName
&& specHashData[moduleName][specHash]
&& specHashData[moduleName][specHash][specParent]
) {
const { tests } = specHashData[moduleName][specHash][specParent];
tests.push(testInfo);
} else if (specHashData[specHash] && specHashData[specHash][specParent]) {
specHashData[specHash][specParent].tests.push(testInfo);
}
this.screenshots = [];
}
/**
* Format resultJsonObject based on parent and nest the tests
* @return null
*/
runnerEnd(runner) {
const specData = this.moduleName && this.specHashData[this.moduleName] ? this.specHashData[this.moduleName] : this.specHashData;
Object.values(specData).forEach((spec) => {
const revSpecs = Object.values(spec);
revSpecs.forEach((test, i) => {
if (test.parent === test.title) {
const { title, parent, ...rest } = revSpecs[i];
revSpecs[i] = {
title,
spec: runner.specs[0],
...rest,
};
}
if (test.parent !== test.title) {
const parentIndex = [...revSpecs].slice(0, i + 1).map(el => el.title).lastIndexOf(test.parent);
if (parentIndex > -1) {
if (!revSpecs[parentIndex].suites) {
revSpecs[parentIndex].suites = [];
}
revSpecs[parentIndex].suites.push(test);
// eslint-disable-next-line no-param-reassign
delete test.parent;
}
}
// eslint-disable-next-line no-param-reassign
delete test.parent;
// eslint-disable-next-line no-param-reassign
delete test.browser;
});
if (this.moduleName) {
const filePathLocation = path.join(
this.resultsDir,
`${this.fileName}.json`,
);
if (this.resultJsonObject.specs[this.moduleName] && this.resultJsonObject.specs[this.moduleName].length) {
this.resultJsonObject.specs[this.moduleName] = this.resultJsonObject.specs[this.moduleName].concat(revSpecs.filter(item => item.spec));
} else {
this.resultJsonObject.specs[this.moduleName] = revSpecs.filter(item => item.spec);
}
this.writeToFile(
this.resultJsonObject.specs[this.moduleName],
filePathLocation,
);
} else {
this.nonMonoRepoResult.push(...revSpecs.filter(item => item.spec));
}
});
if (!this.moduleName) {
this.resultJsonObject.specs = this.nonMonoRepoResult.filter(item => item.browser === this.resultJsonObject.capabilities.browserName).map((elem) => {
const { browser, ...rest } = elem;
return { ...rest };
});
const filePathLocation = path.join(
this.resultsDir,
`${this.fileName}.json`,
);
this.writeToFile(this.resultJsonObject, filePathLocation);
}
this.screenshots = [];
this.specHashData = {};
}
/**
* Set the package name to moduleName property if specsValue contains /package string
* @param {string} specsValue - File path of current spec file from runners
* @return null
*/
setTestModule(specsValue) {
const index = specsValue.lastIndexOf('packages/');
if (index > -1) {
const testFilePath = specsValue.substring(index).split(path.sep);
const moduleName = testFilePath && testFilePath[1]
? testFilePath[1]
: process.cwd().split(path.sep).pop();
if (moduleName && moduleName !== this.moduleName) {
this.moduleName = moduleName;
}
}
}
/**
* Check and create reports dir if doesn't exist
* @return null
*/
hasResultsDir() {
if (!fs.existsSync(this.resultsDir)) {
fs.mkdirSync(this.resultsDir, { recursive: true }, (err) => {
if (err) {
Logger.error(err.message, { context: LOG_CONTEXT });
}
});
}
}
/**
* Sets results directory for the test run. Uses the wdio reporterOptions.outputDir if set, otherwise
* it outputs to tests?/wdio/reports.
* @return null;
*/
setResultsDir() {
const { reporterOptions } = this.options;
if (reporterOptions && reporterOptions.outputDir) {
this.resultsDir = reporterOptions.outputDir;
} else {
let testDir = 'tests';
if (fs.existsSync(path.join(process.cwd(), 'test'))) {
testDir = 'test';
}
this.resultsDir = path.join(process.cwd(), testDir, 'wdio', 'reports');
}
}
/**
* Formatting the filename based on locale, theme, and formFactor
* @return null
*/
fileNameCheck({ formFactor, locale, theme }, { browserName }, moduleName) {
const fileNameConf = ['functional-test-details'];
if (moduleName) {
fileNameConf.push(moduleName);
}
if (locale) {
fileNameConf.push(locale);
this.resultJsonObject.locale = locale;
}
if (theme) {
fileNameConf.push(theme);
this.resultJsonObject.theme = theme;
}
if (formFactor) {
fileNameConf.push(formFactor);
this.resultJsonObject.formFactor = formFactor;
}
if (browserName) {
fileNameConf.push(browserName);
this.resultJsonObject.capabilities.browserName = browserName;
}
this.fileName = fileNameConf.join('-');
}
/**
* writes data to the file
* @param {string} data - Data that should be written to the file
* @param {string} filePath - Location of the file
* @return null
*/
// eslint-disable-next-line class-methods-use-this
writeToFile(data, filePath) {
try {
const json = `${JSON.stringify(data, null, 2)}`;
const options = { flag: 'w+' };
fs.writeFileSync(filePath, json, options);
} catch (err) {
Logger.error(err.message, { context: LOG_CONTEXT });
}
}
}
TerraWDIOTestDetailsReporter.reporterName = 'TerraWDIOTestDetailsReporter';
module.exports = TerraWDIOTestDetailsReporter;