0
# Plugin System
1
2
Web Component Tester features an extensible plugin architecture for adding custom functionality, browser support, and testing environments. The plugin system provides hooks into the test lifecycle and configuration system.
3
4
## Capabilities
5
6
### Plugin Base Class
7
8
Base plugin class for creating custom WCT plugins.
9
10
```typescript { .api }
11
/**
12
* Base plugin class for extending WCT functionality
13
*/
14
interface Plugin {
15
/** Plugin name */
16
name: string;
17
/** CLI configuration options */
18
cliConfig: any;
19
/** Execute plugin during test run */
20
execute(context: Context): Promise<void>;
21
}
22
23
/**
24
* Get plugin instance by name
25
* @param name - Plugin name (e.g., 'local', 'sauce', 'custom-plugin')
26
* @returns Promise resolving to plugin instance
27
*/
28
function get(name: string): Promise<Plugin>;
29
```
30
31
**Usage Examples:**
32
33
```javascript
34
// Using built-in plugins
35
const wct = require('web-component-tester');
36
37
const localPlugin = await wct.Plugin.get('local');
38
const saucePlugin = await wct.Plugin.get('sauce');
39
40
// Plugin has CLI configuration
41
console.log(localPlugin.cliConfig); // Available command line options
42
```
43
44
### Context and Hooks
45
46
Test execution context with plugin hook system for lifecycle integration.
47
48
```typescript { .api }
49
/**
50
* Test execution context with event system and plugin hooks
51
*/
52
interface Context extends EventEmitter {
53
/** Current configuration options */
54
options: Config;
55
/** Emit plugin hook for all loaded plugins */
56
emitHook(name: string): Promise<void>;
57
/** Get all loaded plugins */
58
plugins(): Promise<Plugin[]>;
59
}
60
```
61
62
**Available Hooks:**
63
- `configure` - After configuration is loaded and merged
64
- `prepare` - Before test execution begins
65
- `cleanup` - After all tests complete
66
67
**Usage Examples:**
68
69
```javascript
70
const wct = require('web-component-tester');
71
72
const context = new wct.Context({
73
suites: ['test/*.html'],
74
verbose: true
75
});
76
77
// Hook into test lifecycle
78
context.on('run-start', (options) => {
79
console.log('Starting tests with', options.activeBrowsers.length, 'browsers');
80
});
81
82
// Custom hook registration
83
context.options.registerHooks = (context) => {
84
context.hookRegister('prepare', async () => {
85
console.log('Custom prepare hook');
86
// Custom preparation logic
87
});
88
89
context.hookRegister('cleanup', async () => {
90
console.log('Custom cleanup hook');
91
// Custom cleanup logic
92
});
93
};
94
95
await wct.test(context);
96
```
97
98
## Built-in Plugins
99
100
### Local Plugin
101
102
Runs tests on locally installed browsers.
103
104
```typescript { .api }
105
interface LocalPluginOptions {
106
/** Disable local testing */
107
disabled?: boolean;
108
/** Browsers to run locally */
109
browsers?: string[];
110
/** Browser-specific command line options */
111
browserOptions?: {[browserName: string]: string[]};
112
/** Skip automatic Selenium installation */
113
skipSeleniumInstall?: boolean;
114
}
115
```
116
117
**Configuration Examples:**
118
119
```json
120
{
121
"plugins": {
122
"local": {
123
"browsers": ["chrome", "firefox", "safari"],
124
"browserOptions": {
125
"chrome": ["--headless", "--disable-gpu"],
126
"firefox": ["-headless"]
127
}
128
}
129
}
130
}
131
```
132
133
### Sauce Plugin
134
135
Runs tests on Sauce Labs remote browsers.
136
137
```typescript { .api }
138
interface SaucePluginOptions {
139
/** Disable Sauce Labs testing */
140
disabled?: boolean;
141
/** Sauce Labs username */
142
username?: string;
143
/** Sauce Labs access key */
144
accessKey?: string;
145
/** Existing tunnel ID to use */
146
tunnelId?: string;
147
/** Remote browser configurations */
148
browsers?: SauceBrowserDef[];
149
/** Build identifier */
150
build?: string;
151
/** Test tags */
152
tags?: string[];
153
}
154
155
interface SauceBrowserDef {
156
browserName: string;
157
platform?: string;
158
version?: string;
159
deviceName?: string;
160
[capability: string]: any;
161
}
162
```
163
164
**Configuration Examples:**
165
166
```json
167
{
168
"plugins": {
169
"sauce": {
170
"username": "${SAUCE_USERNAME}",
171
"accessKey": "${SAUCE_ACCESS_KEY}",
172
"build": "${BUILD_NUMBER}",
173
"tags": ["web-components", "polymer"],
174
"browsers": [
175
{
176
"browserName": "chrome",
177
"platform": "Windows 10",
178
"version": "latest"
179
},
180
{
181
"browserName": "safari",
182
"platform": "macOS 10.15",
183
"version": "latest"
184
}
185
]
186
}
187
}
188
}
189
```
190
191
## Custom Plugin Development
192
193
### Plugin Module Structure
194
195
```javascript
196
// package.json
197
{
198
"name": "wct-custom-plugin",
199
"wct-plugin": {
200
"cli-options": {
201
"customOption": {
202
"help": "Description of custom option"
203
}
204
}
205
}
206
}
207
208
// plugin.js (main module)
209
module.exports = function(context, pluginOptions, plugin) {
210
// Plugin initialization logic
211
212
// Register for lifecycle hooks
213
context.hookRegister('configure', async () => {
214
console.log('Custom plugin configure hook');
215
// Modify context.options if needed
216
});
217
218
context.hookRegister('prepare', async () => {
219
console.log('Custom plugin prepare hook');
220
// Start any long-running processes
221
});
222
223
context.hookRegister('cleanup', async () => {
224
console.log('Custom plugin cleanup hook');
225
// Cleanup resources
226
});
227
228
// Handle test events
229
context.on('browser-start', (browser, metadata, stats) => {
230
console.log(`Custom plugin: browser ${browser.browserName} started`);
231
});
232
233
context.on('test-end', (browser, test, stats) => {
234
if (test.state === 'failed') {
235
console.log(`Custom plugin: test failed - ${test.title}`);
236
}
237
});
238
};
239
```
240
241
### Plugin Registration
242
243
```javascript
244
// Custom plugin loading
245
const wct = require('web-component-tester');
246
247
await wct.test({
248
suites: ['test/*.html'],
249
plugins: {
250
'custom-plugin': {
251
customOption: 'value',
252
enabled: true
253
}
254
}
255
});
256
```
257
258
### Advanced Plugin Examples
259
260
```javascript
261
// Screenshot plugin
262
module.exports = function(context, pluginOptions, plugin) {
263
const screenshots = [];
264
265
context.on('test-end', async (browser, test, stats) => {
266
if (test.state === 'failed' && pluginOptions.screenshotOnFailure) {
267
const screenshot = await browser.takeScreenshot();
268
screenshots.push({
269
test: test.title,
270
browser: browser.browserName,
271
screenshot: screenshot
272
});
273
}
274
});
275
276
context.hookRegister('cleanup', async () => {
277
if (screenshots.length > 0) {
278
const fs = require('fs');
279
fs.writeFileSync('failed-test-screenshots.json',
280
JSON.stringify(screenshots, null, 2));
281
}
282
});
283
};
284
285
// Performance monitoring plugin
286
module.exports = function(context, pluginOptions, plugin) {
287
const performanceData = [];
288
289
context.on('test-end', (browser, test, stats) => {
290
if (test.duration > pluginOptions.slowThreshold) {
291
performanceData.push({
292
test: test.title,
293
browser: browser.browserName,
294
duration: test.duration,
295
timestamp: new Date().toISOString()
296
});
297
}
298
});
299
300
context.hookRegister('cleanup', async () => {
301
if (performanceData.length > 0) {
302
console.warn('Slow tests detected:');
303
performanceData.forEach(data => {
304
console.warn(` ${data.test} (${data.browser}): ${data.duration}ms`);
305
});
306
}
307
});
308
};
309
310
// Custom browser plugin
311
module.exports = function(context, pluginOptions, plugin) {
312
context.hookRegister('prepare', async () => {
313
// Add custom browser to active browsers
314
context.options.activeBrowsers.push({
315
browserName: 'custom-browser',
316
platform: 'Custom Platform',
317
// Custom browser launch logic
318
startBrowser: async () => {
319
// Implementation for starting custom browser
320
}
321
});
322
});
323
};
324
```
325
326
## Plugin Configuration
327
328
### Plugin Discovery
329
330
WCT discovers plugins in several ways:
331
1. Built-in plugins (`local`, `sauce`)
332
2. NPM modules with `wct-plugin` in package.json
333
3. Local plugins via relative paths
334
4. Plugins specified in configuration
335
336
### Plugin Loading Order
337
338
1. Built-in plugins are loaded first
339
2. NPM plugin modules are discovered and loaded
340
3. Plugins are initialized in dependency order
341
4. Plugin hooks are executed during test lifecycle
342
343
### Plugin CLI Integration
344
345
```javascript
346
// package.json
347
{
348
"wct-plugin": {
349
"cli-options": {
350
"customBrowser": {
351
"help": "Browser executable path",
352
"metavar": "<path>"
353
},
354
"customTimeout": {
355
"help": "Custom timeout in seconds",
356
"type": "number",
357
"default": 30
358
},
359
"customFlag": {
360
"help": "Enable custom feature",
361
"flag": true
362
}
363
}
364
}
365
}
366
```
367
368
### Plugin Error Handling
369
370
```javascript
371
module.exports = function(context, pluginOptions, plugin) {
372
context.hookRegister('prepare', async () => {
373
try {
374
// Plugin preparation logic
375
await initializeCustomService();
376
} catch (error) {
377
context.emit('log:error', 'Custom plugin failed to initialize:', error);
378
throw error; // This will fail the test run
379
}
380
});
381
382
// Graceful error handling
383
context.on('browser-end', (browser, error, stats) => {
384
if (error && pluginOptions.continueOnError) {
385
context.emit('log:warn', 'Ignoring browser error due to continueOnError option');
386
// Don't re-throw the error
387
}
388
});
389
};
390
```
391
392
## Plugin Examples
393
394
### Real-world Plugin Usage
395
396
```json
397
{
398
"plugins": {
399
"local": {
400
"browsers": ["chrome", "firefox"]
401
},
402
"sauce": {
403
"disabled": true
404
},
405
"wct-coverage": {
406
"threshold": 80,
407
"reporters": ["lcov", "json"]
408
},
409
"wct-screenshot": {
410
"screenshotOnFailure": true,
411
"outputDir": "screenshots"
412
},
413
"wct-performance": {
414
"slowThreshold": 5000,
415
"generateReport": true
416
}
417
}
418
}
419
```
420
421
### Plugin Development Workflow
422
423
```bash
424
# Create plugin project
425
mkdir wct-my-plugin
426
cd wct-my-plugin
427
npm init
428
429
# Add plugin metadata to package.json
430
# Implement plugin in index.js
431
# Test with local WCT project
432
433
# Link for development
434
npm link
435
cd ../my-wct-project
436
npm link wct-my-plugin
437
438
# Use in wct.conf.json
439
{
440
"plugins": {
441
"my-plugin": {
442
"option": "value"
443
}
444
}
445
}
446
```
447
448
## Types
449
450
```typescript { .api }
451
interface PluginHook {
452
(context: Context): Promise<void>;
453
}
454
455
interface PluginMetadata {
456
'cli-options'?: {
457
[optionName: string]: {
458
help: string;
459
type?: 'string' | 'number' | 'boolean';
460
flag?: boolean;
461
default?: any;
462
metavar?: string;
463
};
464
};
465
}
466
467
interface PluginModule {
468
(context: Context, pluginOptions: any, plugin: Plugin): void;
469
}
470
```