When existing reporters don't produce the exact output format your team needs — a Slack notification, a custom dashboard payload, or a specialized log format — writing a custom reporter plugin is straightforward. This lesson covers the pattern.
The Shape of a Reporter Plugin
A reporter plugin is a constructor function that receives baseReporterDecorator (shared reporting behavior) and implements a handful of lifecycle callback methods Karma calls automatically as a run progresses: onSpecComplete, onRunComplete, and others.
// my-reporter.js
function SlackSummaryReporter(baseReporterDecorator, config) {
baseReporterDecorator(this);
let failures = 0;
this.onSpecComplete = (browser, result) => {
if (!result.success) failures++;
};
this.onRunComplete = (browsers, results) => {
console.log(`Run complete: ${results.failed} failed, ${results.success} passed.`);
// e.g. POST a summary to a webhook here
};
}
SlackSummaryReporter.$inject = ['baseReporterDecorator', 'config'];
module.exports = {
'reporter:slack-summary': ['type', SlackSummaryReporter]
};
onSpecComplete fires once per spec; onRunComplete fires once at the very end, with an aggregate results object.
The plugin object's key follows the reporter:<name> convention, matching the name used in reporters.
Multiple reporters (built-in and custom) run simultaneously without interfering with each other.
onBrowserComplete and other lifecycle hooks exist too — check baseReporterDecorator's available hooks for the full list.
Custom reporters can read config (injected via $inject) for their own settings, following the same convention as junitReporter/htmlReporter.
Reporter Lifecycle Hooks Cheat Sheet
The most commonly used hooks available to a reporter plugin.
Hook
Fires When
onRunStart
Once, right before the run begins
onBrowserStart
Once per browser, when it connects
onSpecComplete
Once per individual spec result
onBrowserComplete
Once per browser, when its run finishes
onRunComplete
Once, with the final aggregated results
A Practical Example: Posting to a Webhook
One of the most common real-world custom reporters posts a run summary to a chat webhook (Slack, Microsoft Teams, etc.), useful for teams wanting immediate visibility into CI test results without checking a dashboard.
Wrapping the webhook call in try/catch prevents a reporting failure from crashing the entire Karma process.
Reading Custom Configuration in a Reporter
Following the same convention as built-in reporters, a custom reporter typically reads its own settings from a dedicated top-level config key, named to match the reporter itself.
Letting a reporting-side error (like a failed webhook call) crash the entire Karma process instead of catching it.
Forgetting the reporter:<name> naming convention, causing Karma to fail resolving the custom reporter.
Not distinguishing between per-spec (onSpecComplete) and whole-run (onRunComplete) hooks when choosing where logic belongs.
Hardcoding secrets (like a webhook URL) directly in the reporter file instead of reading them from config/environment variables.
Key Takeaways
A custom reporter plugin implements lifecycle hooks like onSpecComplete and onRunComplete.
The reporter:<name> plugin key convention matches the name used in the reporters array.
Custom reporters can read their own settings from a dedicated config key, just like built-in reporters.
Errors inside a custom reporter should be caught explicitly to avoid crashing the entire test run.
Pro Tip
Start any custom reporter with just a console.log() inside every lifecycle hook you think you need, run the suite once, and read the actual data shape Karma passes in. Building against the real, observed shape is far more reliable than guessing from documentation alone, since exact result object shapes have shifted slightly across Karma versions.
You now understand custom reporter plugins. Next, learn how to write a custom preprocessor plugin.