Spectacular Test Runner for JavaScript with multi-browser support and plugin architecture.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Karma's extensive plugin architecture enables customization of browsers, reporters, preprocessors, testing frameworks, and middleware. The plugin system uses dependency injection and provides standardized interfaces for extensibility.
Load and resolve Karma plugins from various sources including npm packages, inline objects, and glob patterns.
/**
* Resolve plugins from configuration
* @param plugins - Plugin specifications array
* @param emitter - Event emitter for plugin events
* @returns Array of resolved plugin modules
*/
function resolve(plugins: string[], emitter: EventEmitter): any[];
/**
* Create plugin instantiation function
* @param injector - Dependency injector instance
* @returns Function for instantiating plugins
*/
function createInstantiatePlugin(injector: any): Function;Plugin Resolution Examples:
// In karma.conf.js
module.exports = function(config) {
config.set({
plugins: [
// NPM package names
'karma-jasmine',
'karma-chrome-launcher',
'karma-coverage',
// Glob patterns
'karma-*',
// Scoped packages
'@angular-devkit/build-angular/plugins/karma',
// Inline plugin objects
{
'framework:custom': ['factory', function() {
// Custom framework implementation
}]
},
// Relative paths
'./custom-plugins/my-plugin.js'
]
});
};Karma supports several plugin categories, each with specific interfaces and lifecycles.
/**
* Plugin type definitions and interfaces
*/
interface PluginTypes {
// Browser launchers - launch and manage browser instances
'launcher': BrowserLauncher;
// Test result reporters - format and output test results
'reporter': Reporter;
// File preprocessors - transform files before serving
'preprocessor': Preprocessor;
// Testing frameworks - integrate test libraries
'framework': Framework;
// HTTP middleware - custom request handling
'middleware': Middleware;
}Create custom browser launchers for different environments and browser configurations.
/**
* Browser launcher interface
*/
interface BrowserLauncher {
/**
* Start browser instance
* @param url - Test runner URL
* @param onExit - Exit callback
*/
start(url: string, onExit: Function): void;
/**
* Kill browser process
* @param done - Completion callback
*/
kill(done: Function): void;
/**
* Force kill browser process
* @returns Promise for completion
*/
forceKill(): Promise<void>;
/**
* Mark browser as captured
*/
markCaptured(): void;
/**
* Check if browser is captured
* @returns True if captured
*/
isCaptured(): boolean;
// Browser state properties
state: 'BEING_CAPTURED' | 'CAPTURED' | 'BEING_KILLED' | 'FINISHED' | 'RESTARTING';
id: string;
name: string;
displayName: string;
}
/**
* Base launcher decorator factory
*/
interface BaseLauncherDecorator {
(launcher: any): void;
}Custom Launcher Examples:
// Custom Chrome launcher with specific flags
module.exports = function(config) {
config.set({
customLaunchers: {
ChromeHeadlessCustom: {
base: 'ChromeHeadless',
flags: [
'--no-sandbox',
'--disable-web-security',
'--disable-features=VizDisplayCompositor',
'--remote-debugging-port=9222'
]
},
ChromeDebug: {
base: 'Chrome',
flags: ['--remote-debugging-port=9333'],
debug: true
},
FirefoxHeadlessCustom: {
base: 'FirefoxHeadless',
prefs: {
'network.proxy.type': 1,
'network.proxy.http': 'localhost',
'network.proxy.http_port': 9090
}
}
},
browsers: ['ChromeHeadlessCustom', 'FirefoxHeadlessCustom']
});
};
// Inline custom launcher plugin
const customLauncher = {
'launcher:CustomBrowser': ['type', function CustomBrowserLauncher() {
this.start = function(url) {
// Launch custom browser
};
this.kill = function(done) {
// Kill browser process
done();
};
}]
};
module.exports = function(config) {
config.set({
plugins: [customLauncher],
browsers: ['CustomBrowser']
});
};Create custom reporters to format and output test results in different ways.
/**
* Reporter interface
*/
interface Reporter {
/**
* Test run started
* @param browsers - Array of browser instances
*/
onRunStart(browsers: Browser[]): void;
/**
* Browser started
* @param browser - Browser instance
*/
onBrowserStart(browser: Browser): void;
/**
* Browser completed
* @param browser - Browser instance
* @param result - Test results
*/
onBrowserComplete(browser: Browser, result: BrowserResult): void;
/**
* Browser error
* @param browser - Browser instance
* @param error - Error object
*/
onBrowserError(browser: Browser, error: any): void;
/**
* Browser log message
* @param browser - Browser instance
* @param log - Log message
* @param type - Log type
*/
onBrowserLog(browser: Browser, log: string, type: string): void;
/**
* Individual test completed
* @param browser - Browser instance
* @param result - Test result
*/
onSpecComplete(browser: Browser, result: TestResult): void;
/**
* Test run completed
* @param browsers - Array of browser instances
* @param results - Overall results
*/
onRunComplete(browsers: Browser[], results: TestResults): void;
// Optional methods
write?(chunk: string): void;
writeCommonMsg?(msg: string): void;
onExit?(done: Function): void;
}
/**
* Base reporter with utility methods
*/
class BaseReporter implements Reporter {
write(chunk: string): void;
renderBrowser(browser: Browser): string;
specSuccess(browser: Browser, result: TestResult): void;
specFailure(browser: Browser, result: TestResult): void;
specSkipped(browser: Browser, result: TestResult): void;
}Custom Reporter Examples:
// Custom JSON reporter
const jsonReporter = function(baseReporterDecorator, config, logger, helper) {
baseReporterDecorator(this);
const log = logger.create('reporter.json');
const reporterConfig = config.jsonReporter || {};
const outputFile = reporterConfig.outputFile || 'test-results.json';
let results = {
browsers: {},
summary: {},
tests: []
};
this.onRunStart = function(browsers) {
results.timestamp = new Date().toISOString();
results.browsers = browsers.map(b => ({
id: b.id,
name: b.name,
fullName: b.fullName
}));
};
this.onSpecComplete = function(browser, result) {
results.tests.push({
browser: browser.name,
suite: result.suite,
description: result.description,
success: result.success,
time: result.time,
log: result.log
});
};
this.onRunComplete = function(browsers, summary) {
results.summary = summary;
helper.mkdirIfNotExists(path.dirname(outputFile), () => {
fs.writeFileSync(outputFile, JSON.stringify(results, null, 2));
log.info('Test results written to %s', outputFile);
});
};
};
jsonReporter.$inject = ['baseReporterDecorator', 'config', 'logger', 'helper'];
module.exports = {
'reporter:json': ['type', jsonReporter]
};Transform files before they are served to browsers, enabling transpilation, bundling, and instrumentation.
/**
* Preprocessor interface
*/
interface Preprocessor {
/**
* Process file content
* @param content - Original file content
* @param file - File object with metadata
* @param done - Completion callback with (error, content)
*/
(content: string, file: File, done: (error?: any, content?: string) => void): void;
}
/**
* File object passed to preprocessors
*/
interface File {
originalPath: string; // Original file path
path: string; // Processed file path
contentPath: string; // Path to content file
mtime: Date; // Modification time
isUrl: boolean; // Is external URL
isBinary: boolean; // Is binary file
}Custom Preprocessor Examples:
// Simple string replacement preprocessor
const stringReplace = function(args, config, logger) {
const log = logger.create('preprocessor.string-replace');
const options = args.options || {};
return function(content, file, done) {
log.debug('Processing "%s".', file.originalPath);
let result = content;
// Apply string replacements
if (options.replacements) {
options.replacements.forEach(replacement => {
result = result.replace(replacement.from, replacement.to);
});
}
done(null, result);
};
};
stringReplace.$inject = ['args', 'config', 'logger'];
// ES6 to ES5 transpiler preprocessor
const babelPreprocessor = function(args, config, logger) {
const babel = require('@babel/core');
const log = logger.create('preprocessor.babel');
return function(content, file, done) {
log.debug('Processing "%s".', file.originalPath);
try {
const result = babel.transform(content, {
filename: file.originalPath,
presets: ['@babel/preset-env'],
sourceMaps: 'inline'
});
done(null, result.code);
} catch (error) {
log.error('Error processing "%s": %s', file.originalPath, error.message);
done(error);
}
};
};
babelPreprocessor.$inject = ['args', 'config', 'logger'];
module.exports = {
'preprocessor:string-replace': ['factory', stringReplace],
'preprocessor:babel': ['factory', babelPreprocessor]
};Integrate different testing libraries and frameworks with Karma.
/**
* Framework interface
*/
interface Framework {
/**
* Initialize framework on client side
* @param files - File list to modify
*/
(files: any[]): void;
}Framework Plugin Examples:
// Custom testing framework
const customFramework = function(files) {
// Add framework files to the file list
files.unshift({
pattern: require.resolve('./custom-framework.js'),
included: true,
served: true,
watched: false
});
// Add adapter file
files.unshift({
pattern: require.resolve('./custom-adapter.js'),
included: true,
served: true,
watched: false
});
};
customFramework.$inject = ['config.files'];
module.exports = {
'framework:custom': ['factory', customFramework]
};Add custom HTTP middleware to the Karma server for handling specific requests.
/**
* Middleware interface
*/
interface Middleware {
/**
* Express-style middleware function
* @param req - HTTP request
* @param res - HTTP response
* @param next - Next middleware function
*/
(req: any, res: any, next: Function): void;
}Middleware Examples:
// API endpoint middleware
const apiMiddleware = function(config) {
return function(req, res, next) {
if (req.url.startsWith('/api/')) {
// Handle API requests
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify({ message: 'API response' }));
} else {
next();
}
};
};
apiMiddleware.$inject = ['config'];
// CORS middleware
const corsMiddleware = function() {
return function(req, res, next) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
if (req.method === 'OPTIONS') {
res.end();
} else {
next();
}
};
};
module.exports = {
'middleware:api': ['factory', apiMiddleware],
'middleware:cors': ['factory', corsMiddleware]
};Karma includes several built-in plugins and supports many community plugins.
// Built-in and popular browser launchers
'karma-chrome-launcher' // Google Chrome
'karma-firefox-launcher' // Mozilla Firefox
'karma-safari-launcher' // Safari
'karma-ie-launcher' // Internet Explorer
'karma-edge-launcher' // Microsoft Edge
'karma-phantomjs-launcher' // PhantomJS (deprecated)
'karma-browserstack-launcher' // BrowserStack
'karma-sauce-launcher' // Sauce Labs// Built-in and popular reporters
'karma-progress-reporter' // Progress dots (built-in)
'karma-dots-reporter' // Simple dots (built-in)
'karma-junit-reporter' // JUnit XML
'karma-coverage-reporter' // Code coverage
'karma-spec-reporter' // Detailed spec output
'karma-json-reporter' // JSON output
'karma-teamcity-reporter' // TeamCity integration// Popular preprocessors
'karma-babel-preprocessor' // Babel transpilation
'karma-webpack' // Webpack bundling
'karma-rollup-preprocessor' // Rollup bundling
'karma-browserify' // Browserify bundling
'karma-typescript' // TypeScript compilation
'karma-coverage' // Code coverage instrumentation// Popular testing frameworks
'karma-jasmine' // Jasmine
'karma-mocha' // Mocha
'karma-qunit' // QUnit
'karma-tap' // TAP
'karma-jest' // Jest compatibility