123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271 |
- 'use strict'
- const BrowserResult = require('./browser_result')
- const helper = require('./helper')
- const logger = require('./logger')
- const CONNECTED = 'CONNECTED' // The browser is connected but not yet been commanded to execute tests.
- const CONFIGURING = 'CONFIGURING' // The browser has been told to execute tests; it is configuring before tests execution.
- const EXECUTING = 'EXECUTING' // The browser is executing the tests.
- const EXECUTING_DISCONNECTED = 'EXECUTING_DISCONNECTED' // The browser is executing the tests, but temporarily disconnect (waiting for socket reconnecting).
- const DISCONNECTED = 'DISCONNECTED' // The browser got completely disconnected (e.g. browser crash) and can be only restored with a restart of execution.
- class Browser {
- constructor (id, fullName, collection, emitter, socket, timer, disconnectDelay,
- noActivityTimeout, singleRun, clientConfig) {
- this.id = id
- this.fullName = fullName
- this.name = helper.browserFullNameToShort(fullName)
- this.lastResult = new BrowserResult()
- this.disconnectsCount = 0
- this.activeSockets = [socket]
- this.noActivityTimeout = noActivityTimeout
- this.singleRun = singleRun
- this.clientConfig = clientConfig
- this.collection = collection
- this.emitter = emitter
- this.socket = socket
- this.timer = timer
- this.disconnectDelay = disconnectDelay
- this.log = logger.create(this.name)
- this.noActivityTimeoutId = null
- this.pendingDisconnect = null
- this.setState(CONNECTED)
- }
- init () {
- this.log.info(`Connected on socket ${this.socket.id} with id ${this.id}`)
- this.bindSocketEvents(this.socket)
- this.collection.add(this)
- this.emitter.emit('browser_register', this)
- }
- setState (toState) {
- this.log.debug(`${this.state} -> ${toState}`)
- this.state = toState
- }
- onKarmaError (error) {
- if (this.isNotConnected()) {
- this.lastResult.error = true
- }
- this.emitter.emit('browser_error', this, error)
- this.refreshNoActivityTimeout()
- }
- onInfo (info) {
- if (helper.isDefined(info.dump)) {
- this.emitter.emit('browser_log', this, info.dump, 'dump')
- }
- if (helper.isDefined(info.log)) {
- this.emitter.emit('browser_log', this, info.log, info.type)
- } else if (helper.isDefined(info.total)) {
- if (this.state === EXECUTING) {
- this.lastResult.total = info.total
- }
- } else if (!helper.isDefined(info.dump)) {
- this.emitter.emit('browser_info', this, info)
- }
- this.refreshNoActivityTimeout()
- }
- onStart (info) {
- if (info.total === null) {
- this.log.warn('Adapter did not report total number of specs.')
- }
- this.lastResult = new BrowserResult(info.total)
- this.setState(EXECUTING)
- this.emitter.emit('browser_start', this, info)
- this.refreshNoActivityTimeout()
- }
- onComplete (result) {
- if (this.isNotConnected()) {
- this.setState(CONNECTED)
- this.lastResult.totalTimeEnd()
- this.emitter.emit('browsers_change', this.collection)
- this.emitter.emit('browser_complete', this, result)
- this.clearNoActivityTimeout()
- }
- }
- onSocketDisconnect (reason, disconnectedSocket) {
- helper.arrayRemove(this.activeSockets, disconnectedSocket)
- if (this.activeSockets.length) {
- this.log.debug(`Disconnected ${disconnectedSocket.id}, still have ${this.getActiveSocketsIds()}`)
- return
- }
- if (this.isConnected()) {
- this.disconnect(`Client disconnected from CONNECTED state (${reason})`)
- } else if ([CONFIGURING, EXECUTING].includes(this.state)) {
- this.log.debug(`Disconnected during run, waiting ${this.disconnectDelay}ms for reconnecting.`)
- this.setState(EXECUTING_DISCONNECTED)
- this.pendingDisconnect = this.timer.setTimeout(() => {
- this.lastResult.totalTimeEnd()
- this.lastResult.disconnected = true
- this.disconnect(`reconnect failed before timeout of ${this.disconnectDelay}ms (${reason})`)
- this.emitter.emit('browser_complete', this)
- }, this.disconnectDelay)
- this.clearNoActivityTimeout()
- }
- }
- reconnect (newSocket, clientSaysReconnect) {
- if (!clientSaysReconnect || this.state === DISCONNECTED) {
- this.log.info(`Disconnected browser returned on socket ${newSocket.id} with id ${this.id}.`)
- this.setState(CONNECTED)
- // The disconnected browser is already part of the collection.
- // Update the collection view in the UI (header on client.html)
- this.emitter.emit('browsers_change', this.collection)
- // Notify the launcher
- this.emitter.emit('browser_register', this)
- // Execute tests if configured to do so.
- if (this.singleRun) {
- this.execute()
- }
- } else if (this.state === EXECUTING_DISCONNECTED) {
- this.log.debug('Lost socket connection, but browser continued to execute. Reconnected ' +
- `on socket ${newSocket.id}.`)
- this.setState(EXECUTING)
- } else if ([CONNECTED, CONFIGURING, EXECUTING].includes(this.state)) {
- this.log.debug(`Rebinding to new socket ${newSocket.id} (already have ` +
- `${this.getActiveSocketsIds()})`)
- }
- if (!this.activeSockets.some((s) => s.id === newSocket.id)) {
- this.activeSockets.push(newSocket)
- this.bindSocketEvents(newSocket)
- }
- if (this.pendingDisconnect) {
- this.timer.clearTimeout(this.pendingDisconnect)
- }
- this.refreshNoActivityTimeout()
- }
- onResult (result) {
- if (Array.isArray(result)) {
- result.forEach(this.onResult, this)
- } else if (this.isNotConnected()) {
- this.lastResult.add(result)
- this.emitter.emit('spec_complete', this, result)
- }
- this.refreshNoActivityTimeout()
- }
- execute () {
- this.activeSockets.forEach((socket) => socket.emit('execute', this.clientConfig))
- this.setState(CONFIGURING)
- this.refreshNoActivityTimeout()
- }
- getActiveSocketsIds () {
- return this.activeSockets.map((s) => s.id).join(', ')
- }
- disconnect (reason) {
- this.log.warn(`Disconnected (${this.disconnectsCount} times) ${reason || ''}`)
- this.disconnectsCount++
- this.emitter.emit('browser_error', this, `Disconnected ${reason || ''}`)
- this.remove()
- }
- remove () {
- this.setState(DISCONNECTED)
- this.collection.remove(this)
- }
- refreshNoActivityTimeout () {
- if (this.noActivityTimeout) {
- this.clearNoActivityTimeout()
- this.noActivityTimeoutId = this.timer.setTimeout(() => {
- this.lastResult.totalTimeEnd()
- this.lastResult.disconnected = true
- this.disconnect(`, because no message in ${this.noActivityTimeout} ms.`)
- this.emitter.emit('browser_complete', this)
- }, this.noActivityTimeout)
- }
- }
- clearNoActivityTimeout () {
- if (this.noActivityTimeout && this.noActivityTimeoutId) {
- this.timer.clearTimeout(this.noActivityTimeoutId)
- this.noActivityTimeoutId = null
- }
- }
- bindSocketEvents (socket) {
- // TODO: check which of these events are actually emitted by socket
- socket.on('disconnect', (reason) => this.onSocketDisconnect(reason, socket))
- socket.on('start', (info) => this.onStart(info))
- socket.on('karma_error', (error) => this.onKarmaError(error))
- socket.on('complete', (result) => this.onComplete(result))
- socket.on('info', (info) => this.onInfo(info))
- socket.on('result', (result) => this.onResult(result))
- }
- isConnected () {
- return this.state === CONNECTED
- }
- isNotConnected () {
- return !this.isConnected()
- }
- serialize () {
- return {
- id: this.id,
- name: this.name,
- isConnected: this.state === CONNECTED
- }
- }
- toString () {
- return this.name
- }
- toJSON () {
- return {
- id: this.id,
- fullName: this.fullName,
- name: this.name,
- state: this.state,
- lastResult: this.lastResult,
- disconnectsCount: this.disconnectsCount,
- noActivityTimeout: this.noActivityTimeout,
- disconnectDelay: this.disconnectDelay
- }
- }
- }
- Browser.factory = function (
- id, fullName, /* capturedBrowsers */ collection, emitter, socket, timer,
- /* config.browserDisconnectTimeout */ disconnectDelay,
- /* config.browserNoActivityTimeout */ noActivityTimeout,
- /* config.singleRun */ singleRun,
- /* config.client */ clientConfig) {
- return new Browser(id, fullName, collection, emitter, socket, timer,
- disconnectDelay, noActivityTimeout, singleRun, clientConfig)
- }
- Browser.STATE_CONNECTED = CONNECTED
- Browser.STATE_CONFIGURING = CONFIGURING
- Browser.STATE_EXECUTING = EXECUTING
- Browser.STATE_EXECUTING_DISCONNECTED = EXECUTING_DISCONNECTED
- Browser.STATE_DISCONNECTED = DISCONNECTED
- module.exports = Browser
|