@christian-bromann/webdriverio
Version:
A nodejs bindings implementation for selenium 2.0/webdriver
245 lines (213 loc) • 8.8 kB
JavaScript
import fs from 'fs'
import path from 'path'
import mkdirp from 'mkdirp'
import { COLORS, ERROR_CODES } from '../helpers/constants'
import sanitize from '../helpers/sanitize'
const BANNER = `
${COLORS.yellow}=======================================================================================${COLORS.reset}
Selenium 2.0 / webdriver protocol bindings implementation with helper commands in nodejs.
For a complete list of commands, visit ${COLORS.lime}http://webdriver.io/api.html${COLORS.reset}.
${COLORS.yellow}=======================================================================================${COLORS.reset}
`
/**
* Logger module
*
* A Logger helper with fancy colors
*/
class Logger {
constructor (options, eventHandler) {
/**
* log level
* silent : no logs
* command : command only
* result : result only
* error : error only
* verbose : command + data + result
*/
this.logLevel = options.logLevel
this.setupWriteStream(options)
/**
* disable colors if coloredLogs is set to false or if we pipe output into files
*/
if (!JSON.parse(process.env.WEBDRIVERIO_COLORED_LOGS) || this.writeStream) {
Object.keys(COLORS).forEach(colorName => { COLORS[colorName] = '' })
}
/**
* print welcome message
*/
if (this.logLevel !== 'silent' && this.logLevel !== 'error' && !this.infoHasBeenShown) {
this.write(BANNER)
this.infoHasBeenShown = true
}
// register event handler to log command events
eventHandler.on('command', (data) => {
if (this.logLevel === 'command' || this.logLevel === 'verbose') {
this.command(data.method, data.uri.path)
}
if (this.logLevel === 'data' || this.logLevel === 'verbose') {
this.data(data.data)
}
})
eventHandler.on('info', (msg) => {
if (this.logLevel === 'verbose') {
this.info(msg)
}
})
// register event handler to log result events
eventHandler.on('result', (data) => {
// only log result events if they got executed successfully
if (data.body && data.body.status === 0 && (this.logLevel === 'result' || this.logLevel === 'verbose')) {
this.result(typeof data.body.value ? data.body.value : data.body.orgStatusMessage)
}
})
// register event handler to log error events
eventHandler.on('error', (data) => {
if (data.err && data.err.code === 'ECONNREFUSED') {
this.error(`Couldn't find a running selenium server instance on ${data.requestOptions.uri}`)
} else if (data.err && data.err.code === 'ENOTFOUND') {
this.error(`Couldn't resolve hostname ${data.requestOptions.uri}`)
} else if (data.err && data.err.code === 'NOSESSIONID') {
this.error(`Couldn't get a session ID - ${data.err.message}`)
} else if (this.logLevel === 'error' || this.logLevel === 'verbose') {
if (data.body && ERROR_CODES[data.body.status]) {
this.error(ERROR_CODES[data.body.status].id + '\t' + ERROR_CODES[data.body.status].message + '\n\t\t\t' + data.body.value.message)
} else if (typeof data.message === 'string') {
this.error('ServerError\t' + data.message)
} else {
this.error(ERROR_CODES['-1'].id + '\t' + ERROR_CODES['-1'].message)
}
}
})
}
/**
* creates log file name and directories if not existing
* @param {Object} caps capabilities (required to create filename)
* @param {String} logOutputPath specified log directory
* @return {Buffer} log file buffer stream
*/
getLogfile (caps, logOutputPath) {
logOutputPath = path.isAbsolute(logOutputPath) ? logOutputPath : path.join(process.cwd(), logOutputPath)
/**
* create directory if not existing
*/
try {
fs.statSync(logOutputPath)
} catch (e) {
mkdirp.sync(logOutputPath)
}
let newDate = new Date()
let dateString = newDate.toISOString().split(/\./)[0].replace(/:/g, '-')
let filename = sanitize.caps(caps) + '.' + dateString + '.' + process.pid + '.log'
return fs.createWriteStream(path.join(logOutputPath, filename))
}
/**
* create write stream if logOutput is a string
*/
setupWriteStream (options) {
if (typeof options.logOutput === 'string') {
this.writeStream = this.getLogfile(options.desiredCapabilities, options.logOutput)
this.logLevel = this.logLevel === 'silent' ? 'verbose' : this.logLevel
} else if (typeof options.logOutput === 'object' && options.logOutput.writable) {
this.writeStream = options.logOutput
this.logLevel = this.logLevel === 'silent' ? 'verbose' : this.logLevel
}
}
write (...messages) {
let msgString = messages.join(' ')
if (this.writeStream) {
this.writeStream.write(msgString + '\n')
} else {
console.log(msgString)
}
}
/**
* main log function
*/
log (message, content) {
if (!this.logLevel || this.logLevel === 'silent') {
return
}
let currentDate = new Date()
let dateString = currentDate.toString().match(/\d\d:\d\d:\d\d/)[0]
let preamble = `${COLORS.dkgray}[${dateString}] ${COLORS.reset}`
if (!content) {
this.write(preamble, message)
} else {
this.write(preamble, message, '\t', JSON.stringify(sanitize.limit(content)))
}
}
/**
* logs command messages
* @param {String} method method of command request
* @param {String} path path of command request
*/
command (method, path) {
if (method && path) {
this.log(`${COLORS.violet}COMMAND\t${COLORS.reset}${method}`, path)
}
}
/**
* debugger info message
*/
debug () {
this.write('\n')
this.log(`${COLORS.yellow}DEBUG\t${COLORS.reset}queue has stopped, you can now go into the browser`)
this.log(`${COLORS.yellow}DEBUG\t${COLORS.dkgray}continue by pressing the [ENTER] key ...${COLORS.reset}`)
}
/**
* logs data messages
* @param {Object} data data object
*/
data (data) {
data = JSON.stringify(sanitize.limit(data))
if (data && (this.logLevel === 'data' || this.logLevel === 'verbose')) {
this.log(`${COLORS.brown}DATA\t\t${COLORS.reset}${data}`)
}
}
/**
* logs info messages
* @param {String} msg message
*/
info (msg) {
this.log(`${COLORS.blue}INFO\t${COLORS.reset}${msg}`)
}
/**
* logs result messages
* @param {Object} result result object
*/
result (result) {
result = sanitize.limit(JSON.stringify(result))
this.log(`${COLORS.teal}RESULT\t\t${COLORS.reset}${result}`)
}
/**
* logs error messages
* @param {String} msg error message
*/
error (msg) {
if (msg && typeof msg === 'string' && msg.indexOf('caused by Request') !== -1) {
msg = msg.substr(0, msg.indexOf('caused by Request') - 2)
}
if (msg && typeof msg === 'string' && msg.indexOf('Command duration or timeout') !== -1) {
msg = msg.substr(0, msg.indexOf('Command duration or timeout'))
}
if (msg && typeof msg === 'string' && msg.indexOf('ID does not correspond to an open view') !== -1) {
msg = msg.substr(0, msg.indexOf('ID does not correspond to an open view'))
msg += 'NOTE: you probably try to continue your tests after closing a tab/window. Please set the focus on a current opened tab/window to continue. Use the window protocol command to do so.'
}
if (msg) {
this.log(COLORS.red + 'ERROR\t' + COLORS.reset + msg, null)
}
}
/**
* print exception if command fails
* @param {String} type error type
* @param {String} message error message
* @param {String[]} stacktrace error stacktrace
*/
static printException (type, message, stacktrace) {
stacktrace = stacktrace.map(trace => ' at ' + trace)
this.write(COLORS.dkred + (type || 'Error') + ': ' + message + COLORS.reset, null)
this.write(COLORS.dkgray + stacktrace.reverse().join('\n') + COLORS.reset, null)
}
}
export default Logger