karma
Version:
Spectacular Test Runner for JavaScript.
243 lines (186 loc) • 6.37 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);
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.onError = 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);
}
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;