UNPKG

web-component-tester

Version:

web-component-tester makes testing your web components a breeze!

273 lines (272 loc) 9.27 kB
"use strict"; /** * @license * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. * This code may only be used under the BSD style license found at * http://polymer.github.io/LICENSE.txt * The complete set of authors may be found at * http://polymer.github.io/AUTHORS.txt * The complete set of contributors may be found at * http://polymer.github.io/CONTRIBUTORS.txt * Code distributed by Google as part of the polymer project is also * subject to an additional IP rights grant found at * http://polymer.github.io/PATENTS.txt */ Object.defineProperty(exports, "__esModule", { value: true }); const chalk = require("chalk"); const cleankill = require("cleankill"); const _ = require("lodash"); const stacky = require("stacky"); const util = require("util"); const STACKY_CONFIG = { indent: ' ', locationStrip: [ /^https?:\/\/[^\/]+/, /\?[\d\.]+$/, ], unimportantLocation: [ /^\/web-component-tester\//, ] }; const STATE_ICONS = { passing: '✓', pending: '✖', failing: '✖', unknown: '?', }; const STATE_COLORS = { passing: chalk.green, pending: chalk.yellow, failing: chalk.red, unknown: chalk.red, error: chalk.red, }; const SHORT = { 'internet explorer': 'IE', }; const BROWSER_PAD = 24; const STATUS_PAD = 38; class CliReporter { constructor(emitter, stream, options) { this.prettyBrowsers = {}; this.browserStats = {}; this.emitter = emitter; this.stream = stream; this.options = options; cleankill.onInterrupt(() => { return new Promise((resolve) => { this.flush(); resolve(); }); }); emitter.on('log:error', this.log.bind(this, chalk.red)); if (!this.options.quiet) { emitter.on('log:warn', this.log.bind(this, chalk.yellow)); emitter.on('log:info', this.log.bind(this)); if (this.options.verbose) { emitter.on('log:debug', this.log.bind(this, chalk.dim)); } } emitter.on('browser-init', (browser, stats) => { this.browserStats[browser.id] = stats; this.prettyBrowsers[browser.id] = this.prettyBrowser(browser); this.updateStatus(); }); emitter.on('browser-start', (browser, data, stats) => { this.browserStats[browser.id] = stats; this.log(browser, 'Beginning tests via', chalk.magenta(data.url)); this.updateStatus(); }); emitter.on('test-end', (browser, data, stats) => { this.browserStats[browser.id] = stats; if (data.state === 'failing') { this.writeTestError(browser, data); } else if (this.options.expanded || this.options.verbose) { this.log(browser, this.stateIcon(data.state), this.prettyTest(data)); } this.updateStatus(); }); emitter.on('browser-end', (browser, error, stats) => { this.browserStats[browser.id] = stats; if (error) { this.log(chalk.red, browser, 'Tests failed:', error); } else { this.log(chalk.green, browser, 'Tests passed'); } }); emitter.on('run-end', (error) => { if (error) { this.log(chalk.red, 'Test run ended in failure:', error); } else { this.log(chalk.green, 'Test run ended with great success'); } if (!this.options.ttyOutput) { this.updateStatus(true); } }); } // Specialized Reporting updateStatus(force) { if (!this.options.ttyOutput && !force) { return; } // EXTREME TERMINOLOGY FAIL, but here's a glossary: // // stats: An object containing test stats (total, passing, failing, etc). // state: The state that the run is in (running, etc). // status: A string representation of above. const statuses = Object.keys(this.browserStats).map((browserIdStr) => { const browserId = parseInt(browserIdStr, 10); const pretty = this.prettyBrowsers[browserId]; const stats = this.browserStats[browserId]; let status = ''; const counts = [stats.passing, stats.pending, stats.failing]; if (counts[0] > 0 || counts[1] > 0 || counts[2] > 0) { if (counts[0] > 0) { counts[0] = chalk.green(counts[0].toString()); } if (counts[1] > 0) { counts[1] = chalk.yellow(counts[1].toString()); } if (counts[2] > 0) { counts[2] = chalk.red(counts[2].toString()); } status = counts.join('/'); } if (stats.status === 'error') { status = status + (status === '' ? '' : ' ') + chalk.red('error'); } return padRight(pretty + ' (' + status + ')', STATUS_PAD); }); this.writeWrapped(statuses, ' '); } writeTestError(browser, data) { this.log(browser, this.stateIcon(data.state), this.prettyTest(data)); const error = data.error || {}; this.write('\n'); let prettyMessage = error.message || error; if (typeof prettyMessage !== 'string') { prettyMessage = util.inspect(prettyMessage); } this.write(chalk.red(' ' + prettyMessage)); if (error.stack) { try { this.write(stacky.pretty(data.error.stack, STACKY_CONFIG)); } catch (err) { // If we couldn't extract a stack (i.e. there was no stack), the message // is enough. } } this.write('\n'); } // Object Formatting stateIcon(state) { const color = STATE_COLORS[state] || STATE_COLORS['unknown']; return color(STATE_ICONS[state] || STATE_ICONS.unknown); } prettyTest(data) { const color = STATE_COLORS[data.state] || STATE_COLORS['unknown']; return color(data.test.join(' » ') || '<unknown test>'); } prettyBrowser(browser) { const parts = []; if (browser.platform && !browser.deviceName) { parts.push(browser.platform); } const name = browser.deviceName || browser.browserName; parts.push(SHORT[name] || name); if (browser.version) { parts.push(browser.version); } if (browser.variant) { parts.push(`[${browser.variant}]`); } return chalk.blue(parts.join(' ')); } log() { let values = Array.from(arguments); let format; if (_.isFunction(values[0])) { format = values[0]; values = values.slice(1); } if (values[0] && values[0].browserName) { values[0] = padRight(this.prettyBrowser(values[0]), BROWSER_PAD); } let line = _.toArray(values) .map((value) => _.isString(value) ? value : util.inspect(value)) .join(' '); line = line.replace(/[\s\n\r]+$/, ''); if (format) { line = format(line); } this.write(line); } writeWrapped(blocks, separator) { if (blocks.length === 0) { return; } const lines = ['']; const width = this.stream.columns || 0; for (const block of blocks) { const line = lines[lines.length - 1]; const combined = line + separator + block; if (line === '') { lines[lines.length - 1] = block; } else if (chalk.stripColor(combined).length <= width) { lines[lines.length - 1] = combined; } else { lines.push(block); } } this.writeLines(['\n'].concat(lines)); if (this.options.ttyOutput) { this.stream.write('\r'); this.stream.write('\u001b[' + (lines.length + 1) + 'A'); } } write(line) { this.writeLines([line]); this.updateStatus(); } writeLines(lines) { for (let line of lines) { if (line[line.length - 1] !== '\n') { line = line + '\n'; } if (this.options.ttyOutput) { line = '\u001b[J' + line; } this.stream.write(line); } this.linesWritten = lines.length; } flush() { if (!this.options.ttyOutput) { return; } // Add an extra line for padding. for (let i = 0; i <= this.linesWritten; i++) { this.stream.write('\n'); } } } // HACK CliReporter.CliReporter = CliReporter; exports.CliReporter = CliReporter; // Yeah, yeah. function padRight(str, length) { let currLength = chalk.stripColor(str).length; while (currLength < length) { currLength = currLength + 1; str = str + ' '; } return str; } module.exports = CliReporter;