diff --git a/README.MD b/README.MD index 917adb37..f7105125 100644 --- a/README.MD +++ b/README.MD @@ -113,18 +113,21 @@ The directory in which the report needs to be saved, relative from where the scr ### `staticFilePath` - **Type:** `boolean` +- **Default:** `false` - **Mandatory:** No If true each feature will get a static filename for the html. Use this feature only if you are not running multiple instances of the same tests. ### `openReportInBrowser` - **Type:** `boolean` +- **Default:** `false` - **Mandatory:** No If true the report will automatically be opened in the default browser of the operating system. ### `saveCollectedJSON` - **Type:** `boolean` +- **Default:** `false` - **Mandatory:** No This module will first merge all the JSON-files to 1 file and then enrich it with data that is used for the report. If `saveCollectedJSON :true` the merged JSON **AND** the enriched JSON will be saved in the `reportPath`. They will be saved as: @@ -134,8 +137,8 @@ This module will first merge all the JSON-files to 1 file and then enrich it wit ### `disableLog` - **Type:** `boolean` -- **Mandatory:** No - **Default:** `false` +- **Mandatory:** No This will disable the log so will **NOT** see this. @@ -165,8 +168,28 @@ You can change the report name to a name you want You can customise Page Footer if required. You just need to provide a html string like `

A custom footer in html

` +### `plainDescription` +- **Type:** `boolean` +- **Default:** `false` +- **Mandatory:** No + +The feature description is assumed to be a simple string and the library formats it accordingly, by copying it inside a +paragraph tag. Since the description can be any free text, it can also be as complex as a full `div`, e.g.: + +```html +
+

Test description : The test implements comparisons using all our datatypes

+

Expected result : Device does not reset

+

Feature type : Robustness

+

Comments : Test covers comparison operators

+
+``` + +If the description already include formatting tags you can include it _as is_ by setting `plainDescription` to `true`. + ### `displayDuration` - **Type:** `boolean` +- **Default:** `false` - **Mandatory:** No If set to `true` the duration of steps, scenarios and features is displayed on the Features overview and single feature page in an easily readable format. @@ -198,6 +221,7 @@ If set to `true` the date and time at which the JSON-files were generated, is di ### `useCDN` - **Type:** `boolean` +- **Default:** `false` - **Mandatory:** No If you prefer, you can use CDN for assets. @@ -206,14 +230,35 @@ If you prefer, you can use CDN for assets. - **Type:** `path` - **Mandatory:** No -If you need add some custom style to your report. Add it like this `customStyle: 'your-path-where/custom.css'` +If you need to add some custom style to your report. Add it like this `customStyle: 'your-path-where/custom.css'` +Customization is now possible also for the doughnut chart by using [CSS variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties). +The user can define colors for the chart by defining variables for the different categories as follows: +```css +:root { + --ambiguous-color:#AAAAAA; + --failed-color:#BBBBBB; + --not-defined-color:#CCCCCC; + --passed-color:#DDDDDD; + --pending-color:#EEEEEE; + --skipped-color:#FFFFFF; +} +``` +Please note that these colors do _not_ affect the main colors CSS. To have homogeneous styling you can simply link those +colors to the variables, e.g.: + +```css +.ambiguous-color { + color: var(--ambiguous-color) !important; +} +``` +Please refer to the `test` directory and the `embedded-array` test report for a complete color customization example. ### `overrideStyle` - **Type:** `path` - **Mandatory:** No -If you need replace default style for your report. Add it like this `overrideStyle: 'your-path-where/custom.css'` - +If you need to replace completely the default style for your report. Add it like this `overrideStyle: 'your-path-where/custom.css'` +Please refer to the `test` directory for an example. ### `metadata` - **Type:** `JSON` @@ -242,6 +287,7 @@ See [metadata](./README.MD#metadata-1) for more info ### `customMetadata` - **Type:** `boolean` +- **Default:** `false` - **Mandatory:** No It is possible to provide custom metadata by setting this variable to `true`. Custom metadata will override the regular metadata completely and potentially have strange formatting bugs if too many (10+) variables are used. @@ -420,7 +466,7 @@ You can attach screenshots at any time to a Cucumber JSON file. Just create a Cu ### How to attach Plain Text to HTML report You can attach plain-text / data at any time to a Cucumber JSON file to help debug / review the results. You can add them during running or when a `scenario` failed. -> Check the framework you are using to attach plain text to the JSON file. +> Check the framework you are using to attach plain text to the JSON file. Please make sure to convert binary/non-readable data to a suitable textual representation, e.g. via Base64 encoding. ### How to attach pretty JSON to HTML report You can attach JSON at any time to a Cucumber JSON file. You can add them during running or when a `scenario` failed. diff --git a/lib/collect-jsons.js b/lib/collect-jsons.js index b75d3bb9..e2b5d800 100644 --- a/lib/collect-jsons.js +++ b/lib/collect-jsons.js @@ -58,11 +58,11 @@ module.exports = function collectJSONS(options) { json.metadata.reportTime = formatToLocalIso(json.metadata.reportTime) } - // Only check the feature hooks if there are elements (fail safe) + // Only check the feature hooks if there are elements (fail-safe) const {elements} = json; if (elements) { - const scenarios = elements.map(scenario => { + json.elements = elements.map(scenario => { const {before, after} = scenario; if (before) { @@ -74,8 +74,6 @@ module.exports = function collectJSONS(options) { return scenario }) - - json.elements = scenarios } jsonOutput.push(json) diff --git a/lib/generate-report.js b/lib/generate-report.js index 4b060710..0363a3df 100755 --- a/lib/generate-report.js +++ b/lib/generate-report.js @@ -49,21 +49,22 @@ function generateReport(options) { const customMetadata = options.customMetadata || false; const customData = options.customData || null; + const plainDescription = !!options.plainDescription; const style = options.overrideStyle || REPORT_STYLESHEET; const customStyle = options.customStyle; - const disableLog = options.disableLog; - const openReportInBrowser = options.openReportInBrowser; + const disableLog = !!options.disableLog; + const openReportInBrowser = !!options.openReportInBrowser; const reportName = options.reportName || DEFAULT_REPORT_NAME; const reportPath = path.resolve(process.cwd(), options.reportPath); - const saveCollectedJSON = options.saveCollectedJSON; - const displayDuration = options.displayDuration || false; - const displayReportTime = options.displayReportTime || false; - const durationInMS = options.durationInMS || false; - const hideMetadata = options.hideMetadata || false; + const saveCollectedJSON = !!options.saveCollectedJSON; + const displayDuration = !!options.displayDuration; + const displayReportTime = !!options.displayReportTime; + const durationInMS = !!options.durationInMS; + const hideMetadata = !!options.hideMetadata; const pageTitle = options.pageTitle || 'Multiple Cucumber HTML Reporter'; - const pageFooter = options.pageFooter || false; - const useCDN = options.useCDN || false; - const staticFilePath = options.staticFilePath || false; + const pageFooter = !!options.pageFooter; + const useCDN = !!options.useCDN; + const staticFilePath = !!options.staticFilePath; fs.ensureDirSync(reportPath); fs.ensureDirSync(path.resolve(reportPath, FEATURE_FOLDER)); @@ -95,7 +96,7 @@ function generateReport(options) { total: 0, ambiguousPercentage: 0, failedPercentage: 0, - notdefinedPercentage: 0, + notDefinedPercentage: 0, pendingPercentage: 0, skippedPercentage: 0, passedPercentage: 0 @@ -118,7 +119,7 @@ function generateReport(options) { // Percentages suite.featureCount.ambiguousPercentage = _calculatePercentage(suite.featureCount.ambiguous, suite.featureCount.total); suite.featureCount.failedPercentage = _calculatePercentage(suite.featureCount.failed, suite.featureCount.total); - suite.featureCount.notdefinedPercentage = _calculatePercentage(suite.featureCount.notDefined, suite.featureCount.total); + suite.featureCount.notDefinedPercentage = _calculatePercentage(suite.featureCount.notDefined, suite.featureCount.total); suite.featureCount.pendingPercentage = _calculatePercentage(suite.featureCount.pending, suite.featureCount.total); suite.featureCount.skippedPercentage = _calculatePercentage(suite.featureCount.skipped, suite.featureCount.total); suite.featureCount.passedPercentage = _calculatePercentage(suite.featureCount.passed, suite.featureCount.total); @@ -168,7 +169,7 @@ function generateReport(options) { ambiguous: 0, passedPercentage: 0, failedPercentage: 0, - notdefinedPercentage: 0, + notDefinedPercentage: 0, skippedPercentage: 0, pendingPercentage: 0, ambiguousPercentage: 0, @@ -182,7 +183,7 @@ function generateReport(options) { feature.isNotdefined = false; feature.isPending = false; suite.featureCount.total++; - var idPrefix = staticFilePath ? '' : `${ uuid() }.` ; + const idPrefix = staticFilePath ? '' : `${ uuid() }.` ; feature.id = `${ idPrefix }${ feature.id }`.replace(/[^a-zA-Z0-9-_]/g, '-'); feature.app = 0; feature.browser = 0; @@ -225,13 +226,13 @@ function generateReport(options) { // Percentages feature.scenarios.ambiguousPercentage = _calculatePercentage(feature.scenarios.ambiguous, feature.scenarios.total); feature.scenarios.failedPercentage = _calculatePercentage(feature.scenarios.failed, feature.scenarios.total); - feature.scenarios.notdefinedPercentage = _calculatePercentage(feature.scenarios.notDefined, feature.scenarios.total); + feature.scenarios.notDefinedPercentage = _calculatePercentage(feature.scenarios.notDefined, feature.scenarios.total); feature.scenarios.passedPercentage = _calculatePercentage(feature.scenarios.passed, feature.scenarios.total); feature.scenarios.pendingPercentage = _calculatePercentage(feature.scenarios.pending, feature.scenarios.total); feature.scenarios.skippedPercentage = _calculatePercentage(feature.scenarios.skipped, feature.scenarios.total); suite.scenarios.ambiguousPercentage = _calculatePercentage(suite.scenarios.ambiguous, suite.scenarios.total); suite.scenarios.failedPercentage = _calculatePercentage(suite.scenarios.failed, suite.scenarios.total); - suite.scenarios.notdefinedPercentage = _calculatePercentage(suite.scenarios.notDefined, suite.scenarios.total); + suite.scenarios.notDefinedPercentage = _calculatePercentage(suite.scenarios.notDefined, suite.scenarios.total); suite.scenarios.passedPercentage = _calculatePercentage(suite.scenarios.passed, suite.scenarios.total); suite.scenarios.pendingPercentage = _calculatePercentage(suite.scenarios.pending, suite.scenarios.total); suite.scenarios.skippedPercentage = _calculatePercentage(suite.scenarios.skipped, suite.scenarios.total); @@ -332,45 +333,38 @@ function generateReport(options) { function _parseSteps(scenario) { scenario.steps.forEach(step => { if (step.embeddings !== undefined) { - const Base64 = require('js-base64').Base64; - step.attachments = []; step.embeddings.forEach((embedding, embeddingIndex) => { /* istanbul ignore else */ if (embedding.mime_type === 'application/json' || embedding.media && embedding.media.type === 'application/json') { step.json = (step.json ? step.json : []).concat([typeof embedding.data === 'string' ? JSON.parse(embedding.data) : embedding.data]); } else if (embedding.mime_type === 'text/html' || (embedding.media && embedding.media.type === 'text/html')) { - step.html = (step.html ? step.html : []).concat([ - _isBase64(embedding.data) ? Base64.decode(embedding.data) : - embedding.data - ]); + step.html = (step.html ? step.html : []).concat([embedding.data]); } else if (embedding.mime_type === 'text/plain' || (embedding.media && embedding.media.type === 'text/plain')) { - step.text = (step.text ? step.text : []).concat([ - _isBase64(embedding.data) ? _escapeHtml(Base64.decode(embedding.data)) : _escapeHtml(embedding.data) - ]); + step.text = (step.text ? step.text : []).concat([_escapeHtml(embedding.data)]); } else if (embedding.mime_type === 'image/png' || (embedding.media && embedding.media.type === 'image/png')) { step.image = (step.image ? step.image : []).concat([ 'data:image/png;base64,' + embedding.data ]); step.embeddings[ embeddingIndex ] = {}; } else { - let embeddingtype = 'text/plain'; + let embeddingType = 'text/plain'; if (embedding.mime_type) { - embeddingtype = embedding.mime_type; + embeddingType = embedding.mime_type; } else if (embedding.media && embedding.media.type) { - embeddingtype = embedding.media.type; + embeddingType = embedding.media.type; } step.attachments.push({ - data: 'data:' + embeddingtype + ';base64,' + embedding.data, - type: embeddingtype + data: 'data:' + embeddingType + ';base64,' + embedding.data, + type: embeddingType }); step.embeddings[ embeddingIndex ] = {}; } }); } - if (step.doc_string !== undefined) { - step.id = `${uuid()}.${scenario.id}.${step.name}`.replace(/[^a-zA-Z0-9-_]/g, '-'); - step.restWireData = _escapeHtml(step.doc_string.value).replace(new RegExp('\r?\n', 'g'), '
'); - } + if (step.doc_string !== undefined) { + step.id = `${uuid()}.${scenario.id}.${step.name}`.replace(/[^a-zA-Z0-9-_]/g, '-'); + step.restWireData = _escapeHtml(step.doc_string.value).replace(new RegExp('\r?\n', 'g'), '
'); + } if (!step.result // Don't log steps that don't have a text/hidden/images/attachments unless they are failed. @@ -430,27 +424,6 @@ function generateReport(options) { } } - /** - * Check if the string a base64 string - * @param string - * @return {boolean} - * @private - */ - function _isBase64(string) { - const notBase64 = /[^A-Z0-9+\/=]/i; - const stringLength = string.length; - - if (!stringLength || stringLength % 4 !== 0 || notBase64.test(string)) { - return false; - } - - const firstPaddingChar = string.indexOf('='); - - return firstPaddingChar === -1 || - firstPaddingChar === stringLength - 1 || - (firstPaddingChar === stringLength - 2 && string[ stringLength - 1 ] === '='); - } - /** * Escape html in string * @param string @@ -553,9 +526,10 @@ function generateReport(options) { pageTitle: pageTitle, reportName: reportName, pageFooter: pageFooter, + plainDescription: plainDescription }) ); - // Copy the assets, but first check if they don't exists and not useCDN + // Copy the assets, but first check if they don't exist and not useCDN if (!fs.pathExistsSync(path.resolve(reportPath, 'assets')) && !suite.useCDN) { fs.copySync( path.resolve(path.dirname(require.resolve('../package.json')), 'templates/assets'), diff --git a/package-lock.json b/package-lock.json index 0554e619..5505958a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1476,11 +1476,6 @@ "integrity": "sha512-zl0nZWDrmbCiKns0NcjkFGYkVTGCPUgoHypTaj+G2AzaWus7QGoXARSlYsSle2VRpSdfJmM+hzmFKzQNhF2kHg==", "dev": true }, - "js-base64": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz", - "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==" - }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", diff --git a/package.json b/package.json index 310b87e3..460cf3c2 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,6 @@ "chalk": "^3.0.0", "find": "^0.3.0", "fs-extra": "^8.1.0", - "js-base64": "^2.5.1", "jsonfile": "^5.0.0", "lodash": "^4.17.19", "moment": "^2.24.0", diff --git a/templates/assets/js/Chart.style.js b/templates/assets/js/Chart.style.js new file mode 100644 index 00000000..0b2bb0ee --- /dev/null +++ b/templates/assets/js/Chart.style.js @@ -0,0 +1,16 @@ +function getChartColors() { + const colorsMap = [ + {colorVar: "--passed-color", defaultColor: "#26B99A"}, + {colorVar: "--failed-color", defaultColor: "#E74C3C"}, + {colorVar: "--pending-color", defaultColor: "#FFD119"}, + {colorVar: "--skipped-color", defaultColor: "#3498DB"}, + {colorVar: "--ambiguous-color", defaultColor: "#b73122"}, + {colorVar: "--not-defined-color", defaultColor: "#F39C12"}, + ]; + const colors = [] + const style = window.getComputedStyle(document.body); + for (let i = 0; i < colorsMap.length; i++) { + colors.push(style.getPropertyValue(colorsMap[i].colorVar) || colorsMap[i].defaultColor) + } + return colors +} \ No newline at end of file diff --git a/templates/components/features-overview.chart.tmpl b/templates/components/features-overview.chart.tmpl index a59fa9df..67ab6927 100644 --- a/templates/components/features-overview.chart.tmpl +++ b/templates/components/features-overview.chart.tmpl @@ -70,7 +70,7 @@ Not Defined

- <%= suite.featureCount.notdefinedPercentage %> % + <%= suite.featureCount.notDefinedPercentage %> % <%}%> <%if(suite.featureCount.pending > 0){%> diff --git a/templates/components/scenarios-overview.chart.tmpl b/templates/components/scenarios-overview.chart.tmpl index 0795085a..421ff242 100644 --- a/templates/components/scenarios-overview.chart.tmpl +++ b/templates/components/scenarios-overview.chart.tmpl @@ -68,7 +68,7 @@ Not defined

- <%= scenarios.notdefinedPercentage %> % + <%= scenarios.notDefinedPercentage %> % <% } %> diff --git a/templates/components/scenarios.tmpl b/templates/components/scenarios.tmpl index 4ec208d8..9298f93f 100755 --- a/templates/components/scenarios.tmpl +++ b/templates/components/scenarios.tmpl @@ -153,7 +153,11 @@ + Show Error <% } %> - <% if (step.text || step.json) { %> + <% if (step.json) { %> + + Show Info + <% } %> + + <% if (step.text) { %> + Show Info <% } %> @@ -186,7 +190,7 @@ <% } %> <% if (step.json) { %> -
+
<% try { %><%= JSON.stringify(step.json, undefined, 2) %><% } catch (error) { %><%= step.json %><% } %>
<% } %> diff --git a/templates/feature-overview.index.tmpl b/templates/feature-overview.index.tmpl index 0a287e4e..c1716c85 100644 --- a/templates/feature-overview.index.tmpl +++ b/templates/feature-overview.index.tmpl @@ -67,7 +67,7 @@ -
+
@@ -82,14 +82,18 @@

Feature: <%= feature.name %>

-

<% if (feature.description && feature.description.length > 0) { %> - Description: <%= feature.description %>

+ <% if (!plainDescription) { %> +

<% if (feature.description && feature.description.length > 0) { %> + Description: <%= feature.description %>

+ <% } %> +

File name: + <%= Array.from(feature.uri.replace(/\\/g,'/').split('/')).pop() %> +

+

Relative path: + <%= Array.from(feature.uri.replace(/\\/g,'/').split('/')).slice(-2).join('/') %>

+ <% } else { %> + <%= feature.description %> <% } %> -

File name: - <%= Array.from(feature.uri.replace(/\\/g,'/').split('/')).pop() %> -

-

Relative path: - <%= Array.from(feature.uri.replace(/\\/g,'/').split('/')).slice(-2).join('/') %>

@@ -164,6 +168,7 @@ <% } %> +