UNPKG

6.56 kBJavaScriptView Raw
1var Promise = require('bluebird')
2var Jobs = require('qjobs')
3
4var helper = require('./helper')
5var log = require('./logger').create('launcher')
6
7var baseDecorator = require('./launchers/base').decoratorFactory
8var captureTimeoutDecorator = require('./launchers/capture_timeout').decoratorFactory
9var retryDecorator = require('./launchers/retry').decoratorFactory
10var processDecorator = require('./launchers/process').decoratorFactory
11
12// TODO(vojta): remove once nobody uses it
13var baseBrowserDecoratorFactory = function (
14 baseLauncherDecorator,
15 captureTimeoutLauncherDecorator,
16 retryLauncherDecorator,
17 processLauncherDecorator,
18 processKillTimeout
19) {
20 return function (launcher) {
21 baseLauncherDecorator(launcher)
22 captureTimeoutLauncherDecorator(launcher)
23 retryLauncherDecorator(launcher)
24 processLauncherDecorator(launcher, processKillTimeout)
25 }
26}
27
28var Launcher = function (server, emitter, injector) {
29 this._browsers = []
30 var lastStartTime
31 var self = this
32
33 var getBrowserById = function (id) {
34 for (var i = 0; i < self._browsers.length; i++) {
35 if (self._browsers[i].id === id) {
36 return self._browsers[i]
37 }
38 }
39
40 return null
41 }
42
43 this.launchSingle = function (protocol, hostname, port, urlRoot, upstreamProxy, processKillTimeout) {
44 var self = this
45 return function (name) {
46 name = (name + '').trim();
47 if (upstreamProxy) {
48 protocol = upstreamProxy.protocol
49 hostname = upstreamProxy.hostname
50 port = upstreamProxy.port
51 urlRoot = upstreamProxy.path + urlRoot.substr(1)
52 }
53 var url = protocol + '//' + hostname + ':' + port + urlRoot
54
55 var locals = {
56 id: ['value', Launcher.generateId()],
57 name: ['value', name],
58 processKillTimeout: ['value', processKillTimeout],
59 baseLauncherDecorator: ['factory', baseDecorator],
60 captureTimeoutLauncherDecorator: ['factory', captureTimeoutDecorator],
61 retryLauncherDecorator: ['factory', retryDecorator],
62 processLauncherDecorator: ['factory', processDecorator],
63 baseBrowserDecorator: ['factory', baseBrowserDecoratorFactory]
64 }
65
66 // TODO(vojta): determine script from name
67 if (name.indexOf('/') !== -1) {
68 name = 'Script'
69 }
70
71 try {
72 var browser = injector.createChild([locals], ['launcher:' + name]).get('launcher:' + name)
73 } catch (e) {
74 if (e.message.indexOf('No provider for "launcher:' + name + '"') !== -1) {
75 log.error('Cannot load browser "%s": it is not registered! ' +
76 'Perhaps you are missing some plugin?', name)
77 } else {
78 log.error('Cannot load browser "%s"!\n ' + e.stack, name)
79 }
80
81 emitter.emit('load_error', 'launcher', name)
82 return
83 }
84
85 // TODO(vojta): remove in v1.0 (BC for old launchers)
86 if (!browser.forceKill) {
87 browser.forceKill = function () {
88 var me = this
89 return new Promise(function (resolve) {
90 me.kill(resolve)
91 })
92 }
93
94 browser.restart = function () {
95 var me = this
96 this.kill(function () {
97 me.start(url)
98 })
99 }
100 }
101
102 self.jobs.add(function (args, done) {
103 log.info('Starting browser %s', helper.isDefined(browser.displayName) ? browser.displayName : browser.name)
104
105 browser.on('browser_process_failure', function () {
106 done(browser.error)
107 })
108
109 browser.on('done', function () {
110 // We are not done if there was an error as first the retry takes
111 // place which we catch with `browser_process_failure` if it fails
112 if (browser.error || browser.state === browser.STATE_RESTARTING) return
113
114 done(null, browser)
115 })
116
117 browser.start(url)
118 }, [])
119
120 self.jobs.run()
121 self._browsers.push(browser)
122 }
123 }
124
125 this.launch = function (names, concurrency) {
126 log.info(
127 'Launching browser%s %s with %s',
128 names.length > 1 ? 's' : '',
129 names.join(', '),
130 concurrency === Infinity ? 'unlimited concurrency' : 'concurrency ' + concurrency
131 )
132 this.jobs = new Jobs({maxConcurrency: concurrency})
133
134 var self = this
135 lastStartTime = Date.now()
136
137 if (server.loadErrors.length === 0) {
138 names.forEach(function (name) {
139 injector.invoke(self.launchSingle, self)(name)
140 })
141 } else {
142 // Empty task to ensure `end` is emitted
143 this.jobs.add(function (args, done) {
144 done()
145 }, [])
146 }
147
148 this.jobs.on('end', function (err) {
149 log.debug('Finished all browsers')
150
151 if (err) {
152 log.error(err)
153 }
154 })
155
156 this.jobs.run()
157
158 return self._browsers
159 }
160
161 this.launch.$inject = [
162 'config.browsers',
163 'config.concurrency',
164 'config.processKillTimeout'
165 ]
166
167 this.launchSingle.$inject = [
168 'config.protocol',
169 'config.hostname',
170 'config.port',
171 'config.urlRoot',
172 'config.upstreamProxy',
173 'config.processKillTimeout'
174 ]
175
176 this.kill = function (id, callback) {
177 var browser = getBrowserById(id)
178 callback = callback || function () {}
179
180 if (!browser) {
181 process.nextTick(callback)
182 return false
183 }
184
185 browser.forceKill().then(callback)
186 return true
187 }
188
189 this.restart = function (id) {
190 var browser = getBrowserById(id)
191
192 if (!browser) {
193 return false
194 }
195
196 browser.restart()
197 return true
198 }
199
200 this.killAll = function (callback) {
201 log.debug('Disconnecting all browsers')
202
203 var remaining = 0
204 var finish = function () {
205 remaining--
206 if (!remaining && callback) {
207 callback()
208 }
209 }
210
211 if (!self._browsers.length) {
212 return process.nextTick(callback)
213 }
214
215 self._browsers.forEach(function (browser) {
216 remaining++
217 browser.forceKill().then(finish)
218 })
219 }
220
221 this.areAllCaptured = function () {
222 return !self._browsers.some(function (browser) {
223 return !browser.isCaptured()
224 })
225 }
226
227 this.markCaptured = function (id) {
228 self._browsers.forEach(function (browser) {
229 if (browser.id === id) {
230 browser.markCaptured()
231 log.debug('%s (id %s) captured in %d secs', browser.name, browser.id,
232 (Date.now() - lastStartTime) / 1000)
233 }
234 })
235 }
236
237 // register events
238 emitter.on('exit', this.killAll)
239}
240
241Launcher.$inject = ['server', 'emitter', 'injector']
242
243Launcher.generateId = function () {
244 return '' + Math.floor(Math.random() * 100000000)
245}
246
247// PUBLISH
248exports.Launcher = Launcher