'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