0
# Plugin System
1
2
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.
3
4
## Capabilities
5
6
### Plugin Resolution
7
8
Load and resolve Karma plugins from various sources including npm packages, inline objects, and glob patterns.
9
10
```javascript { .api }
11
/**
12
* Resolve plugins from configuration
13
* @param plugins - Plugin specifications array
14
* @param emitter - Event emitter for plugin events
15
* @returns Array of resolved plugin modules
16
*/
17
function resolve(plugins: string[], emitter: EventEmitter): any[];
18
19
/**
20
* Create plugin instantiation function
21
* @param injector - Dependency injector instance
22
* @returns Function for instantiating plugins
23
*/
24
function createInstantiatePlugin(injector: any): Function;
25
```
26
27
**Plugin Resolution Examples:**
28
29
```javascript
30
// In karma.conf.js
31
module.exports = function(config) {
32
config.set({
33
plugins: [
34
// NPM package names
35
'karma-jasmine',
36
'karma-chrome-launcher',
37
'karma-coverage',
38
39
// Glob patterns
40
'karma-*',
41
42
// Scoped packages
43
'@angular-devkit/build-angular/plugins/karma',
44
45
// Inline plugin objects
46
{
47
'framework:custom': ['factory', function() {
48
// Custom framework implementation
49
}]
50
},
51
52
// Relative paths
53
'./custom-plugins/my-plugin.js'
54
]
55
});
56
};
57
```
58
59
### Plugin Types
60
61
Karma supports several plugin categories, each with specific interfaces and lifecycles.
62
63
```javascript { .api }
64
/**
65
* Plugin type definitions and interfaces
66
*/
67
interface PluginTypes {
68
// Browser launchers - launch and manage browser instances
69
'launcher': BrowserLauncher;
70
71
// Test result reporters - format and output test results
72
'reporter': Reporter;
73
74
// File preprocessors - transform files before serving
75
'preprocessor': Preprocessor;
76
77
// Testing frameworks - integrate test libraries
78
'framework': Framework;
79
80
// HTTP middleware - custom request handling
81
'middleware': Middleware;
82
}
83
```
84
85
### Browser Launchers
86
87
Create custom browser launchers for different environments and browser configurations.
88
89
```javascript { .api }
90
/**
91
* Browser launcher interface
92
*/
93
interface BrowserLauncher {
94
/**
95
* Start browser instance
96
* @param url - Test runner URL
97
* @param onExit - Exit callback
98
*/
99
start(url: string, onExit: Function): void;
100
101
/**
102
* Kill browser process
103
* @param done - Completion callback
104
*/
105
kill(done: Function): void;
106
107
/**
108
* Force kill browser process
109
* @returns Promise for completion
110
*/
111
forceKill(): Promise<void>;
112
113
/**
114
* Mark browser as captured
115
*/
116
markCaptured(): void;
117
118
/**
119
* Check if browser is captured
120
* @returns True if captured
121
*/
122
isCaptured(): boolean;
123
124
// Browser state properties
125
state: 'BEING_CAPTURED' | 'CAPTURED' | 'BEING_KILLED' | 'FINISHED' | 'RESTARTING';
126
id: string;
127
name: string;
128
displayName: string;
129
}
130
131
/**
132
* Base launcher decorator factory
133
*/
134
interface BaseLauncherDecorator {
135
(launcher: any): void;
136
}
137
```
138
139
**Custom Launcher Examples:**
140
141
```javascript
142
// Custom Chrome launcher with specific flags
143
module.exports = function(config) {
144
config.set({
145
customLaunchers: {
146
ChromeHeadlessCustom: {
147
base: 'ChromeHeadless',
148
flags: [
149
'--no-sandbox',
150
'--disable-web-security',
151
'--disable-features=VizDisplayCompositor',
152
'--remote-debugging-port=9222'
153
]
154
},
155
156
ChromeDebug: {
157
base: 'Chrome',
158
flags: ['--remote-debugging-port=9333'],
159
debug: true
160
},
161
162
FirefoxHeadlessCustom: {
163
base: 'FirefoxHeadless',
164
prefs: {
165
'network.proxy.type': 1,
166
'network.proxy.http': 'localhost',
167
'network.proxy.http_port': 9090
168
}
169
}
170
},
171
172
browsers: ['ChromeHeadlessCustom', 'FirefoxHeadlessCustom']
173
});
174
};
175
176
// Inline custom launcher plugin
177
const customLauncher = {
178
'launcher:CustomBrowser': ['type', function CustomBrowserLauncher() {
179
this.start = function(url) {
180
// Launch custom browser
181
};
182
183
this.kill = function(done) {
184
// Kill browser process
185
done();
186
};
187
}]
188
};
189
190
module.exports = function(config) {
191
config.set({
192
plugins: [customLauncher],
193
browsers: ['CustomBrowser']
194
});
195
};
196
```
197
198
### Reporters
199
200
Create custom reporters to format and output test results in different ways.
201
202
```javascript { .api }
203
/**
204
* Reporter interface
205
*/
206
interface Reporter {
207
/**
208
* Test run started
209
* @param browsers - Array of browser instances
210
*/
211
onRunStart(browsers: Browser[]): void;
212
213
/**
214
* Browser started
215
* @param browser - Browser instance
216
*/
217
onBrowserStart(browser: Browser): void;
218
219
/**
220
* Browser completed
221
* @param browser - Browser instance
222
* @param result - Test results
223
*/
224
onBrowserComplete(browser: Browser, result: BrowserResult): void;
225
226
/**
227
* Browser error
228
* @param browser - Browser instance
229
* @param error - Error object
230
*/
231
onBrowserError(browser: Browser, error: any): void;
232
233
/**
234
* Browser log message
235
* @param browser - Browser instance
236
* @param log - Log message
237
* @param type - Log type
238
*/
239
onBrowserLog(browser: Browser, log: string, type: string): void;
240
241
/**
242
* Individual test completed
243
* @param browser - Browser instance
244
* @param result - Test result
245
*/
246
onSpecComplete(browser: Browser, result: TestResult): void;
247
248
/**
249
* Test run completed
250
* @param browsers - Array of browser instances
251
* @param results - Overall results
252
*/
253
onRunComplete(browsers: Browser[], results: TestResults): void;
254
255
// Optional methods
256
write?(chunk: string): void;
257
writeCommonMsg?(msg: string): void;
258
onExit?(done: Function): void;
259
}
260
261
/**
262
* Base reporter with utility methods
263
*/
264
class BaseReporter implements Reporter {
265
write(chunk: string): void;
266
renderBrowser(browser: Browser): string;
267
specSuccess(browser: Browser, result: TestResult): void;
268
specFailure(browser: Browser, result: TestResult): void;
269
specSkipped(browser: Browser, result: TestResult): void;
270
}
271
```
272
273
**Custom Reporter Examples:**
274
275
```javascript
276
// Custom JSON reporter
277
const jsonReporter = function(baseReporterDecorator, config, logger, helper) {
278
baseReporterDecorator(this);
279
280
const log = logger.create('reporter.json');
281
const reporterConfig = config.jsonReporter || {};
282
const outputFile = reporterConfig.outputFile || 'test-results.json';
283
284
let results = {
285
browsers: {},
286
summary: {},
287
tests: []
288
};
289
290
this.onRunStart = function(browsers) {
291
results.timestamp = new Date().toISOString();
292
results.browsers = browsers.map(b => ({
293
id: b.id,
294
name: b.name,
295
fullName: b.fullName
296
}));
297
};
298
299
this.onSpecComplete = function(browser, result) {
300
results.tests.push({
301
browser: browser.name,
302
suite: result.suite,
303
description: result.description,
304
success: result.success,
305
time: result.time,
306
log: result.log
307
});
308
};
309
310
this.onRunComplete = function(browsers, summary) {
311
results.summary = summary;
312
313
helper.mkdirIfNotExists(path.dirname(outputFile), () => {
314
fs.writeFileSync(outputFile, JSON.stringify(results, null, 2));
315
log.info('Test results written to %s', outputFile);
316
});
317
};
318
};
319
320
jsonReporter.$inject = ['baseReporterDecorator', 'config', 'logger', 'helper'];
321
322
module.exports = {
323
'reporter:json': ['type', jsonReporter]
324
};
325
```
326
327
### Preprocessors
328
329
Transform files before they are served to browsers, enabling transpilation, bundling, and instrumentation.
330
331
```javascript { .api }
332
/**
333
* Preprocessor interface
334
*/
335
interface Preprocessor {
336
/**
337
* Process file content
338
* @param content - Original file content
339
* @param file - File object with metadata
340
* @param done - Completion callback with (error, content)
341
*/
342
(content: string, file: File, done: (error?: any, content?: string) => void): void;
343
}
344
345
/**
346
* File object passed to preprocessors
347
*/
348
interface File {
349
originalPath: string; // Original file path
350
path: string; // Processed file path
351
contentPath: string; // Path to content file
352
mtime: Date; // Modification time
353
isUrl: boolean; // Is external URL
354
isBinary: boolean; // Is binary file
355
}
356
```
357
358
**Custom Preprocessor Examples:**
359
360
```javascript
361
// Simple string replacement preprocessor
362
const stringReplace = function(args, config, logger) {
363
const log = logger.create('preprocessor.string-replace');
364
const options = args.options || {};
365
366
return function(content, file, done) {
367
log.debug('Processing "%s".', file.originalPath);
368
369
let result = content;
370
371
// Apply string replacements
372
if (options.replacements) {
373
options.replacements.forEach(replacement => {
374
result = result.replace(replacement.from, replacement.to);
375
});
376
}
377
378
done(null, result);
379
};
380
};
381
382
stringReplace.$inject = ['args', 'config', 'logger'];
383
384
// ES6 to ES5 transpiler preprocessor
385
const babelPreprocessor = function(args, config, logger) {
386
const babel = require('@babel/core');
387
const log = logger.create('preprocessor.babel');
388
389
return function(content, file, done) {
390
log.debug('Processing "%s".', file.originalPath);
391
392
try {
393
const result = babel.transform(content, {
394
filename: file.originalPath,
395
presets: ['@babel/preset-env'],
396
sourceMaps: 'inline'
397
});
398
399
done(null, result.code);
400
} catch (error) {
401
log.error('Error processing "%s": %s', file.originalPath, error.message);
402
done(error);
403
}
404
};
405
};
406
407
babelPreprocessor.$inject = ['args', 'config', 'logger'];
408
409
module.exports = {
410
'preprocessor:string-replace': ['factory', stringReplace],
411
'preprocessor:babel': ['factory', babelPreprocessor]
412
};
413
```
414
415
### Testing Frameworks
416
417
Integrate different testing libraries and frameworks with Karma.
418
419
```javascript { .api }
420
/**
421
* Framework interface
422
*/
423
interface Framework {
424
/**
425
* Initialize framework on client side
426
* @param files - File list to modify
427
*/
428
(files: any[]): void;
429
}
430
```
431
432
**Framework Plugin Examples:**
433
434
```javascript
435
// Custom testing framework
436
const customFramework = function(files) {
437
// Add framework files to the file list
438
files.unshift({
439
pattern: require.resolve('./custom-framework.js'),
440
included: true,
441
served: true,
442
watched: false
443
});
444
445
// Add adapter file
446
files.unshift({
447
pattern: require.resolve('./custom-adapter.js'),
448
included: true,
449
served: true,
450
watched: false
451
});
452
};
453
454
customFramework.$inject = ['config.files'];
455
456
module.exports = {
457
'framework:custom': ['factory', customFramework]
458
};
459
```
460
461
### Middleware
462
463
Add custom HTTP middleware to the Karma server for handling specific requests.
464
465
```javascript { .api }
466
/**
467
* Middleware interface
468
*/
469
interface Middleware {
470
/**
471
* Express-style middleware function
472
* @param req - HTTP request
473
* @param res - HTTP response
474
* @param next - Next middleware function
475
*/
476
(req: any, res: any, next: Function): void;
477
}
478
```
479
480
**Middleware Examples:**
481
482
```javascript
483
// API endpoint middleware
484
const apiMiddleware = function(config) {
485
return function(req, res, next) {
486
if (req.url.startsWith('/api/')) {
487
// Handle API requests
488
res.setHeader('Content-Type', 'application/json');
489
res.end(JSON.stringify({ message: 'API response' }));
490
} else {
491
next();
492
}
493
};
494
};
495
496
apiMiddleware.$inject = ['config'];
497
498
// CORS middleware
499
const corsMiddleware = function() {
500
return function(req, res, next) {
501
res.setHeader('Access-Control-Allow-Origin', '*');
502
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
503
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
504
505
if (req.method === 'OPTIONS') {
506
res.end();
507
} else {
508
next();
509
}
510
};
511
};
512
513
module.exports = {
514
'middleware:api': ['factory', apiMiddleware],
515
'middleware:cors': ['factory', corsMiddleware]
516
};
517
```
518
519
## Built-in Plugins
520
521
Karma includes several built-in plugins and supports many community plugins.
522
523
### Popular Browser Launchers
524
525
```javascript { .api }
526
// Built-in and popular browser launchers
527
'karma-chrome-launcher' // Google Chrome
528
'karma-firefox-launcher' // Mozilla Firefox
529
'karma-safari-launcher' // Safari
530
'karma-ie-launcher' // Internet Explorer
531
'karma-edge-launcher' // Microsoft Edge
532
'karma-phantomjs-launcher' // PhantomJS (deprecated)
533
'karma-browserstack-launcher' // BrowserStack
534
'karma-sauce-launcher' // Sauce Labs
535
```
536
537
### Popular Reporters
538
539
```javascript { .api }
540
// Built-in and popular reporters
541
'karma-progress-reporter' // Progress dots (built-in)
542
'karma-dots-reporter' // Simple dots (built-in)
543
'karma-junit-reporter' // JUnit XML
544
'karma-coverage-reporter' // Code coverage
545
'karma-spec-reporter' // Detailed spec output
546
'karma-json-reporter' // JSON output
547
'karma-teamcity-reporter' // TeamCity integration
548
```
549
550
### Popular Preprocessors
551
552
```javascript { .api }
553
// Popular preprocessors
554
'karma-babel-preprocessor' // Babel transpilation
555
'karma-webpack' // Webpack bundling
556
'karma-rollup-preprocessor' // Rollup bundling
557
'karma-browserify' // Browserify bundling
558
'karma-typescript' // TypeScript compilation
559
'karma-coverage' // Code coverage instrumentation
560
```
561
562
### Popular Frameworks
563
564
```javascript { .api }
565
// Popular testing frameworks
566
'karma-jasmine' // Jasmine
567
'karma-mocha' // Mocha
568
'karma-qunit' // QUnit
569
'karma-tap' // TAP
570
'karma-jest' // Jest compatibility
571
```