| 'use strict' |
| |
| const Promise = require('bluebird') |
| const Jobs = require('qjobs') |
| |
| const log = require('./logger').create('launcher') |
| |
| const baseDecorator = require('./launchers/base').decoratorFactory |
| const captureTimeoutDecorator = require('./launchers/capture_timeout').decoratorFactory |
| const retryDecorator = require('./launchers/retry').decoratorFactory |
| const processDecorator = require('./launchers/process').decoratorFactory |
| |
| // TODO(vojta): remove once nobody uses it |
| const baseBrowserDecoratorFactory = function ( |
| baseLauncherDecorator, |
| captureTimeoutLauncherDecorator, |
| retryLauncherDecorator, |
| processLauncherDecorator, |
| processKillTimeout |
| ) { |
| return function (launcher) { |
| baseLauncherDecorator(launcher) |
| captureTimeoutLauncherDecorator(launcher) |
| retryLauncherDecorator(launcher) |
| processLauncherDecorator(launcher, processKillTimeout) |
| } |
| } |
| |
| function Launcher (server, emitter, injector) { |
| this._browsers = [] |
| let lastStartTime |
| |
| const getBrowserById = (id) => this._browsers.find((browser) => browser.id === id) |
| |
| this.launchSingle = (protocol, hostname, port, urlRoot, upstreamProxy, processKillTimeout) => { |
| if (upstreamProxy) { |
| protocol = upstreamProxy.protocol |
| hostname = upstreamProxy.hostname |
| port = upstreamProxy.port |
| urlRoot = upstreamProxy.path + urlRoot.substr(1) |
| } |
| |
| return (name) => { |
| let browser |
| const locals = { |
| id: ['value', Launcher.generateId()], |
| name: ['value', name], |
| processKillTimeout: ['value', processKillTimeout], |
| baseLauncherDecorator: ['factory', baseDecorator], |
| captureTimeoutLauncherDecorator: ['factory', captureTimeoutDecorator], |
| retryLauncherDecorator: ['factory', retryDecorator], |
| processLauncherDecorator: ['factory', processDecorator], |
| baseBrowserDecorator: ['factory', baseBrowserDecoratorFactory] |
| } |
| |
| // TODO(vojta): determine script from name |
| if (name.includes('/')) { |
| name = 'Script' |
| } |
| |
| try { |
| browser = injector.createChild([locals], ['launcher:' + name]).get('launcher:' + name) |
| } catch (e) { |
| if (e.message.includes(`No provider for "launcher:${name}"`)) { |
| log.error(`Cannot load browser "${name}": it is not registered! Perhaps you are missing some plugin?`) |
| } else { |
| log.error(`Cannot load browser "${name}"!\n ` + e.stack) |
| } |
| |
| emitter.emit('load_error', 'launcher', name) |
| return |
| } |
| |
| this.jobs.add((args, done) => { |
| log.info(`Starting browser ${browser.displayName || browser.name}`) |
| |
| browser.on('browser_process_failure', () => done(browser.error)) |
| |
| browser.on('done', () => { |
| if (!browser.error && browser.state !== browser.STATE_RESTARTING) { |
| done(null, browser) |
| } |
| }) |
| |
| browser.start(`${protocol}//${hostname}:${port}${urlRoot}`) |
| }, []) |
| |
| this.jobs.run() |
| this._browsers.push(browser) |
| } |
| } |
| |
| this.launch = (names, concurrency) => { |
| log.info(`Launching browsers ${names.join(', ')} with concurrency ${concurrency === Infinity ? 'unlimited' : concurrency}`) |
| this.jobs = new Jobs({ maxConcurrency: concurrency }) |
| |
| lastStartTime = Date.now() |
| |
| if (server.loadErrors.length) { |
| this.jobs.add((args, done) => done(), []) |
| } else { |
| names.forEach((name) => injector.invoke(this.launchSingle, this)(name)) |
| } |
| |
| this.jobs.on('end', (err) => { |
| log.debug('Finished all browsers') |
| |
| if (err) { |
| log.error(err) |
| } |
| }) |
| |
| this.jobs.run() |
| |
| return this._browsers |
| } |
| |
| this.launch.$inject = [ |
| 'config.browsers', |
| 'config.concurrency', |
| 'config.processKillTimeout' |
| ] |
| |
| this.launchSingle.$inject = [ |
| 'config.protocol', |
| 'config.hostname', |
| 'config.port', |
| 'config.urlRoot', |
| 'config.upstreamProxy', |
| 'config.processKillTimeout' |
| ] |
| |
| this.kill = (id, callback) => { |
| callback = callback || function () {} |
| const browser = getBrowserById(id) |
| |
| if (browser) { |
| browser.forceKill().then(callback) |
| return true |
| } |
| process.nextTick(callback) |
| return false |
| } |
| |
| this.restart = (id) => { |
| const browser = getBrowserById(id) |
| if (browser) { |
| browser.restart() |
| return true |
| } |
| return false |
| } |
| |
| this.killAll = (callback) => { |
| callback = callback || function () {} |
| log.debug('Disconnecting all browsers') |
| |
| if (!this._browsers.length) { |
| return process.nextTick(callback) |
| } |
| |
| Promise.all( |
| this._browsers |
| .map((browser) => browser.forceKill()) |
| ).then(callback) |
| } |
| |
| this.areAllCaptured = () => this._browsers.every((browser) => browser.isCaptured()) |
| |
| this.markCaptured = (id) => { |
| const browser = getBrowserById(id) |
| if (browser) { |
| browser.markCaptured() |
| log.debug(`${browser.name} (id ${browser.id}) captured in ${(Date.now() - lastStartTime) / 1000} secs`) |
| } |
| } |
| |
| emitter.on('exit', this.killAll) |
| } |
| |
| Launcher.$inject = ['server', 'emitter', 'injector'] |
| Launcher.generateId = () => Math.floor(Math.random() * 100000000).toString() |
| |
| exports.Launcher = Launcher |