UNPKG

6.39 kBJavaScriptView Raw
1var helper = require('./helper')
2var events = require('./events')
3var logger = require('./logger')
4
5var Result = require('./browser_result')
6
7// The browser is ready to execute tests.
8var READY = 1
9
10// The browser is executing the tests.
11var EXECUTING = 2
12
13// The browser is not executing, but temporarily disconnected (waiting for reconnecting).
14var READY_DISCONNECTED = 3
15
16// The browser is executing the tests, but temporarily disconnect (waiting for reconnecting).
17var EXECUTING_DISCONNECTED = 4
18
19// The browser got permanently disconnected (being removed from the collection and destroyed).
20var DISCONNECTED = 5
21
22var Browser = function (id, fullName, /* capturedBrowsers */ collection, emitter, socket, timer,
23 /* config.browserDisconnectTimeout */ disconnectDelay,
24 /* config.browserNoActivityTimeout */ noActivityTimeout) {
25 var name = helper.browserFullNameToShort(fullName)
26 var log = logger.create(name)
27 var activeSockets = [socket]
28 var activeSocketsIds = function () {
29 return activeSockets.map(function (s) {
30 return s.id
31 }).join(', ')
32 }
33
34 var self = this
35 var pendingDisconnect
36 var disconnect = function (reason) {
37 self.state = DISCONNECTED
38 self.disconnectsCount++
39 log.warn('Disconnected (%d times)' + (reason || ''), self.disconnectsCount)
40 collection.remove(self)
41 }
42
43 var noActivityTimeoutId
44 var refreshNoActivityTimeout = noActivityTimeout ? function () {
45 clearNoActivityTimeout()
46 noActivityTimeoutId = timer.setTimeout(function () {
47 self.lastResult.totalTimeEnd()
48 self.lastResult.disconnected = true
49 disconnect(', because no message in ' + noActivityTimeout + ' ms.')
50 emitter.emit('browser_complete', self)
51 }, noActivityTimeout)
52 } : function () {}
53
54 var clearNoActivityTimeout = noActivityTimeout ? function () {
55 if (noActivityTimeoutId) {
56 timer.clearTimeout(noActivityTimeoutId)
57 noActivityTimeoutId = null
58 }
59 } : function () {}
60
61 this.id = id
62 this.fullName = fullName
63 this.name = name
64 this.state = READY
65 this.lastResult = new Result()
66 this.disconnectsCount = 0
67
68 this.init = function () {
69 collection.add(this)
70
71 events.bindAll(this, socket)
72
73 log.info('Connected on socket %s with id %s', socket.id, id)
74
75 // TODO(vojta): move to collection
76 emitter.emit('browsers_change', collection)
77
78 emitter.emit('browser_register', this)
79 }
80
81 this.isReady = function () {
82 return this.state === READY
83 }
84
85 this.toString = function () {
86 return this.name
87 }
88
89 this.onKarmaError = function (error) {
90 if (this.isReady()) {
91 return
92 }
93
94 this.lastResult.error = true
95 emitter.emit('browser_error', this, error)
96
97 refreshNoActivityTimeout()
98 }
99
100 this.onInfo = function (info) {
101 if (this.isReady()) {
102 return
103 }
104
105 // TODO(vojta): remove
106 if (helper.isDefined(info.dump)) {
107 emitter.emit('browser_log', this, info.dump, 'dump')
108 }
109
110 if (helper.isDefined(info.log)) {
111 emitter.emit('browser_log', this, info.log, info.type)
112 }
113
114 refreshNoActivityTimeout()
115 }
116
117 this.onStart = function (info) {
118 this.lastResult = new Result()
119 this.lastResult.total = info.total
120
121 if (info.total === null) {
122 log.warn('Adapter did not report total number of specs.')
123 }
124
125 emitter.emit('browser_start', this, info)
126 refreshNoActivityTimeout()
127 }
128
129 this.onComplete = function (result) {
130 if (this.isReady()) {
131 return
132 }
133
134 this.state = READY
135 this.lastResult.totalTimeEnd()
136
137 if (!this.lastResult.success) {
138 this.lastResult.error = true
139 }
140
141 emitter.emit('browsers_change', collection)
142 emitter.emit('browser_complete', this, result)
143
144 clearNoActivityTimeout()
145 }
146
147 this.onDisconnect = function (_, disconnectedSocket) {
148 activeSockets.splice(activeSockets.indexOf(disconnectedSocket), 1)
149
150 if (activeSockets.length) {
151 log.debug('Disconnected %s, still have %s', disconnectedSocket.id, activeSocketsIds())
152 return
153 }
154
155 if (this.state === READY) {
156 disconnect()
157 } else if (this.state === EXECUTING) {
158 log.debug('Disconnected during run, waiting %sms for reconnecting.', disconnectDelay)
159 this.state = EXECUTING_DISCONNECTED
160
161 pendingDisconnect = timer.setTimeout(function () {
162 self.lastResult.totalTimeEnd()
163 self.lastResult.disconnected = true
164 disconnect()
165 emitter.emit('browser_complete', self)
166 }, disconnectDelay)
167
168 clearNoActivityTimeout()
169 }
170 }
171
172 this.reconnect = function (newSocket) {
173 if (this.state === EXECUTING_DISCONNECTED) {
174 this.state = EXECUTING
175 log.debug('Reconnected on %s.', newSocket.id)
176 } else if (this.state === EXECUTING || this.state === READY) {
177 log.debug('New connection %s (already have %s)', newSocket.id, activeSocketsIds())
178 } else if (this.state === DISCONNECTED) {
179 this.state = READY
180 log.info('Connected on socket %s with id %s', newSocket.id, this.id)
181 collection.add(this)
182
183 // TODO(vojta): move to collection
184 emitter.emit('browsers_change', collection)
185
186 emitter.emit('browser_register', this)
187 }
188
189 var exists = activeSockets.some(function (s) {
190 return s.id === newSocket.id
191 })
192 if (!exists) {
193 activeSockets.push(newSocket)
194 events.bindAll(this, newSocket)
195 }
196
197 if (pendingDisconnect) {
198 timer.clearTimeout(pendingDisconnect)
199 }
200
201 refreshNoActivityTimeout()
202 }
203
204 this.onResult = function (result) {
205 if (result.length) {
206 return result.forEach(this.onResult, this)
207 }
208
209 // ignore - probably results from last run (after server disconnecting)
210 if (this.isReady()) {
211 return
212 }
213
214 this.lastResult.add(result)
215
216 emitter.emit('spec_complete', this, result)
217 refreshNoActivityTimeout()
218 }
219
220 this.serialize = function () {
221 return {
222 id: this.id,
223 name: this.name,
224 isReady: this.state === READY
225 }
226 }
227
228 this.execute = function (config) {
229 activeSockets.forEach(function (socket) {
230 socket.emit('execute', config)
231 })
232
233 this.state = EXECUTING
234 refreshNoActivityTimeout()
235 }
236}
237
238Browser.STATE_READY = READY
239Browser.STATE_EXECUTING = EXECUTING
240Browser.STATE_READY_DISCONNECTED = READY_DISCONNECTED
241Browser.STATE_EXECUTING_DISCONNECTED = EXECUTING_DISCONNECTED
242Browser.STATE_DISCONNECTED = DISCONNECTED
243
244module.exports = Browser