UNPKG

yeti

Version:

Yeti automates browser testing.

339 lines (281 loc) 8.78 kB
"use strict"; /** * @module reporter-cli */ var util = require("util"); var Reporter = require("./reporter"); var color = require("../color").codes; var ARROW = "►"; var GOOD = "✓"; var BAD = "✗"; /** * @class FeedbackLineReporter * @constructor * @extends Reporter */ function FeedbackLineReporter(options) { Reporter.call(this, options); this.batchDetails = { passed: 0, failed: 0, currentIndex: 0, coverage: [], calledLines: 0, coveredLines: 0, total: this.batch.tests.length }; this.eta = ""; this.beats = 0; this.spinIndex = 0; this.cli.rl.setPrompt("", 0); this.timeStart = new Date(); this.feedbackLineDisplayed = false; } util.inherits(FeedbackLineReporter, Reporter); FeedbackLineReporter.prototype.bindEvents = function () { this.batch.on("agentResult", this.handleAgentResult.bind(this)); this.batch.on("agentScriptError", this.handleAgentScriptError.bind(this)); this.batch.on("agentError", this.handleAgentError.bind(this)); this.batch.on("agentComplete", this.handleAgentComplete.bind(this)); this.batch.on("agentProgress", this.updateFeedbackLine.bind(this)); this.batch.on("agentBeat", this.handleAgentBeat.bind(this)); this.batch.once("dispatch", this.handleDispatch.bind(this)); this.batch.once("complete", this.handleComplete.bind(this)); this.batch.once("end", this.handleEnd.bind(this)); }; /** * @method putsVerboseResult * @param {object} result Test result. * @private */ FeedbackLineReporter.prototype.putsVerboseResult = function (result) { var self = this, lastSuite, k, k1, suite, test; function reportTestError(test) { var msg, m; if ("fail" === test.result) { if (!lastSuite || lastSuite !== suite.name) { self.puts(" in", color.bold(suite.name)); lastSuite = suite.name; } msg = test.message.split("\n"); self.puts(" ", color.bold(color.red(test.name)) + ":", msg[0]); for (m = 1; m < msg.length; m = m + 1) { self.puts(" " + msg[m]); } } } function hasResults(o) { return (("passed" in test) && ("failed" in test) && ("type" in test)); } function walk(o) { var i; for (i in o) { if (hasResults(o[i])) { reportTestError(o[i]); } else { walk(o[i]); } } } for (k in result) { suite = result[k]; if (suite && "object" === typeof suite) { if (suite.failed) { for (k1 in suite) { test = suite[k1]; if ("object" === typeof test) { if (hasResults(test)) { walk(test); } else { reportTestError(test); } } } } } } self.puts(""); }; FeedbackLineReporter.prototype.puts = function () { if (this.feedbackLineDisplayed) { this.clearLine(); this.feedbackLineDisplayed = false; } this.cli.puts.apply(this.cli, Array.prototype.slice.call(arguments)); }; FeedbackLineReporter.prototype.clearLine = function () { // If the output of self.rl is NOT a TTY, // a bug in Node.js readline will cause // the unused first argument to be treated // as a Buffer. // // https://github.com/joyent/node/blob/6e2055889091a424fbb5c500bc3ab9c05d1c28b4/lib/readline.js#L291 // // Wrap this call to determine if the rl // output is a terminal. if (this.cli.rl.terminal) { this.cli.rl.write(null, { ctrl: true, name: "u" }); } }; FeedbackLineReporter.prototype.formatTime = function (milliseconds) { var formatted = "", seconds = (milliseconds / 1000).toFixed(0), minutes = 0, minutesUnit, secondsUnit; if (seconds > 60) { minutes = (seconds / 60).toFixed(0); seconds %= 60; } minutesUnit = minutes === 1 ? "minute" : "minutes"; secondsUnit = seconds === 1 ? "second" : "seconds"; if (minutes > 0) { formatted += minutes + " " + minutesUnit + ", "; } if (!minutes && seconds <= 3) { // higher resolution seconds = (milliseconds / 1000).toFixed(2); } formatted += seconds + " " + secondsUnit; return formatted; }; FeedbackLineReporter.prototype.updateETA = function () { var current = this.batchDetails.currentIndex, total = this.batchDetails.total, elapsed = (new Date().getTime()) - this.timeStart, eta = elapsed * (total / current - 1), formatted = this.formatTime(eta); if (formatted) { formatted = "ETA " + formatted + " "; } this.eta = formatted; }; /** * @method updateFeedbackLine * @private */ FeedbackLineReporter.prototype.updateFeedbackLine = function () { var current = this.batchDetails.currentIndex, total = this.batchDetails.total, percent = current / total * 100, spin = ["/", "|", "\\", "-"], spins = spin.length - 1, elapsed = (new Date().getTime()) - this.timeStart, bps = (this.beats * 1000) / elapsed, line; this.clearLine(); line = ARROW + " Testing... " + spin[this.spinIndex] + " " + percent.toFixed(0) + "% complete (" + current + "/" + total + ") " + bps.toFixed(2) + " beats/sec " + this.eta; if (this.cli.rl.terminal) { this.cli.rl.write(line); } else { this.cli.puts(line); } this.feedbackLineDisplayed = true; this.spinIndex += 1; if (this.spinIndex > spins) { this.spinIndex = 0; } }; /** * @method handleAgentResult */ FeedbackLineReporter.prototype.handleAgentResult = function (agent, details) { var passed = details.passed, failed = details.failed, icon = failed ? BAD : GOOD, iconColor = failed ? color.red : color.green; this.batchDetails.currentIndex += 1; this.batchDetails.passed += passed; this.batchDetails.failed += failed; this.updateETA(); if (details.coverage) { this.batchDetails.coverage.push(details.coverage); } if (failed) { if (!details.name) { details.name = details.url; } this.clearLine(); this.puts(iconColor(icon), color.bold(details.name), "on", agent); this.putsVerboseResult(details); } }; /** * @method handleAgentScriptError */ FeedbackLineReporter.prototype.handleAgentScriptError = function (agent, details) { this.puts(color.red(BAD + " Script error") + ": " + details.message); this.puts(" URL: " + details.url); this.puts(" Line: " + details.line); this.puts(" User-Agent: " + agent); }; /** * @method handleAgentError */ FeedbackLineReporter.prototype.handleAgentError = function (agent, details) { this.puts(color.red(BAD + " Error") + ": " + details.message); this.puts(" User-Agent: " + agent); }; /** * @method handleAgentComplete */ FeedbackLineReporter.prototype.handleAgentComplete = function (agent) { this.puts(GOOD, "Agent completed:", agent); }; /** * @method handleAgentBeat */ FeedbackLineReporter.prototype.handleAgentBeat = function (agent) { this.beats += 1; if (this.cli.rl.terminal) { this.updateFeedbackLine(); } }; /** * @method handleDispatch */ FeedbackLineReporter.prototype.handleDispatch = function (agents) { if (!agents.length) { this.cli.error(BAD, "No browsers connected, exiting."); this.emit("error"); return; } this.puts(GOOD, "Testing started on", agents.join(", ")); this.batchDetails.total *= agents.length; }; /** * @method handleComplete */ FeedbackLineReporter.prototype.handleComplete = function () { this.updateFeedbackLine(); var duration = Number(new Date()) - this.timeStart, total = this.batchDetails.passed + this.batchDetails.failed, durationString = "(" + this.formatTime(duration) + ")"; if (this.batchDetails.failed) { this.puts(color.red(BAD + " Failures") + ":", this.batchDetails.failed, "of", total, "tests failed.", durationString); } else { this.puts(color.green(GOOD + " " + total + " tests passed!"), durationString); } }; /** * @method handleEnd */ FeedbackLineReporter.prototype.handleEnd = function () { this.emit("end", this.batchDetails.failed ? 3 : 0); }; module.exports = FeedbackLineReporter;