Skip to content

Commit

Permalink
Merge pull request #192 from adamgruber/filename-enhancements
Browse files Browse the repository at this point in the history
Support token replacements in `reportFilename` option
  • Loading branch information
adamgruber authored Feb 24, 2022
2 parents c28bb8a + d4c6536 commit cf51b85
Show file tree
Hide file tree
Showing 8 changed files with 288 additions and 72 deletions.
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.md
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# mochawesome-report-generator changelog

## [Unreleased]
### Added
- `reportFilename` option: support replacement tokens (`[name]`, `[status]`, `[datetime]`)

## [6.0.1] - 2021-11-05
### Fixed
Expand Down
28 changes: 25 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ mochawesome-report/
Flag | Type | Default | Description
:--- | :--- | :------ | :----------
-f, --reportFilename | string | | Filename of saved report
-f, --reportFilename | string | mochawesome | Filename of saved report. *See [notes](#reportFilename) for available token replacements.*
-o, --reportDir | string | [cwd]/mochawesome-report | Path to save report
-t, --reportTitle | string | mochawesome | Report title
-p, --reportPageTitle | string | mochawesome-report | Browser title
Expand All @@ -100,12 +100,34 @@ Flag | Type | Default | Description
*Boolean options can be negated by adding `--no` before the option. For example: `--no-code` would set `code` to `false`.*
#### Overwrite
#### reportFilename replacement tokens
Using the following tokens it is possible to dynamically alter the filename of the generated report.
- **[name]** will be replaced with the spec filename when possible.
- **[status]** will be replaced with the status (pass/fail) of the test run.
- **[datetime]** will be replaced with a timestamp. The format can be - specified using the `timestamp` option.
For example, given the spec `cypress/integration/sample.spec.js` and the following config:
```
{
reporter: "mochawesome",
reporterOptions: {
reportFilename: "[status]_[datetime]-[name]-report",
timestamp: "longDate"
}
}
```
The resulting report file will be named `pass_February_23_2022-sample-report.html`
**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.
#### overwrite
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`).
**Note:** `overwrite` will always be `false` when passing multiple files or using the `timestamp` option.
#### Timestamp
#### timestamp
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
replacements are made:
Expand Down
70 changes: 61 additions & 9 deletions src/bin/cli-main.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const fs = require('fs-extra');
const path = require('path');
const chalk = require('chalk');
const t = require('tcomb-validation');
const dateFormat = require('dateformat');
const report = require('../lib/main');
const types = require('./types');
const logger = require('./logger');
Expand Down Expand Up @@ -120,25 +121,76 @@ function handleResolved(resolvedValues) {
return resolvedValues;
}

/**
* Get the dateformat format string based on the timestamp option
*
* @param {string|boolean} ts Timestamp option value
*
* @return {string} Valid dateformat format string
*/
function getTimestampFormat(ts) {
return ts === undefined ||
ts === true ||
ts === 'true' ||
ts === false ||
ts === 'false'
? 'isoDateTime'
: ts;
}

/**
* Get the reportFilename option to be passed to `report.create`
*
* Returns the `reportFilename` option if provided otherwise
* it returns the base filename stripped of path and extension
*
* @param {Object} file.filename Name of file to be processed
* @param {Object} file File object
* @param {string} file.filename Name of file to be processed
* @param {Object} file.data JSON test data
* @param {Object} args CLI process arguments
*
* @return {string} Filename
*/
function getReportFilename({ filename }, { reportFilename }) {
return (
reportFilename ||
filename
.split(path.sep)
.pop()
.replace(JsonFileRegex, '')
);
function getReportFilename({ filename, data }, { reportFilename, timestamp }) {
const DEFAULT_FILENAME = filename
.split(path.sep)
.pop()
.replace(JsonFileRegex, '');
const NAME_REPLACE = '[name]';
const STATUS_REPLACE = '[status]';
const DATETIME_REPLACE = '[datetime]';
const STATUSES = {
Pass: 'pass',
Fail: 'fail',
};

let outFilename = reportFilename || DEFAULT_FILENAME;

const hasDatetimeReplacement = outFilename.includes(DATETIME_REPLACE);
const tsFormat = getTimestampFormat(timestamp);
const ts = dateFormat(new Date(), tsFormat)
// replace commas, spaces or comma-space combinations with underscores
.replace(/(,\s*)|,|\s+/g, '_')
// replace forward and back slashes with hyphens
.replace(/\\|\//g, '-')
// remove colons
.replace(/:/g, '');

if (timestamp) {
if (!hasDatetimeReplacement) {
outFilename = `${outFilename}_${DATETIME_REPLACE}`;
}
}

// Special handling of replacement tokens
const status = data.stats.failures > 0 ? STATUSES.Fail : STATUSES.Pass;

outFilename = outFilename
.replace(NAME_REPLACE, DEFAULT_FILENAME)
.replace(STATUS_REPLACE, status)
.replace(DATETIME_REPLACE, ts);

return outFilename;
}

/**
Expand Down
111 changes: 66 additions & 45 deletions src/lib/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,64 +64,91 @@ function loadFile(filename) {
/**
* Get the dateformat format string based on the timestamp option
*
* @param {string|boolean} timestamp Timestamp option value
* @param {string|boolean} ts Timestamp option value
*
* @return {string} Valid dateformat format string
*/
function getTimestampFormat(timestamp) {
switch (timestamp) {
case '':
case 'true':
case true:
return 'isoDateTime';
default:
return timestamp;
}
function getTimestampFormat(ts) {
return ts === '' ||
ts === true ||
ts === 'true' ||
ts === false ||
ts === 'false'
? 'isoDateTime'
: ts;
}

/**
* Construct the path/name of the HTML/JSON file to be saved
*
* @param {Object} reportOptions Options object
* @param {object} reportOptions Options object
* @param {string} reportOptions.reportDir Directory to save report to
* @param {string} reportOptions.reportFilename Filename to save report to
* @param {string} reportOptions.timestamp Timestamp format to be appended to the filename
* @param {object} reportData JSON test data
*
* @return {string} Fully resolved path without extension
*/
function getFilename({ reportDir, reportFilename = 'mochawesome', timestamp }) {
let ts = '';
function getFilename({ reportDir, reportFilename, timestamp }, reportData) {
const DEFAULT_FILENAME = 'mochawesome';
const NAME_REPLACE = '[name]';
const STATUS_REPLACE = '[status]';
const DATETIME_REPLACE = '[datetime]';
const STATUSES = {
Pass: 'pass',
Fail: 'fail',
};

let filename = reportFilename || DEFAULT_FILENAME;

const hasDatetimeReplacement = filename.includes(DATETIME_REPLACE);
const tsFormat = getTimestampFormat(timestamp);
const ts = dateFormat(new Date(), tsFormat)
// replace commas, spaces or comma-space combinations with underscores
.replace(/(,\s*)|,|\s+/g, '_')
// replace forward and back slashes with hyphens
.replace(/\\|\//g, '-')
// remove colons
.replace(/:/g, '');

if (timestamp !== false && timestamp !== 'false') {
const format = getTimestampFormat(timestamp);
ts = `_${dateFormat(new Date(), format)}`
// replace commas, spaces or comma-space combinations with underscores
.replace(/(,\s*)|,|\s+/g, '_')
// replace forward and back slashes with hyphens
.replace(/\\|\//g, '-')
// remove colons
.replace(/:/g, '');
if (!hasDatetimeReplacement) {
filename = `${filename}_${DATETIME_REPLACE}`;
}
}
const filename = `${reportFilename.replace(fileExtRegex, '')}${ts}`;

const specFilename = path
.basename(reportData.results[0].file || '')
.replace(/\..+/, '');

const status = reportData.stats.failures > 0 ? STATUSES.Fail : STATUSES.Pass;

filename = filename
.replace(NAME_REPLACE, specFilename || DEFAULT_FILENAME)
.replace(STATUS_REPLACE, status)
.replace(DATETIME_REPLACE, ts);

return path.resolve(process.cwd(), reportDir, filename);
}

/**
* Get report options by extending base options
* with user provided options
*
* @param {Object} opts Report options
* @param {object} opts Report options
* @param {object} reportData JSON test data
*
* @return {Object} User options merged with default options
* @return {object} User options merged with default options
*/
function getOptions(opts) {
function getOptions(opts, reportData) {
const mergedOptions = getMergedOptions(opts || {});

// For saving JSON from mochawesome reporter
if (mergedOptions.saveJson) {
mergedOptions.jsonFile = `${getFilename(mergedOptions)}.json`;
mergedOptions.jsonFile = `${getFilename(mergedOptions, reportData)}.json`;
}

mergedOptions.htmlFile = `${getFilename(mergedOptions)}.html`;
mergedOptions.htmlFile = `${getFilename(mergedOptions, reportData)}.html`;
return mergedOptions;
}

Expand Down Expand Up @@ -159,7 +186,7 @@ function _shouldCopyAssets(assetsDir) {
/**
* Copy the report assets to the report dir, ignoring inline assets
*
* @param {Object} opts Report options
* @param {object} opts Report options
*/
function copyAssets({ assetsDir }) {
if (_shouldCopyAssets(assetsDir)) {
Expand All @@ -172,8 +199,8 @@ function copyAssets({ assetsDir }) {
/**
* Get the report assets object
*
* @param {Object} reportOptions Options
* @return {Object} Object with assets props
* @param {object} reportOptions Options
* @return {object} Object with assets props
*/
function getAssets(reportOptions) {
const { assetsDir, cdn, dev, inlineAssets, reportDir } = reportOptions;
Expand Down Expand Up @@ -220,20 +247,14 @@ function getAssets(reportOptions) {
/**
* Prepare options, assets, and html for saving
*
* @param {string} reportData JSON test data
* @param {Object} opts Report options
* @param {object} reportData JSON test data
* @param {object} opts Report options
*
* @return {Object} Prepared data for saving
* @return {object} Prepared data for saving
*/
function prepare(reportData, opts) {
// Stringify the data if needed
let data = reportData;
if (typeof data === 'object') {
data = JSON.stringify(reportData);
}

// Get the options
const reportOptions = getOptions(opts);
const reportOptions = getOptions(opts, reportData);

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

// Render basic template to string
const renderedHtml = renderMainHTML({
data,
data: JSON.stringify(reportData),
options: reportOptions,
title: reportOptions.reportPageTitle,
useInlineAssets: reportOptions.inlineAssets && !reportOptions.cdn,
Expand All @@ -259,8 +280,8 @@ function prepare(reportData, opts) {
/**
* Create the report
*
* @param {string} data JSON test data
* @param {Object} opts Report options
* @param {object} data JSON test data
* @param {object} opts Report options
*
* @return {Promise} Resolves if report was created successfully
*/
Expand Down Expand Up @@ -302,8 +323,8 @@ function create(data, opts) {
/**
* Create the report synchronously
*
* @param {string} data JSON test data
* @param {Object} opts Report options
* @param {object} data JSON test data
* @param {object} opts Report options
*
*/
function createSync(data, opts) {
Expand Down
4 changes: 2 additions & 2 deletions test/sample-data/test.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@
"uuid": "85326d2a-1546-4657-a3dc-8a210b578804",
"beforeHooks": [],
"afterHooks": [],
"fullFile": "",
"file": "",
"fullFile": "/Users/adamgruber/Sites/ma-test/cases/test.js",
"file": "/cases/test.js",
"passes": [],
"failures": [],
"skipped": [],
Expand Down
Loading

0 comments on commit cf51b85

Please sign in to comment.