A comprehensive testing framework specifically designed for web components with browser-based testing environment, Mocha/Chai/Sinon integration, and support for both local and remote testing via Sauce Labs.
—
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.
Base plugin class for creating custom WCT plugins.
/**
* Base plugin class for extending WCT functionality
*/
interface Plugin {
/** Plugin name */
name: string;
/** CLI configuration options */
cliConfig: any;
/** Execute plugin during test run */
execute(context: Context): Promise<void>;
}
/**
* Get plugin instance by name
* @param name - Plugin name (e.g., 'local', 'sauce', 'custom-plugin')
* @returns Promise resolving to plugin instance
*/
function get(name: string): Promise<Plugin>;Usage Examples:
// Using built-in plugins
const wct = require('web-component-tester');
const localPlugin = await wct.Plugin.get('local');
const saucePlugin = await wct.Plugin.get('sauce');
// Plugin has CLI configuration
console.log(localPlugin.cliConfig); // Available command line optionsTest execution context with plugin hook system for lifecycle integration.
/**
* Test execution context with event system and plugin hooks
*/
interface Context extends EventEmitter {
/** Current configuration options */
options: Config;
/** Emit plugin hook for all loaded plugins */
emitHook(name: string): Promise<void>;
/** Get all loaded plugins */
plugins(): Promise<Plugin[]>;
}Available Hooks:
configure - After configuration is loaded and mergedprepare - Before test execution beginscleanup - After all tests completeUsage Examples:
const wct = require('web-component-tester');
const context = new wct.Context({
suites: ['test/*.html'],
verbose: true
});
// Hook into test lifecycle
context.on('run-start', (options) => {
console.log('Starting tests with', options.activeBrowsers.length, 'browsers');
});
// Custom hook registration
context.options.registerHooks = (context) => {
context.hookRegister('prepare', async () => {
console.log('Custom prepare hook');
// Custom preparation logic
});
context.hookRegister('cleanup', async () => {
console.log('Custom cleanup hook');
// Custom cleanup logic
});
};
await wct.test(context);Runs tests on locally installed browsers.
interface LocalPluginOptions {
/** Disable local testing */
disabled?: boolean;
/** Browsers to run locally */
browsers?: string[];
/** Browser-specific command line options */
browserOptions?: {[browserName: string]: string[]};
/** Skip automatic Selenium installation */
skipSeleniumInstall?: boolean;
}Configuration Examples:
{
"plugins": {
"local": {
"browsers": ["chrome", "firefox", "safari"],
"browserOptions": {
"chrome": ["--headless", "--disable-gpu"],
"firefox": ["-headless"]
}
}
}
}Runs tests on Sauce Labs remote browsers.
interface SaucePluginOptions {
/** Disable Sauce Labs testing */
disabled?: boolean;
/** Sauce Labs username */
username?: string;
/** Sauce Labs access key */
accessKey?: string;
/** Existing tunnel ID to use */
tunnelId?: string;
/** Remote browser configurations */
browsers?: SauceBrowserDef[];
/** Build identifier */
build?: string;
/** Test tags */
tags?: string[];
}
interface SauceBrowserDef {
browserName: string;
platform?: string;
version?: string;
deviceName?: string;
[capability: string]: any;
}Configuration Examples:
{
"plugins": {
"sauce": {
"username": "${SAUCE_USERNAME}",
"accessKey": "${SAUCE_ACCESS_KEY}",
"build": "${BUILD_NUMBER}",
"tags": ["web-components", "polymer"],
"browsers": [
{
"browserName": "chrome",
"platform": "Windows 10",
"version": "latest"
},
{
"browserName": "safari",
"platform": "macOS 10.15",
"version": "latest"
}
]
}
}
}// package.json
{
"name": "wct-custom-plugin",
"wct-plugin": {
"cli-options": {
"customOption": {
"help": "Description of custom option"
}
}
}
}
// plugin.js (main module)
module.exports = function(context, pluginOptions, plugin) {
// Plugin initialization logic
// Register for lifecycle hooks
context.hookRegister('configure', async () => {
console.log('Custom plugin configure hook');
// Modify context.options if needed
});
context.hookRegister('prepare', async () => {
console.log('Custom plugin prepare hook');
// Start any long-running processes
});
context.hookRegister('cleanup', async () => {
console.log('Custom plugin cleanup hook');
// Cleanup resources
});
// Handle test events
context.on('browser-start', (browser, metadata, stats) => {
console.log(`Custom plugin: browser ${browser.browserName} started`);
});
context.on('test-end', (browser, test, stats) => {
if (test.state === 'failed') {
console.log(`Custom plugin: test failed - ${test.title}`);
}
});
};// Custom plugin loading
const wct = require('web-component-tester');
await wct.test({
suites: ['test/*.html'],
plugins: {
'custom-plugin': {
customOption: 'value',
enabled: true
}
}
});// Screenshot plugin
module.exports = function(context, pluginOptions, plugin) {
const screenshots = [];
context.on('test-end', async (browser, test, stats) => {
if (test.state === 'failed' && pluginOptions.screenshotOnFailure) {
const screenshot = await browser.takeScreenshot();
screenshots.push({
test: test.title,
browser: browser.browserName,
screenshot: screenshot
});
}
});
context.hookRegister('cleanup', async () => {
if (screenshots.length > 0) {
const fs = require('fs');
fs.writeFileSync('failed-test-screenshots.json',
JSON.stringify(screenshots, null, 2));
}
});
};
// Performance monitoring plugin
module.exports = function(context, pluginOptions, plugin) {
const performanceData = [];
context.on('test-end', (browser, test, stats) => {
if (test.duration > pluginOptions.slowThreshold) {
performanceData.push({
test: test.title,
browser: browser.browserName,
duration: test.duration,
timestamp: new Date().toISOString()
});
}
});
context.hookRegister('cleanup', async () => {
if (performanceData.length > 0) {
console.warn('Slow tests detected:');
performanceData.forEach(data => {
console.warn(` ${data.test} (${data.browser}): ${data.duration}ms`);
});
}
});
};
// Custom browser plugin
module.exports = function(context, pluginOptions, plugin) {
context.hookRegister('prepare', async () => {
// Add custom browser to active browsers
context.options.activeBrowsers.push({
browserName: 'custom-browser',
platform: 'Custom Platform',
// Custom browser launch logic
startBrowser: async () => {
// Implementation for starting custom browser
}
});
});
};WCT discovers plugins in several ways:
local, sauce)wct-plugin in package.json// package.json
{
"wct-plugin": {
"cli-options": {
"customBrowser": {
"help": "Browser executable path",
"metavar": "<path>"
},
"customTimeout": {
"help": "Custom timeout in seconds",
"type": "number",
"default": 30
},
"customFlag": {
"help": "Enable custom feature",
"flag": true
}
}
}
}module.exports = function(context, pluginOptions, plugin) {
context.hookRegister('prepare', async () => {
try {
// Plugin preparation logic
await initializeCustomService();
} catch (error) {
context.emit('log:error', 'Custom plugin failed to initialize:', error);
throw error; // This will fail the test run
}
});
// Graceful error handling
context.on('browser-end', (browser, error, stats) => {
if (error && pluginOptions.continueOnError) {
context.emit('log:warn', 'Ignoring browser error due to continueOnError option');
// Don't re-throw the error
}
});
};{
"plugins": {
"local": {
"browsers": ["chrome", "firefox"]
},
"sauce": {
"disabled": true
},
"wct-coverage": {
"threshold": 80,
"reporters": ["lcov", "json"]
},
"wct-screenshot": {
"screenshotOnFailure": true,
"outputDir": "screenshots"
},
"wct-performance": {
"slowThreshold": 5000,
"generateReport": true
}
}
}# Create plugin project
mkdir wct-my-plugin
cd wct-my-plugin
npm init
# Add plugin metadata to package.json
# Implement plugin in index.js
# Test with local WCT project
# Link for development
npm link
cd ../my-wct-project
npm link wct-my-plugin
# Use in wct.conf.json
{
"plugins": {
"my-plugin": {
"option": "value"
}
}
}interface PluginHook {
(context: Context): Promise<void>;
}
interface PluginMetadata {
'cli-options'?: {
[optionName: string]: {
help: string;
type?: 'string' | 'number' | 'boolean';
flag?: boolean;
default?: any;
metavar?: string;
};
};
}
interface PluginModule {
(context: Context, pluginOptions: any, plugin: Plugin): void;
}Install with Tessl CLI
npx tessl i tessl/npm-web-component-tester