From 45c57440553c8d324d886c20b11566c39da5635b Mon Sep 17 00:00:00 2001 From: Alex Tiley Date: Sun, 10 Jun 2018 14:03:58 +0100 Subject: [PATCH] Add support for multiple JSON outputs --- README.md | 13 ++++++- package.json | 2 +- src/JSONBuilder.js | 96 ++++++++++++++++++++++++++++++++++++++-------- src/reporter.js | 52 +++++++++++++++++-------- 4 files changed, 129 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 895a085..d6f1913 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,8 @@ I have adapted and added missing properties to the JSON report. It's quite rough outputDir: 'some/output/dir', cucumberJsonReporter: { silent: true, // true|false - supresses message notifying of report output - baseDir: __dirname // Should be your project's root directory, used to determine where your feature files are stored + baseDir: __dirname, // Should be your project's root directory, used to determine where your feature files are stored + deviceName: 'Local test environment' // Meta data for multiple-cucumber-html-reporter } } } @@ -31,3 +32,13 @@ I have adapted and added missing properties to the JSON report. It's quite rough ## Bug reporting ## Feel free to raise a pull request, or throw me a ticket via the issues section. + +## Known issues / missing features ## + +* Add support for screenshots via cucumber attachments/"embeddings" +* Add browser name to feature metadata +* Add metadata for test start time, end time and total duration +* Add metadata for feature count, scenario/scenario outline counts and step counts +* Add metadata for failing test count +* Step duration and line number is experimental - It's nigh on impossible to fetch the correct step data if the step contains a variable +* Svme report file names based on browser name and timestamp \ No newline at end of file diff --git a/package.json b/package.json index b83535d..9ffdc46 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wdio-json-cucumber-reporter", - "version": "1.0.5", + "version": "1.1.0", "description": "JSON Cucumber reporter", "main": "lib/reporter.js", "directories": { diff --git a/src/JSONBuilder.js b/src/JSONBuilder.js index 9e8169f..d04c1fe 100644 --- a/src/JSONBuilder.js +++ b/src/JSONBuilder.js @@ -1,10 +1,22 @@ +import os from 'os'; + class JSONBuilder { constructor() { - this.features = []; + this.reports = {}; + } + + initialiseReport(cid) { + if (typeof this.reports[cid] === 'undefined') { + this.reports[cid] = {}; + this.reports[cid].features = []; + } + return this.reports[cid]; } addFeature(options) { - this.features.push({ + const report = this.initialiseReport(options.cid); + + report.features.push({ keyword: options.keyword, type: options.type, name: options.name, @@ -19,8 +31,10 @@ class JSONBuilder { addScenario(options) { - const featureIndex = this.features.findIndex(feature => feature.id === options.parentId); - const scenarioIndex = this.features[featureIndex].elements + const report = this.initialiseReport(options.cid); + + const featureIndex = report.features.findIndex(feature => feature.id === options.parentId); + const scenarioIndex = report.features[featureIndex].elements .findIndex(scenario => scenario.id === options.id); const scenarioData = { @@ -36,19 +50,21 @@ class JSONBuilder { }; if (scenarioIndex === -1) { - this.features[featureIndex].elements.push(scenarioData); + report.features[featureIndex].elements.push(scenarioData); } } addStep(options) { - const featureIndex = this.features + const report = this.initialiseReport(options.cid); + + const featureIndex = report.features .findIndex(feature => feature.elements.find(scenario => scenario.id === options.parentId)); - const scenarioIndex = this + const scenarioIndex = report .features[featureIndex] .elements.findIndex(scenario => scenario.id === options.parentId); - const stepIndex = this + const stepIndex = report .features[featureIndex] .elements[scenarioIndex] .steps.findIndex(step => step.id === options.id); @@ -61,21 +77,28 @@ class JSONBuilder { tags: options.tags, uri: options.uri, result: options.result, - embeddings: options.embeddings, + embeddings: options.embeddings.map(embedding => ({ + data: embedding.data, + media: { + type: embedding.mimeType, + }, + })), }; if (stepIndex === -1) { - this.features[featureIndex].elements[scenarioIndex].steps.push(stepData); + report.features[featureIndex].elements[scenarioIndex].steps.push(stepData); } else { - this.features[featureIndex].elements[scenarioIndex].steps[stepIndex] = stepData; + report.features[featureIndex].elements[scenarioIndex].steps[stepIndex] = stepData; } } addHook(options) { - const featureIndex = this.features + const report = this.initialiseReport(options.cid); + + const featureIndex = report.features .findIndex(feature => feature.elements.find(scenario => scenario.id === options.parentId)); - const scenarioIndex = this + const scenarioIndex = report .features[featureIndex] .elements.findIndex(scenario => scenario.id === options.parentId); @@ -95,11 +118,54 @@ class JSONBuilder { })), }; - this.features[featureIndex].elements[scenarioIndex].steps.push(stepData); + report.features[featureIndex].elements[scenarioIndex].steps.push(stepData); + } + + /** + * @todo add further meta data + * test execution start/end/total + * feature|scenario|scenariooverview|step counts + * failing test count + * passing test count + * @see https://github.com/evrycollin/wdio-allure-addons-reporter/issues/1 for an idea of how to get more data + */ + addMeta(options) { + const report = this.initialiseReport(options.cid); + const platformId = this.getPlatformId(); + + report.features.forEach((feature) => { + feature.metadata = { + browser: { + name: options.browser, + // @todo - check if it's possible to get the browser version from wdio events + version: options.browser.charAt(0).toUpperCase() + options.browser.slice(1), + }, + device: options.deviceName, + platform: { + name: platformId, + version: `${os.type()} ${os.release()}`, + }, + }; + }) } clearEmptyFeatures() { - this.features = this.features.filter(feature => feature.elements.length > 0); + Object.keys(this.reports).forEach((key) => { + this.reports[key].features = this.reports[key].features.filter( + feature => feature.elements.length > 0 + ); + }); + } + + /** + * Grabs meta data about the OS for the report output + */ + getPlatformId() { + switch (process.platform) { + case 'darwin': return 'osx'; + case 'win32': return 'windows'; + default: return 'linux'; + } } } diff --git a/src/reporter.js b/src/reporter.js index 61c60a8..0f4c349 100644 --- a/src/reporter.js +++ b/src/reporter.js @@ -29,17 +29,8 @@ class CucumberJSONReporter extends EventEmitter { this.reportIdentifier = 0; this.jsonBuilder = new JSONBuilder(); - // this.on('suite:end', (suite) => { - // This is a feature, not a scenario - // if (suite.parent === null) { - // console.log('suite:end'); - // console.log(suite); - // } - // }); - /** - * @todo Collect unique reports for each browsing session or 'capability' - * At the moment this hook is only called once. Rewrite to collect multiple JSON's and use suite:end + * Once all tests completed, iterate over reports and generate multiple JSON reports */ this.on('end', () => { @@ -50,29 +41,49 @@ class CucumberJSONReporter extends EventEmitter { try { const dir = path.resolve(this.options.outputDir); - const filename = (this.reportIdentifier === 0 ? 'report' : `report_${this.reportIdentifier}`) + '.json'; - const filepath = path.join(dir, filename); mkdirp.sync(dir); this.jsonBuilder.clearEmptyFeatures(); - fs.writeFileSync(filepath, JSON.stringify(this.jsonBuilder.features)); + Object.keys(this.jsonBuilder.reports).forEach((cid) => { + const report = this.jsonBuilder.reports[cid]; + const filename = (this.reportIdentifier === 0 ? 'report' : `report_${this.reportIdentifier}`) + '.json'; + const filepath = path.join(dir, filename); - if (this.options.cucumberJsonReporter.silent !== true) { - console.log(`Wrote json report to [${this.options.outputDir}].`); - } - this.reportIdentifier += 1; + fs.writeFileSync(filepath, JSON.stringify(report.features)); + + if (this.options.cucumberJsonReporter.silent !== true) { + console.log(`Wrote json report '${filename}' to [${this.options.outputDir}].`); + } + this.reportIdentifier += 1; + }); } catch (e) { console.log(`Failed to write json report to [${this.options.outputDir}]. Error: ${e}`); } }); + /** + * Decorate features with meta data whenever a feature finishes + */ + this.on('suite:end', (suite) => { + if (suite.parent === null) { + this.jsonBuilder.addMeta({ + cid: suite.cid, + browser: suite.runner[suite.cid].browserName, + deviceName: this.options.cucumberJsonReporter.deviceName + ? this.options.cucumberJsonReporter.deviceName + : 'Local test environment' + }); + } + }); + this.on('suite:start', (test) => { if (test.parent) { const scenario = this.getScenario(test.file, test.title); this.jsonBuilder.addScenario({ + cid: test.cid, type: 'scenario', keyword: scenario.keyword, description: scenario.description, @@ -87,6 +98,7 @@ class CucumberJSONReporter extends EventEmitter { const feature = this.getFeature(test.file); this.jsonBuilder.addFeature({ + cid: test.cid, type: 'feature', keyword: feature.keyword, name: feature.name, @@ -104,6 +116,7 @@ class CucumberJSONReporter extends EventEmitter { const step = this.getStep(test.file, test.title); const stepData = { + cid: test.cid, type: 'step', name: test.title, id: test.uid, @@ -116,6 +129,7 @@ class CucumberJSONReporter extends EventEmitter { status: 'passed', duration: test.duration * 1000000, }, + embeddings: [] }; if (stepData.keyword === 'After' || stepData.keyword === 'Before') { @@ -129,6 +143,7 @@ class CucumberJSONReporter extends EventEmitter { const step = this.getStep(test.file, test.title); const stepData = { + cid: test.cid, type: 'step', name: test.title, id: test.uid, @@ -141,6 +156,7 @@ class CucumberJSONReporter extends EventEmitter { status: 'failed', duration: test.duration * 1000000, }, + embeddings: [], }; if (test.err.stack) { @@ -160,6 +176,7 @@ class CucumberJSONReporter extends EventEmitter { const step = this.getStep(test.file, test.title); const stepData = { + cid: test.cid, type: 'step', name: test.title, id: test.uid, @@ -172,6 +189,7 @@ class CucumberJSONReporter extends EventEmitter { status: 'skipped', duration: test.duration * 1000000, }, + embeddings: [], }; if (stepData.keyword === 'After' || stepData.keyword === 'Before') {