Created
August 9, 2018 11:11
-
-
Save songzhiyong/365e40b91422f23b2e569daced2e5162 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var _ = require('lodash'), | |
colors = require('colors/safe'), | |
Table = require('cli-table2'), | |
format = require('util').format, | |
util = require('../../util'), | |
cliUtils = require('./cli-utils'), | |
print = require('../../print'), | |
pad = cliUtils.padLeft, | |
LF = '\n', | |
SPC = ' ', | |
DOT = '.', | |
E = '', | |
PostmanCLIReporter; | |
// sets theme for colors for console logging | |
colors.setTheme({ | |
log: 'grey', | |
info: 'cyan', | |
warn: 'yellow', | |
debug: 'blue', | |
error: 'red' | |
}); | |
/** | |
* CLI reporter | |
* | |
* @param {EventEmitter} emitter - An EventEmitter instance with event handler attachers to trigger reporting. | |
* @param {Object} reporterOptions - CLI reporter options object. | |
* @param {Boolean=} reporterOptions.silent - Boolean flag to turn off CLI reporting altogether, if set to true. | |
* @param {Boolean=} reporterOptions.noAssertions - Boolean flag to turn off assertion reporting, if set to true. | |
* @param {Boolean=} reporterOptions.noSummary - Boolean flag to turn off summary reporting altogether, if set to true. | |
* @param {Boolean=} reporterOptions.noFailures - Boolean flag to turn off failure reporting altogether, if set to true. | |
* @param {Boolean=} reporterOptions.noConsole - Boolean flag to turn off console logging, if set to true. | |
* @param {Object} options - A set of generic collection run options. | |
* @returns {*} | |
*/ | |
PostmanCLIReporter = function (emitter, reporterOptions, options) { | |
var currentGroup = options.collection, | |
inspect = cliUtils.inspector(options), | |
wrap = cliUtils.wrapper(), | |
symbols = cliUtils.symbols(options.disableUnicode); | |
// respect silent option to not report anything | |
if (reporterOptions.silent || options.silent) { | |
return; // we simply do not register anything! | |
} | |
// we register the `done` listener first so that in case user does not want to show results of collection run, we | |
// simply do not register the other events | |
emitter.on('done', function () { | |
// for some reason, if there is no run summary, it is unexpected and hence don't validate this | |
var run = this.summary.run; | |
// show the summary table (provided option does not say it is not to be shown) | |
if (!reporterOptions.noSummary) { | |
// @todo: possible missing .toString() call on parseStatistics below? (see parseFailures call below) | |
print(LF + PostmanCLIReporter.parseStatistics(run.stats, run.timings, run.transfers, options) + LF); | |
} | |
// show the failures table (provided option does not say it is not to be shown) | |
// if (!reporterOptions.noFailures && run.failures && run.failures.length) { | |
// print(LF + PostmanCLIReporter.parseFailures(run.failures).toString() + LF); | |
// } | |
}); | |
// in case user does not want to show results of collection run, we simply do not register the other events. | |
if (reporterOptions.noAssertions) { | |
return; | |
} | |
emitter.on('start', function () { | |
var collectionIdentifier = currentGroup && (currentGroup.name || currentGroup.id); | |
// print the newman banner | |
print('%s\n\n', colors.reset('newman')); | |
// print the collection name and newman info line | |
collectionIdentifier && print.lf('%s', colors.reset(collectionIdentifier)); | |
}); | |
emitter.on('beforeIteration', function (err, o) { | |
if (err || o.cursor.cycles <= 1) { | |
return; // do not print iteration banner if it is a single iteration run | |
} | |
// print the iteration info line | |
print.lf(LF + colors.gray.underline('Iteration %d/%d'), o.cursor.iteration + 1, o.cursor.cycles); | |
}); | |
emitter.on('beforeItem', function (err, o) { | |
if (err) { return; } | |
var itemGroup = o.item.parent(), | |
root = !itemGroup || (itemGroup === options.collection); | |
// in case this item belongs to a separate folder, print that folder name | |
if (itemGroup && (currentGroup !== itemGroup)) { | |
!root && print('\n%s %s', symbols.folder, colors.reset(util.getFullName(itemGroup))); | |
// set the flag that keeps track of the currently running group | |
currentGroup = itemGroup; | |
} | |
// we print the item name. the symbol prefix denotes if the item is in root or under folder. | |
// @todo - when we do indentation, we would not need symbolic representation | |
// o.item && print.lf('\n%s %s', (root ? | |
// symbols.root : symbols.sub), colors.reset(o.item.name || E)); | |
}); | |
// print out the request name to be executed and start a spinner | |
emitter.on('beforeRequest', function (err, o) { | |
if (err) { return; } | |
// o.request && print(' %s %s ', colors.gray(o.request.method), colors.gray(o.request.url)).wait(colors.gray); | |
}); | |
// output the response code, reason and time | |
emitter.on('request', function (err, o) { | |
if (err) { return; } | |
var size = o.response && o.response.size(); | |
size = size && (size.header || 0) + (size.body || 0) || 0; | |
// err ? print.lf(colors.red('[errored]')) : | |
// print.lf(colors.gray('[%d %s, %s, %s]'), o.response.code, o.response.reason(), | |
// util.filesize(size), util.prettyms(o.response.responseTime)); | |
}); | |
// Print script errors in real time | |
emitter.on('script', function (err, o) { | |
err && print.lf(colors.red.bold('%s⠄ %s in %s-script'), pad(this.summary.run.failures.length, 3, SPC), err.name, | |
o.event && o.event.listen || 'unknown'); | |
}); | |
emitter.on('assertion', function (err, o) { | |
var passed = !err; | |
// handle skipped test display | |
if (o.skipped) { | |
print.lf('%s %s', colors.cyan(' - '), colors.cyan('[skipped] ' + o.assertion)); | |
return; | |
} | |
if (!passed){ | |
print.lf('\n%s %s', (root ? | |
symbols.root : symbols.sub), colors.reset(o.item.name || E)); | |
print(' %s %s '+ LF, colors.gray(o.item.request.method), colors.gray(o.item.request.url.toString())); | |
print.lf('%s %s', passed ? colors.green(` ${symbols.ok} `) : | |
colors.red.bold(pad(this.summary.run.failures.length, 3, SPC) + symbols.dot), passed ? | |
colors.gray(o.assertion) : colors.red.bold(o.assertion)); | |
} | |
}); | |
// show user console logs in a neatly formatted way (provided user has not disabled the same) | |
!reporterOptions.noConsole && emitter.on('console', function (err, o) { | |
if (err) { return; } | |
var color = colors[o.level] || colors.gray, | |
message; | |
// we first merge all messages to a string. while merging we run the values to util.inspect to colour code the | |
// messages based on data type | |
message = wrap(_.reduce(o.messages, function (log, message) { // wrap the whole message to the window size | |
return (log += (log ? colors.white(', ') : '') + inspect(message)); | |
}, E), ` ${color(symbols.console.middle)} `); // add an indentation line at the beginning | |
print.buffer(color(` ${symbols.console.top}\n`), color(` ${symbols.console.bottom}\n`)) | |
// tweak the message to ensure that its surrounding is not brightly coloured. | |
// also ensure to remove any blank lines generated due to util.inspect | |
.nobuffer(colors.gray(message.replace(/\n\s*\n/g, LF) + LF)); | |
}); | |
}; | |
_.assignIn(PostmanCLIReporter, { | |
// @todo: change function signature to accept run object and options, thereby reducing parameters | |
/** | |
* A CLI reporter method to parse collection run statistics into a CLI table. | |
* | |
* @param {Object} stats - The cumulative collection run status object. | |
* @param {Object} stats.iterations - A set of values for total, pending, and failed iterations. | |
* @param {Number} stats.iterations.total - Total iterations in the current collection run. | |
* @param {Number} stats.iterations.pending - Pending iterations in the current collection run. | |
* @param {Number} stats.iterations.failed - Failed iterations in the current collection run. | |
* @param {Object} stats.requests - A set of values for total, pending, and failed requests. | |
* @param {Number} stats.requests.total - Total requests in the current collection run. | |
* @param {Number} stats.requests.pending - Pending requests in the current collection run. | |
* @param {Number} stats.requests.failed - Failed requests in the current collection run. | |
* @param {Object} stats.testScripts - A set of values for total, pending, and failed testScripts. | |
* @param {Number} stats.testScripts.total - Total testScripts in the current collection run. | |
* @param {Number} stats.testScripts.pending - Pending testScripts in the current collection run. | |
* @param {Number} stats.testScripts.failed - Failed testScripts in the current collection run. | |
* @param {Object} stats.prerequestScripts - A set of values for total, pending, and failed prerequestScripts. | |
* @param {Number} stats.prerequestScripts.total - Total prerequestScripts in the current collection run. | |
* @param {Number} stats.prerequestScripts.pending - Pending prerequestScripts in the current collection run. | |
* @param {Number} stats.prerequestScripts.failed - Failed prerequestScripts in the current collection run. | |
* @param {Object} stats.assertions - A set of values for total, pending, and failed assertions. | |
* @param {Number} stats.assertions.total - Total assertions in the current collection run. | |
* @param {Number} stats.assertions.pending - Pending assertions in the current collection run. | |
* @param {Number} stats.assertions.failed - Failed assertions in the current collection run. | |
* @param {Object} timings - A set of values for the timings of the current collection run. | |
* @param {Number} timings.completed - The end timestamp for the current collection run. | |
* @param {Number} timings.started - The start timestamp for the current collection run | |
* @param {String} timings.responseAverage - The average response time across all requests | |
* @param {Object} transfers - A set of details on the network usage for the current collection run. | |
* @param {String} transfers.responseTotal - The net extent of the data transfer achieved during the collection run. | |
* @param {Object} options - The set of generic collection run options. | |
* @returns {Table} - The constructed collection run statistics table. | |
*/ | |
parseStatistics: function (stats, timings, transfers, options) { | |
var summaryTable; | |
// create the summary table | |
summaryTable = new Table({ | |
chars: options.disableUnicode && cliUtils.cliTableTemplateFallback, | |
style: { head: [] }, | |
head: [E, 'executed', ' failed'], | |
colAligns: ['right', 'right', 'right'], | |
colWidths: [25] | |
}); | |
// add specific rows to show in summary | |
stats && _.forEach([{ | |
source: 'iterations', | |
label: 'iterations' | |
}, { | |
source: 'requests', | |
label: 'requests' | |
}, { | |
source: 'testScripts', | |
label: 'test-scripts' | |
}, { | |
source: 'prerequestScripts', | |
label: 'prerequest-scripts' | |
}, { | |
source: 'assertions', | |
label: 'assertions' | |
}], function (row) { | |
var metric = stats[row.source], | |
label = row.label; | |
// colour the label based on the failure or pending count of the metric | |
label = metric.failed ? colors.red(label) : (metric.pending ? label : colors.green(label)); | |
// push the statistics | |
summaryTable.push([ | |
label, | |
metric.total, | |
(metric.failed ? colors.red(metric.failed) : metric.failed) | |
// @todo - add information of pending scripts | |
// (metric.failed ? colors.red(metric.failed) : metric.failed) + | |
// (metric.pending ? format(' (%d pending)', metric.pending) : E) | |
]); | |
}); | |
// add the total execution time to summary | |
timings && summaryTable.push([{ | |
colSpan: 3, | |
content: format('total run duration: %s', util.prettyms(timings.completed - timings.started)), | |
hAlign: 'left' // since main style was set to right | |
}]); | |
// add row to show total data received | |
transfers && summaryTable.push([{ | |
colSpan: 3, | |
content: format('total data received: %s (approx)', util.filesize(transfers.responseTotal)), | |
hAlign: 'left' | |
}]); | |
// add row to show average response time | |
summaryTable.push([{ | |
colSpan: 3, | |
content: format('average response time: %s', util.prettyms(timings.responseAverage)), | |
hAlign: 'left' | |
}]); | |
return summaryTable; | |
}, | |
/** | |
* A CLI reporter method to parse collection run failure statistics into a CLI table. | |
* | |
* @param {Array} failures - An array of failure objects. | |
* @returns {Table} - The constructed CLI failure Table object. | |
*/ | |
parseFailures: function (failures) { | |
var failureTable = new Table({ | |
head: [{ | |
hAlign: 'right', | |
content: colors.red.underline('#') | |
}, colors.red.underline('failure'), | |
colors.red.underline('detail')], | |
chars: cliUtils.cliTableTemplate_Blank, | |
wordWrap: true, | |
colAligns: ['right'], | |
colWidths: cliUtils.noTTY() ? [] : (function (size, indexOrder) { | |
var colWidths; | |
if (size.exists && size.width && (size.width > 20)) { | |
colWidths = []; | |
colWidths[0] = indexOrder + 3; | |
colWidths[1] = parseInt((size.width - colWidths[0]) * 0.2, 10); | |
colWidths[2] = parseInt(size.width - (colWidths[0] + colWidths[1] + 5), 10); | |
} | |
return colWidths; | |
}(cliUtils.dimension(), Number(failures.length.toString().length))) | |
}); | |
_.forEach(failures, function (failure, index) { | |
var name = failure.error && failure.error.name || E, | |
message = failure.error && failure.error.message || E; | |
// augment name with iteration information | |
failure.cursor && (failure.cursor.cycles > 1) && | |
(name += LF + colors.gray('iteration: ' + (failure.cursor.iteration + 1))); | |
// augment the message with stack information | |
failure.at && (message += LF + colors.gray('at ' + failure.at)); | |
// augment message with item information | |
failure.source && | |
(message += format(colors.gray('\ninside "%s"'), util.getFullName(failure.source))); | |
failureTable.push([pad(Number(index + 1), Number(failures.length.toString().length)).toString() + | |
DOT, name, message]); | |
}); | |
return failureTable; | |
} | |
}); | |
// Mark the CLI reporter as dominant, so that no two dominant reporters are together | |
PostmanCLIReporter.prototype.dominant = true; | |
module.exports = PostmanCLIReporter; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment