1 | var Promise = require('bluebird')
|
2 | var Jobs = require('qjobs')
|
3 |
|
4 | var helper = require('./helper')
|
5 | var log = require('./logger').create('launcher')
|
6 |
|
7 | var baseDecorator = require('./launchers/base').decoratorFactory
|
8 | var captureTimeoutDecorator = require('./launchers/capture_timeout').decoratorFactory
|
9 | var retryDecorator = require('./launchers/retry').decoratorFactory
|
10 | var processDecorator = require('./launchers/process').decoratorFactory
|
11 |
|
12 |
|
13 | var 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 |
|
28 | var 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 |
|
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 |
|
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 |
|
111 |
|
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 |
|
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 |
|
238 | emitter.on('exit', this.killAll)
|
239 | }
|
240 |
|
241 | Launcher.$inject = ['server', 'emitter', 'injector']
|
242 |
|
243 | Launcher.generateId = function () {
|
244 | return '' + Math.floor(Math.random() * 100000000)
|
245 | }
|
246 |
|
247 |
|
248 | exports.Launcher = Launcher
|