nightwatch
Version:
Easy to use Node.js based end-to-end testing solution for web applications using the W3C WebDriver API.
533 lines (414 loc) • 13 kB
JavaScript
const util = require('util');
const boxen = require('boxen');
const didYouMean = require('didyoumean');
const AssertionError = require('assertion-error');
const lodashEscape = require('lodash/escape');
const LogSettings = require('./log_settings.js');
const chalkColors = require('../chalkColors.js');
const addDetailedError = require('../addDetailedError.js');
const Errors = require('../../transport/errors');
const beautifyStackTrace = require('../beautifyStackTrace.js');
const alwaysDisplayError = require('../alwaysDisplayError.js');
const Severity = {
LOG: 'LOG',
INFO: 'INFO',
WARN: 'WARN',
ERROR: 'ERROR'
};
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep',
'Oct', 'Nov', 'Dec'];
function pad(n) {
return n < 10 ? '0' + n.toString(10) : n.toString(10);
}
let __instance__;
// 26 Feb 16:19:34
function timestamp(d = new Date(), format) {
if (format === 'iso') {
return d.toISOString();
}
const time = [
pad(d.getHours()),
pad(d.getMinutes()),
pad(d.getSeconds())
].join(':');
return [d.getDate(), months[d.getMonth()], time].join(' ');
}
function inspectObject(obj) {
return util.inspect(obj, {
showHidden: false,
depth: 4,
colors: chalkColors.colorsEnabled
})
.replace(/^\s{2}/gm, ' ')
.replace(/^}$/m, ' }');
}
function logObject(obj) {
// eslint-disable-next-line no-console
console.log(' ', inspectObject(obj));
}
function logTimestamp(d) {
if (LogSettings.isLogTimestamp()) {
return chalkColors.colors.white.bold(timestamp(d, LogSettings.timestampFormat));
}
return '';
}
function logMessage(type, message, args, alwaysShow) {
if (!message || !LogSettings.outputEnabled || !LogSettings.enabled && !alwaysShow) {
return;
}
const colors = chalkColors.colors;
let messageStr = '';
let logMethod = 'log';
let prefix;
const d = new Date();
const timeIso = d.toISOString();
let timestamp = logTimestamp(d);
switch (type) {
case Severity.ERROR:
prefix = colors.bgBlack.yellow.bold(type);
// eslint-disable-next-line no-control-regex
messageStr = message.match(/\u001b\[.*?m/) ? message : colors.red.bold(message);
logMethod = 'error';
break;
case Severity.INFO:
prefix = colors.bgBlack.magenta.bold(type);
// eslint-disable-next-line no-control-regex
messageStr = message.match(/\u001b\[.*?m/) ? message : colors.cyan.bold(message);
break;
case Severity.LOG:
prefix = colors.bgBlack.white.bold(type + ' ');
// eslint-disable-next-line no-control-regex
messageStr = message.match(/\u001b\[.*?m/) ? message : colors.white.bold(message);
break;
case Severity.WARN:
prefix = colors.bgBlack.green.bold(type);
// eslint-disable-next-line no-control-regex
messageStr = message.match(/\u001b\[.*?m/) ? message : colors.green.bold(message);
logMethod = 'warn';
break;
}
if (LogSettings.timestampFormat === 'iso') {
let severity = type.toUpperCase();
if (severity === Severity.LOG) {
severity = Severity.INFO;
}
timestamp += ` ${severity} nightwatch:`;
}
// eslint-disable-next-line no-console
console[logMethod](timestamp, messageStr);
if (args.length > 0) {
let inlineArgs = [];
args.forEach(function(item) {
if (item === undefined) {
return;
}
if (Object.prototype.toString.call(item) === '[object Object]' && Object.keys(item).length > 0) {
if (inlineArgs.length) {
// eslint-disable-next-line no-console
console[logMethod](...inlineArgs);
inlineArgs = [];
}
logObject(item);
} else {
inlineArgs.push(item);
}
});
if (inlineArgs.length) {
// eslint-disable-next-line no-console
console[logMethod](...inlineArgs);
inlineArgs = [];
}
}
}
function findSuggestion({message} = {}) {
if (message && message.includes('is not a function')) {
const match = message.replace(' is not a function', '').replace('browser.', '');
if (match) {
const suggestion = didYouMean(match, ['element.findAll']);
if (suggestion) {
return suggestion;
}
}
}
return null;
}
function logRequest(message, params) {
if (!message || !LogSettings.htmlReporterEnabled) {
return;
}
const d = new Date();
const timeIso = d.toISOString();
const instance = Logger.getInstance();
instance.output.push([timeIso, lodashEscape(message), lodashEscape(inspectObject(params))]);
instance.commandOutput.push([timeIso, lodashEscape(message), lodashEscape(inspectObject(params))]);
instance.testSectionOutput.push([timeIso, lodashEscape(message), lodashEscape(inspectObject(params))]);
instance.testHooksOutput.push([timeIso, lodashEscape(message), lodashEscape(inspectObject(params))]);
}
function logError(severity, errOrMessage, args) {
const alwaysDisplay = LogSettings.isErrorLogEnabled() && severity === Severity.ERROR;
logMessage(severity, errOrMessage, args, alwaysDisplay);
}
class Logger {
static getInstance() {
return __instance__;
}
constructor() {
this.colors = chalkColors.colors;
this.output = [];
this.commandOutput = [];
this.testSectionOutput = [];
this.testHooksOutput = [];
}
logMessage(...args) {
logMessage(...args);
}
inspectObject(obj) {
return inspectObject(obj);
}
info(message, ...args) {
logMessage('INFO', message, args);
}
log(message, ...args) {
logMessage('LOG', message, args);
}
warn(message, ...args) {
message = this.getErrorContent(message);
logError('WARN', message, args);
}
error(message, ...args) {
message = this.getErrorContent(message);
logError('ERROR', message, args);
}
request(message, params) {
logRequest(message, params);
}
response(message, params) {
logRequest(message, params);
}
underline(text) {
if (!this.colors) {
return text;
}
return '\u{1b}[4m' + text + '\u{1b}[24m';
}
setOptions(settings) {
this.setOutputEnabled(settings.output);
this.setHtmlReporterEnabled(typeof settings.output_folder == 'string');
this.setDetailedOutput(settings.detailed_output);
this.setLogTimestamp(settings.output_timestamp, settings.timestamp_format);
this.setErrorLog(settings.disable_error_log);
if (settings.disable_colors) {
this.disableColors();
}
if (settings.silent) {
this.disable();
} else {
this.enable();
}
}
disableColors() {
chalkColors.disable();
}
setHtmlReporterEnabled(value) {
LogSettings.htmlReporterEnabled = value;
}
disable() {
LogSettings.disable();
}
enable() {
LogSettings.enable();
}
setOutputEnabled(val = true) {
LogSettings.outputEnabled = val;
}
isOutputEnabled() {
return LogSettings.outputEnabled;
}
isHtmlReporterEnabled() {
return LogSettings.htmlReporterEnabled;
}
setDetailedOutput(val) {
LogSettings.detailedOutput = val;
}
isDetailedOutput() {
return LogSettings.outputEnabled && LogSettings.detailedOutput;
}
setLogTimestamp(val, format) {
LogSettings.setLogTimestamp(val, format);
}
setHttpLogOptions(opts) {
LogSettings.setHttpLogOptions(opts);
}
showRequestData() {
return LogSettings.showRequestData;
}
showResponseHeaders() {
return LogSettings.showResponseHeaders;
}
isLogTimestamp() {
return LogSettings.isLogTimestamp();
}
isErrorLogEnabled() {
return LogSettings.isErrorLogEnabled();
}
isEnabled() {
return LogSettings.enabled;
}
setErrorLog(val = false) {
LogSettings.disableErrorLog = val;
}
logDetailedMessage(message, type = 'log') {
if (!LogSettings.outputEnabled || !LogSettings.detailedOutput) {
return;
}
// eslint-disable-next-line no-console
console[type](message);
}
getFailedAssertions(assertions, modulepath) {
return assertions.reduce((prev, a) => {
if (a.failure !== false) {
prev.push(` ${this.colors.red.bold('→') + this.getErrorContent(a, modulepath)}`);
}
return prev;
}, []).join('\n');
}
isAssertionError(err) {
return (err instanceof AssertionError) || err.name === 'AssertionError' || err.name === 'NightwatchAssertError' || err.name === 'NightwatchMountError';
}
getErrorContent(error, modulepath) {
let errorObj;
if (this.isAssertionError(error)) {
errorObj = error;
} else {
errorObj = Errors.getErrorObject(error);
}
addDetailedError(errorObj, modulepath);
const content = [];
let errorTitle = ` ${this.colors.light_red(errorObj.name)}`;
if (this.isAssertionError(error) || alwaysDisplayError(error)) {
errorTitle = ` ${this.colors.red.bold('✖') + errorTitle}`;
}
const message = [];
if (errorObj.message || errorObj.fullMsg) {
let errorObjMessage = errorObj.message;
// eslint-disable-next-line no-control-regex
errorObjMessage = errorObjMessage.match(/\u001b\[.*?m/) ? errorObjMessage : this.colors.red(errorObjMessage);
message.push(errorObjMessage);
if (errorObj.detailedErr) {
message.push(this.colors.light_green(errorObj.detailedErr));
if (errorObj.extraDetail) {
message.push(this.colors.light_green(errorObj.extraDetail));
}
}
}
content.push(errorTitle);
const showStack = errorObj.showTrace || errorObj.showTrace === undefined;
if (!showStack && errorObj.reportShown) {
return ' ' + message.join('\n ');
}
if (!showStack && !this.isAssertionError(error) && !error.help) {
return '\n' + boxen(`${message.join('\n')}\n`, {padding: 1, borderColor: 'red'}) + '\n';
}
const messageStr = message.join('\n ');
content.push(` ${messageStr}`);
const suggestion = findSuggestion(error);
if (suggestion) {
errorObj.help = errorObj.help || [];
errorObj.help.unshift(' did you mean: ' + suggestion + '?');
}
if (errorObj.help) {
content.push(this.colors.brown('\n Try fixing by :'));
errorObj.help.forEach((step, index) => {
content.push(` ${this.colors.blue(index + 1)}. ${step}`);
});
}
if (errorObj.link){
content.push(`\n ${this.colors.brown('Read More')} : \n ${this.colors.cyan(errorObj.link)} `);
}
let stack = error.stack || error.stackTrace;
if (stack && showStack) {
stack = ' at' + stack.split(/ {4}at/g).slice(1).join(' at');
const beautified = beautifyStackTrace(stack, true, modulepath);
if (beautified) {
content.push(this.colors.brown('\n Error location:'));
content.push(beautified);
}
if (alwaysDisplayError(errorObj)) {
content.push(this.colors.brown(' Stack Trace :'));
const coloredStack = stack.split('\n').map((line) => this.colors.stack_trace(line)).join('\n');
content.push(`${coloredStack}\n`);
}
}
if (content.length === 2) {
if (content[0].includes('WebDriverError')) {
return content.join(this.colors.light_red(':'));
}
if (content[0].includes('Error') && content[1].includes('Error while')) {
const firstLine = content.shift();
content[0] = ' ' + firstLine + content[0];
}
}
return content.join('\n');
}
formatMessage(msg, ...args) {
args = args.map(val => {
return this.colors.brown(val);
});
return util.format(msg, ...args);
}
}
module.exports = new (function() {
__instance__ = new Logger();
return Logger.getInstance();
});
const getOutput = module.exports.getOutput = function() {
if (!Logger.getInstance()) {
return [];
}
const {output} = Logger.getInstance();
return output.slice(0);
};
module.exports.collectOutput = function() {
const instance = Logger.getInstance();
if (!instance) {
return [];
}
const output = getOutput();
instance.output = [];
return output;
};
module.exports.collectTestSectionOutput = function() {
const instance = Logger.getInstance();
if (!instance) {
return [];
}
const {testSectionOutput} = instance;
instance.testSectionOutput = [];
return testSectionOutput;
};
module.exports.collectCommandOutput = function() {
const instance = Logger.getInstance();
if (!instance) {
return [];
}
const {commandOutput} = instance;
instance.commandOutput = [];
return commandOutput;
};
module.exports.collectTestHooksOutput = function() {
const instance = Logger.getInstance();
if (!instance) {
return [];
}
const {testHooksOutput} = instance;
instance.testHooksOutput = [];
return testHooksOutput;
};
module.exports.reset = function() {
const instance = Logger.getInstance();
if (!instance) {
return;
}
instance.testSectionOutput = [];
instance.output = [];
};