'use strict' const log = require('./logger').create() class Executor { constructor (capturedBrowsers, config, emitter) { this.capturedBrowsers = capturedBrowsers this.config = config this.emitter = emitter this.executionScheduled = false this.errorsScheduled = [] this.pendingCount = 0 this.runningBrowsers = null this.emitter.on('run_complete', () => this.onRunComplete()) this.emitter.on('browser_complete', () => this.onBrowserComplete()) } schedule () { if (this.capturedBrowsers.length === 0) { log.warn(`No captured browser, open ${this.config.protocol}//${this.config.hostname}:${this.config.port}${this.config.urlRoot}`) return false } else if (this.capturedBrowsers.areAllReady()) { log.debug('All browsers are ready, executing') log.debug(`Captured ${this.capturedBrowsers.length} browsers`) this.executionScheduled = false this.capturedBrowsers.clearResults() this.pendingCount = this.capturedBrowsers.length this.runningBrowsers = this.capturedBrowsers.clone() this.emitter.emit('run_start', this.runningBrowsers) this.socketIoSockets.emit('execute', this.config.client) return true } else { log.info('Delaying execution, these browsers are not ready: ' + this.capturedBrowsers.getNonReady().join(', ')) this.executionScheduled = true return false } } /** * Schedule an error to be reported * @param {string} errorMessage * @returns {boolean} a boolean indicating whether or not the error was handled synchronously */ scheduleError (errorMessage) { // We don't want to interfere with any running test. // Verify that no test is running before reporting the error. if (this.capturedBrowsers.areAllReady()) { log.warn(errorMessage) const errorResult = { success: 0, failed: 0, skipped: 0, error: errorMessage, exitCode: 1 } const noBrowsersStartedTests = [] this.emitter.emit('run_start', noBrowsersStartedTests) // A run cannot complete without being started this.emitter.emit('run_complete', noBrowsersStartedTests, errorResult) return true } else { this.errorsScheduled.push(errorMessage) return false } } onRunComplete () { if (this.executionScheduled) { this.schedule() } if (this.errorsScheduled.length) { const errorsToReport = this.errorsScheduled this.errorsScheduled = [] errorsToReport.forEach((error) => this.scheduleError(error)) } } onBrowserComplete () { this.pendingCount-- if (!this.pendingCount) { // Ensure run_complete is emitted in the next tick // so it is never emitted before browser_complete setTimeout(() => { this.emitter.emit('run_complete', this.runningBrowsers, this.runningBrowsers.getResults()) }) } } } Executor.factory = function (capturedBrowsers, config, emitter) { return new Executor(capturedBrowsers, config, emitter) } module.exports = Executor