Skip to content

Commit cf51b85

Browse files
authored
Merge pull request #192 from adamgruber/filename-enhancements
Support token replacements in `reportFilename` option
2 parents c28bb8a + d4c6536 commit cf51b85

File tree

8 files changed

+288
-72
lines changed

8 files changed

+288
-72
lines changed

.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.md

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# mochawesome-report-generator changelog
22

33
## [Unreleased]
4+
### Added
5+
- `reportFilename` option: support replacement tokens (`[name]`, `[status]`, `[datetime]`)
46

57
## [6.0.1] - 2021-11-05
68
### Fixed

README.md

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ mochawesome-report/
7575

7676
Flag | Type | Default | Description
7777
:--- | :--- | :------ | :----------
78-
-f, --reportFilename | string | | Filename of saved report
78+
-f, --reportFilename | string | mochawesome | Filename of saved report. *See [notes](#reportFilename) for available token replacements.*
7979
-o, --reportDir | string | [cwd]/mochawesome-report | Path to save report
8080
-t, --reportTitle | string | mochawesome | Report title
8181
-p, --reportPageTitle | string | mochawesome-report | Browser title
@@ -100,12 +100,34 @@ Flag | Type | Default | Description
100100

101101
*Boolean options can be negated by adding `--no` before the option. For example: `--no-code` would set `code` to `false`.*
102102

103-
#### Overwrite
103+
#### reportFilename replacement tokens
104+
Using the following tokens it is possible to dynamically alter the filename of the generated report.
105+
106+
- **[name]** will be replaced with the spec filename when possible.
107+
- **[status]** will be replaced with the status (pass/fail) of the test run.
108+
- **[datetime]** will be replaced with a timestamp. The format can be - specified using the `timestamp` option.
109+
110+
For example, given the spec `cypress/integration/sample.spec.js` and the following config:
111+
```
112+
{
113+
reporter: "mochawesome",
114+
reporterOptions: {
115+
reportFilename: "[status]_[datetime]-[name]-report",
116+
timestamp: "longDate"
117+
}
118+
}
119+
```
120+
121+
The resulting report file will be named `pass_February_23_2022-sample-report.html`
122+
123+
**Note:** The `[name]` replacement only occurs when mocha is running one spec file per process and outputting a separate report for each spec. The most common use-case is with Cypress.
124+
125+
#### overwrite
104126
By default, report files are overwritten by subsequent report generation. Passing `--overwrite=false` will not replace existing files. Instead, if a duplicate filename is found, the report will be saved with a counter digit added to the filename. (ie. `mochawesome_001.html`).
105127

106128
**Note:** `overwrite` will always be `false` when passing multiple files or using the `timestamp` option.
107129

108-
#### Timestamp
130+
#### timestamp
109131
The `timestamp` option can be used to append a timestamp to the report filename. It uses [dateformat][] to parse format strings so you can pass any valid string that [dateformat][] accepts with a few exceptions. In order to produce valid filenames, the following
110132
replacements are made:
111133

src/bin/cli-main.js

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const fs = require('fs-extra');
22
const path = require('path');
33
const chalk = require('chalk');
44
const t = require('tcomb-validation');
5+
const dateFormat = require('dateformat');
56
const report = require('../lib/main');
67
const types = require('./types');
78
const logger = require('./logger');
@@ -120,25 +121,76 @@ function handleResolved(resolvedValues) {
120121
return resolvedValues;
121122
}
122123

124+
/**
125+
* Get the dateformat format string based on the timestamp option
126+
*
127+
* @param {string|boolean} ts Timestamp option value
128+
*
129+
* @return {string} Valid dateformat format string
130+
*/
131+
function getTimestampFormat(ts) {
132+
return ts === undefined ||
133+
ts === true ||
134+
ts === 'true' ||
135+
ts === false ||
136+
ts === 'false'
137+
? 'isoDateTime'
138+
: ts;
139+
}
140+
123141
/**
124142
* Get the reportFilename option to be passed to `report.create`
125143
*
126144
* Returns the `reportFilename` option if provided otherwise
127145
* it returns the base filename stripped of path and extension
128146
*
129-
* @param {Object} file.filename Name of file to be processed
147+
* @param {Object} file File object
148+
* @param {string} file.filename Name of file to be processed
149+
* @param {Object} file.data JSON test data
130150
* @param {Object} args CLI process arguments
131151
*
132152
* @return {string} Filename
133153
*/
134-
function getReportFilename({ filename }, { reportFilename }) {
135-
return (
136-
reportFilename ||
137-
filename
138-
.split(path.sep)
139-
.pop()
140-
.replace(JsonFileRegex, '')
141-
);
154+
function getReportFilename({ filename, data }, { reportFilename, timestamp }) {
155+
const DEFAULT_FILENAME = filename
156+
.split(path.sep)
157+
.pop()
158+
.replace(JsonFileRegex, '');
159+
const NAME_REPLACE = '[name]';
160+
const STATUS_REPLACE = '[status]';
161+
const DATETIME_REPLACE = '[datetime]';
162+
const STATUSES = {
163+
Pass: 'pass',
164+
Fail: 'fail',
165+
};
166+
167+
let outFilename = reportFilename || DEFAULT_FILENAME;
168+
169+
const hasDatetimeReplacement = outFilename.includes(DATETIME_REPLACE);
170+
const tsFormat = getTimestampFormat(timestamp);
171+
const ts = dateFormat(new Date(), tsFormat)
172+
// replace commas, spaces or comma-space combinations with underscores
173+
.replace(/(,\s*)|,|\s+/g, '_')
174+
// replace forward and back slashes with hyphens
175+
.replace(/\\|\//g, '-')
176+
// remove colons
177+
.replace(/:/g, '');
178+
179+
if (timestamp) {
180+
if (!hasDatetimeReplacement) {
181+
outFilename = `${outFilename}_${DATETIME_REPLACE}`;
182+
}
183+
}
184+
185+
// Special handling of replacement tokens
186+
const status = data.stats.failures > 0 ? STATUSES.Fail : STATUSES.Pass;
187+
188+
outFilename = outFilename
189+
.replace(NAME_REPLACE, DEFAULT_FILENAME)
190+
.replace(STATUS_REPLACE, status)
191+
.replace(DATETIME_REPLACE, ts);
192+
193+
return outFilename;
142194
}
143195

144196
/**

src/lib/main.js

Lines changed: 66 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -64,64 +64,91 @@ function loadFile(filename) {
6464
/**
6565
* Get the dateformat format string based on the timestamp option
6666
*
67-
* @param {string|boolean} timestamp Timestamp option value
67+
* @param {string|boolean} ts Timestamp option value
6868
*
6969
* @return {string} Valid dateformat format string
7070
*/
71-
function getTimestampFormat(timestamp) {
72-
switch (timestamp) {
73-
case '':
74-
case 'true':
75-
case true:
76-
return 'isoDateTime';
77-
default:
78-
return timestamp;
79-
}
71+
function getTimestampFormat(ts) {
72+
return ts === '' ||
73+
ts === true ||
74+
ts === 'true' ||
75+
ts === false ||
76+
ts === 'false'
77+
? 'isoDateTime'
78+
: ts;
8079
}
8180

8281
/**
8382
* Construct the path/name of the HTML/JSON file to be saved
8483
*
85-
* @param {Object} reportOptions Options object
84+
* @param {object} reportOptions Options object
8685
* @param {string} reportOptions.reportDir Directory to save report to
8786
* @param {string} reportOptions.reportFilename Filename to save report to
8887
* @param {string} reportOptions.timestamp Timestamp format to be appended to the filename
88+
* @param {object} reportData JSON test data
8989
*
9090
* @return {string} Fully resolved path without extension
9191
*/
92-
function getFilename({ reportDir, reportFilename = 'mochawesome', timestamp }) {
93-
let ts = '';
92+
function getFilename({ reportDir, reportFilename, timestamp }, reportData) {
93+
const DEFAULT_FILENAME = 'mochawesome';
94+
const NAME_REPLACE = '[name]';
95+
const STATUS_REPLACE = '[status]';
96+
const DATETIME_REPLACE = '[datetime]';
97+
const STATUSES = {
98+
Pass: 'pass',
99+
Fail: 'fail',
100+
};
101+
102+
let filename = reportFilename || DEFAULT_FILENAME;
103+
104+
const hasDatetimeReplacement = filename.includes(DATETIME_REPLACE);
105+
const tsFormat = getTimestampFormat(timestamp);
106+
const ts = dateFormat(new Date(), tsFormat)
107+
// replace commas, spaces or comma-space combinations with underscores
108+
.replace(/(,\s*)|,|\s+/g, '_')
109+
// replace forward and back slashes with hyphens
110+
.replace(/\\|\//g, '-')
111+
// remove colons
112+
.replace(/:/g, '');
113+
94114
if (timestamp !== false && timestamp !== 'false') {
95-
const format = getTimestampFormat(timestamp);
96-
ts = `_${dateFormat(new Date(), format)}`
97-
// replace commas, spaces or comma-space combinations with underscores
98-
.replace(/(,\s*)|,|\s+/g, '_')
99-
// replace forward and back slashes with hyphens
100-
.replace(/\\|\//g, '-')
101-
// remove colons
102-
.replace(/:/g, '');
115+
if (!hasDatetimeReplacement) {
116+
filename = `${filename}_${DATETIME_REPLACE}`;
117+
}
103118
}
104-
const filename = `${reportFilename.replace(fileExtRegex, '')}${ts}`;
119+
120+
const specFilename = path
121+
.basename(reportData.results[0].file || '')
122+
.replace(/\..+/, '');
123+
124+
const status = reportData.stats.failures > 0 ? STATUSES.Fail : STATUSES.Pass;
125+
126+
filename = filename
127+
.replace(NAME_REPLACE, specFilename || DEFAULT_FILENAME)
128+
.replace(STATUS_REPLACE, status)
129+
.replace(DATETIME_REPLACE, ts);
130+
105131
return path.resolve(process.cwd(), reportDir, filename);
106132
}
107133

108134
/**
109135
* Get report options by extending base options
110136
* with user provided options
111137
*
112-
* @param {Object} opts Report options
138+
* @param {object} opts Report options
139+
* @param {object} reportData JSON test data
113140
*
114-
* @return {Object} User options merged with default options
141+
* @return {object} User options merged with default options
115142
*/
116-
function getOptions(opts) {
143+
function getOptions(opts, reportData) {
117144
const mergedOptions = getMergedOptions(opts || {});
118145

119146
// For saving JSON from mochawesome reporter
120147
if (mergedOptions.saveJson) {
121-
mergedOptions.jsonFile = `${getFilename(mergedOptions)}.json`;
148+
mergedOptions.jsonFile = `${getFilename(mergedOptions, reportData)}.json`;
122149
}
123150

124-
mergedOptions.htmlFile = `${getFilename(mergedOptions)}.html`;
151+
mergedOptions.htmlFile = `${getFilename(mergedOptions, reportData)}.html`;
125152
return mergedOptions;
126153
}
127154

@@ -159,7 +186,7 @@ function _shouldCopyAssets(assetsDir) {
159186
/**
160187
* Copy the report assets to the report dir, ignoring inline assets
161188
*
162-
* @param {Object} opts Report options
189+
* @param {object} opts Report options
163190
*/
164191
function copyAssets({ assetsDir }) {
165192
if (_shouldCopyAssets(assetsDir)) {
@@ -172,8 +199,8 @@ function copyAssets({ assetsDir }) {
172199
/**
173200
* Get the report assets object
174201
*
175-
* @param {Object} reportOptions Options
176-
* @return {Object} Object with assets props
202+
* @param {object} reportOptions Options
203+
* @return {object} Object with assets props
177204
*/
178205
function getAssets(reportOptions) {
179206
const { assetsDir, cdn, dev, inlineAssets, reportDir } = reportOptions;
@@ -220,20 +247,14 @@ function getAssets(reportOptions) {
220247
/**
221248
* Prepare options, assets, and html for saving
222249
*
223-
* @param {string} reportData JSON test data
224-
* @param {Object} opts Report options
250+
* @param {object} reportData JSON test data
251+
* @param {object} opts Report options
225252
*
226-
* @return {Object} Prepared data for saving
253+
* @return {object} Prepared data for saving
227254
*/
228255
function prepare(reportData, opts) {
229-
// Stringify the data if needed
230-
let data = reportData;
231-
if (typeof data === 'object') {
232-
data = JSON.stringify(reportData);
233-
}
234-
235256
// Get the options
236-
const reportOptions = getOptions(opts);
257+
const reportOptions = getOptions(opts, reportData);
237258

238259
// Stop here if we're not generating an HTML report
239260
if (!reportOptions.saveHtml) {
@@ -245,7 +266,7 @@ function prepare(reportData, opts) {
245266

246267
// Render basic template to string
247268
const renderedHtml = renderMainHTML({
248-
data,
269+
data: JSON.stringify(reportData),
249270
options: reportOptions,
250271
title: reportOptions.reportPageTitle,
251272
useInlineAssets: reportOptions.inlineAssets && !reportOptions.cdn,
@@ -259,8 +280,8 @@ function prepare(reportData, opts) {
259280
/**
260281
* Create the report
261282
*
262-
* @param {string} data JSON test data
263-
* @param {Object} opts Report options
283+
* @param {object} data JSON test data
284+
* @param {object} opts Report options
264285
*
265286
* @return {Promise} Resolves if report was created successfully
266287
*/
@@ -302,8 +323,8 @@ function create(data, opts) {
302323
/**
303324
* Create the report synchronously
304325
*
305-
* @param {string} data JSON test data
306-
* @param {Object} opts Report options
326+
* @param {object} data JSON test data
327+
* @param {object} opts Report options
307328
*
308329
*/
309330
function createSync(data, opts) {

test/sample-data/test.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,8 @@
101101
"uuid": "85326d2a-1546-4657-a3dc-8a210b578804",
102102
"beforeHooks": [],
103103
"afterHooks": [],
104-
"fullFile": "",
105-
"file": "",
104+
"fullFile": "/Users/adamgruber/Sites/ma-test/cases/test.js",
105+
"file": "/cases/test.js",
106106
"passes": [],
107107
"failures": [],
108108
"skipped": [],

0 commit comments

Comments
 (0)