diff --git a/lib/reporter/global-reporter.js b/lib/reporter/global-reporter.js index 110ec7b64b..341d0cf0f5 100644 --- a/lib/reporter/global-reporter.js +++ b/lib/reporter/global-reporter.js @@ -342,9 +342,12 @@ module.exports = class GlobalReporter { const reporters = plugins.reduce((prev, pluginName) => { const plugin = PluginLoader.load(pluginName); if (isFunction(plugin.instance?.reporter)) { - prev.push(plugin.instance.reporter); + prev.push(plugin.instance); } else if (plugin.globals?.reporter && isFunction(plugin.globals.reporter)) { - prev.push(plugin.globals.reporter); + prev.push({ + reporter: plugin.globals.reporter, + settings: plugin.globals.settings || {} + }); } return prev; @@ -353,12 +356,77 @@ module.exports = class GlobalReporter { return reporters; } + getTimeoutValueMs(customReporter) { + // global timeout for all custom reporters – set by user + if (this.settings.custom_reporter_timeout) { + return this.settings.custom_reporter_timeout; + } + + // timeout for a specific custom reporter – set by plugin + if (customReporter.settings?.timeoutMs) { + return customReporter.settings.timeoutMs; + } + + // default timeout when nothing is set – set by nightwatch + return this.settings.globals.customReporterCallbackTimeout; + } + + callReporterFn(reporter, globalResults, {callbackTimeoutId, resolve, reject}) { + try { + if (reporter.length === 2) { + const reporterFnAsync = Utils.makeFnAsync(2, reporter, this.settings.globals); + reporterFnAsync.call(this.settings.globals, globalResults, function () { + clearTimeout(callbackTimeoutId); + resolve(); + }); + } else { + const promise = reporter.call(this.settings.globals, globalResults); + + if (promise instanceof Promise) { + promise + .then(() => { + clearTimeout(callbackTimeoutId); + resolve(); + }) + .catch(err => { + clearTimeout(callbackTimeoutId); + reject(err); + }); + } else { + clearTimeout(callbackTimeoutId); + resolve(); + } + } + } catch (err) { + clearTimeout(callbackTimeoutId); + reject(err); + } + } + runCustomGlobalReporter(globalResults) { const pluginReporters = this.getPluginReporters(); - let customReporters = this.settings.globals.reporter; + let customReporters; + + if (Utils.isFunction(this.settings.globals.reporter)) { + customReporters = [{ + reporter: this.settings.globals.reporter, + settings: { + timeoutMs: this.settings.globals.customReporterCallbackTimeout + } + }]; + } else if (Array.isArray(this.settings.globals.reporter)) { + customReporters = this.settings.globals.reporter.map(reporter => { + if (Utils.isFunction(reporter)) { + return { + reporter, + settings: { + timeoutMs: this.settings.globals.customReporterCallbackTimeout + } + }; + } - if (customReporters && !Array.isArray(customReporters)) { - customReporters = [customReporters]; + return reporter; + }); } customReporters = customReporters.concat(pluginReporters); @@ -366,38 +434,21 @@ module.exports = class GlobalReporter { const results = customReporters.map(customReporter => { return new Promise((resolve, reject) => { const callbackTimeoutId = setTimeout(() => { - reject(new Error('Timeout while waiting (20s) for the custom global reporter callback to be called.')); - }, this.settings.globals.customReporterCallbackTimeout); + const reporterName = customReporter.reporterName ? `"${customReporter.reporterName}" ` : ''; - try { - if (customReporter.length === 2) { - const reporterFnAsync = Utils.makeFnAsync(2, customReporter, this.settings.globals); - reporterFnAsync.call(this.settings.globals, globalResults, function () { - clearTimeout(callbackTimeoutId); - resolve(); - }); - } else { - const promise = customReporter.call(this.settings.globals, globalResults); - - if (promise instanceof Promise) { - promise - .then(() => { - clearTimeout(callbackTimeoutId); - resolve(); - }) - .catch(err => { - clearTimeout(callbackTimeoutId); - reject(err); - }); - } else { - clearTimeout(callbackTimeoutId); - resolve(); - } - } - } catch (err) { - clearTimeout(callbackTimeoutId); - reject(err); - } + const error = new Error(`Timeout while waiting for the custom reporter ${reporterName}to finish.`); + error.help = [ + 'Make sure the custom reporter calls the "done" callback when finished.', + 'If the reporter is async, make sure the async operation is completed before the timeout is reached.', + 'You can extend the timeout by defining the "custom_reporter_timeout" config setting in your nightwatch config file.' + ]; + error.link = 'See https://nightwatchjs.org/guide/extending-nightwatch/adding-custom-reporters.html for more details.'; + + reject(error); + }, this.getTimeoutValueMs(customReporter)); + + const {reporter} = customReporter; + this.callReporterFn(reporter, globalResults, {callbackTimeoutId, resolve, reject}); }); }); diff --git a/test/src/runner/testRunWithGlobalReporter.js b/test/src/runner/testRunWithGlobalReporter.js index 750467baac..d63ac02b99 100644 --- a/test/src/runner/testRunWithGlobalReporter.js +++ b/test/src/runner/testRunWithGlobalReporter.js @@ -26,7 +26,7 @@ describe('testRunWithGlobalReporter', function() { }); it('testRunWithGlobalReporter', function() { - let testsPath = path.join(__dirname, '../../sampletests/before-after'); + const testsPath = path.join(__dirname, '../../sampletests/before-after'); const globals = { reporterCount: 0 }; @@ -42,7 +42,7 @@ describe('testRunWithGlobalReporter', function() { }); it('testRunner with global async reporter', function() { - let testsPath = path.join(__dirname, '../../sampletests/before-after'); + const testsPath = path.join(__dirname, '../../sampletests/before-after'); let reporterCount = 0; return runTests(testsPath, settings({ @@ -62,7 +62,7 @@ describe('testRunWithGlobalReporter', function() { }); it('testRunner with global async reporter and timeout error', function() { - let testsPath = path.join(__dirname, '../../sampletests/before-after'); + const testsPath = path.join(__dirname, '../../sampletests/before-after'); let reporterCount = 0; return runTests(testsPath, settings({ @@ -77,12 +77,12 @@ describe('testRunWithGlobalReporter', function() { })).then(_ => { assert.strictEqual(reporterCount, 1); }).catch(err => { - assert.strictEqual(err.message, 'Timeout while waiting (20s) for the custom global reporter callback to be called.'); + assert.strictEqual(err.message, 'Timeout while waiting for the custom reporter to finish.'); }); }); it('to check skipped count in global reporter', function() { - let testsPath = path.join(__dirname, '../../sampletests/globalreporterskippedcount/sample.js'); + const testsPath = path.join(__dirname, '../../sampletests/globalreporterskippedcount/sample.js'); let reporterCount = 0; return runTests(testsPath, settings({ @@ -97,7 +97,7 @@ describe('testRunWithGlobalReporter', function() { output_folder: false })).then(_ => { assert.strictEqual(reporterCount, 1); - }).catch(err => (err)) + }).catch(err => (err)); });