UNPKG

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