karma
Version:
Spectacular Test Runner for JavaScript.
246 lines (192 loc) • 6.46 kB
JavaScript
var helper = require('./helper')
var events = require('./events')
var logger = require('./logger')
var Result = require('./browser_result')
// The browser is ready to execute tests.
var READY = 1
// The browser is executing the tests.
var EXECUTING = 2
// The browser is not executing, but temporarily disconnected (waiting for reconnecting).
var READY_DISCONNECTED = 3
// The browser is executing the tests, but temporarily disconnect (waiting for reconnecting).
var EXECUTING_DISCONNECTED = 4
// The browser got permanently disconnected (being removed from the collection and destroyed).
var DISCONNECTED = 5
var Browser = function (id, fullName, /* capturedBrowsers */ collection, emitter, socket, timer,
/* config.browserDisconnectTimeout */ disconnectDelay,
/* config.browserNoActivityTimeout */ noActivityTimeout) {
var name = helper.browserFullNameToShort(fullName)
var log = logger.create(name)
var activeSockets = [socket]
var activeSocketsIds = function () {
return activeSockets.map(function (s) {
return s.id
}).join(', ')
}
var self = this
var pendingDisconnect
var disconnect = function (reason) {
self.state = DISCONNECTED
self.disconnectsCount++
log.warn('Disconnected (%d times)' + (reason || ''), self.disconnectsCount)
emitter.emit('browser_error', self, 'Disconnected' + reason)
collection.remove(self)
}
var noActivityTimeoutId
var refreshNoActivityTimeout = noActivityTimeout ? function () {
clearNoActivityTimeout()
noActivityTimeoutId = timer.setTimeout(function () {
self.lastResult.totalTimeEnd()
self.lastResult.disconnected = true
disconnect(', because no message in ' + noActivityTimeout + ' ms.')
emitter.emit('browser_complete', self)
}, noActivityTimeout)
} : function () {}
var clearNoActivityTimeout = noActivityTimeout ? function () {
if (noActivityTimeoutId) {
timer.clearTimeout(noActivityTimeoutId)
noActivityTimeoutId = null
}
} : function () {}
this.id = id
this.fullName = fullName
this.name = name
this.state = READY
this.lastResult = new Result()
this.disconnectsCount = 0
this.init = function () {
collection.add(this)
events.bindAll(this, socket)
log.info('Connected on socket %s with id %s', socket.id, id)
// TODO(vojta): move to collection
emitter.emit('browsers_change', collection)
emitter.emit('browser_register', this)
}
this.isReady = function () {
return this.state === READY
}
this.toString = function () {
return this.name
}
this.onKarmaError = function (error) {
if (this.isReady()) {
return
}
this.lastResult.error = true
emitter.emit('browser_error', this, error)
refreshNoActivityTimeout()
}
this.onInfo = function (info) {
if (this.isReady()) {
return
}
// TODO(vojta): remove
if (helper.isDefined(info.dump)) {
emitter.emit('browser_log', this, info.dump, 'dump')
}
if (helper.isDefined(info.log)) {
emitter.emit('browser_log', this, info.log, info.type)
}
refreshNoActivityTimeout()
}
this.onStart = function (info) {
this.lastResult = new Result()
this.lastResult.total = info.total
if (info.total === null) {
log.warn('Adapter did not report total number of specs.')
}
emitter.emit('browser_start', this, info)
refreshNoActivityTimeout()
}
this.onComplete = function (result) {
if (this.isReady()) {
return
}
this.state = READY
this.lastResult.totalTimeEnd()
if (!this.lastResult.success) {
this.lastResult.error = true
}
emitter.emit('browsers_change', collection)
emitter.emit('browser_complete', this, result)
clearNoActivityTimeout()
}
this.onDisconnect = function (_, disconnectedSocket) {
activeSockets.splice(activeSockets.indexOf(disconnectedSocket), 1)
if (activeSockets.length) {
log.debug('Disconnected %s, still have %s', disconnectedSocket.id, activeSocketsIds())
return
}
if (this.state === READY) {
disconnect()
} else if (this.state === EXECUTING) {
log.debug('Disconnected during run, waiting %sms for reconnecting.', disconnectDelay)
this.state = EXECUTING_DISCONNECTED
pendingDisconnect = timer.setTimeout(function () {
self.lastResult.totalTimeEnd()
self.lastResult.disconnected = true
disconnect()
emitter.emit('browser_complete', self)
}, disconnectDelay)
clearNoActivityTimeout()
}
}
this.reconnect = function (newSocket) {
if (this.state === EXECUTING_DISCONNECTED) {
this.state = EXECUTING
log.debug('Reconnected on %s.', newSocket.id)
} else if (this.state === EXECUTING || this.state === READY) {
log.debug('New connection %s (already have %s)', newSocket.id, activeSocketsIds())
} else if (this.state === DISCONNECTED) {
this.state = READY
log.info('Connected on socket %s with id %s', newSocket.id, this.id)
collection.add(this)
// TODO(vojta): move to collection
emitter.emit('browsers_change', collection)
emitter.emit('browser_register', this)
}
var exists = activeSockets.some(function (s) {
return s.id === newSocket.id
})
if (!exists) {
activeSockets.push(newSocket)
events.bindAll(this, newSocket)
}
if (pendingDisconnect) {
timer.clearTimeout(pendingDisconnect)
}
refreshNoActivityTimeout()
}
this.onResult = function (result) {
if (result.length) {
return result.forEach(this.onResult, this)
}
// ignore - probably results from last run (after server disconnecting)
if (this.isReady()) {
return
}
this.lastResult.add(result)
emitter.emit('spec_complete', this, result)
refreshNoActivityTimeout()
}
this.serialize = function () {
return {
id: this.id,
name: this.name,
isReady: this.state === READY
}
}
this.execute = function (config) {
activeSockets.forEach(function (socket) {
socket.emit('execute', config)
})
this.state = EXECUTING
refreshNoActivityTimeout()
}
}
Browser.STATE_READY = READY
Browser.STATE_EXECUTING = EXECUTING
Browser.STATE_READY_DISCONNECTED = READY_DISCONNECTED
Browser.STATE_EXECUTING_DISCONNECTED = EXECUTING_DISCONNECTED
Browser.STATE_DISCONNECTED = DISCONNECTED
module.exports = Browser