CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-web-component-tester

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.

Pending
Overview
Eval results
Files

plugin-system.mddocs/

Plugin System

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.

Capabilities

Plugin Base Class

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 options

Context and Hooks

Test 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 merged
  • prepare - Before test execution begins
  • cleanup - After all tests complete

Usage 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);

Built-in Plugins

Local Plugin

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"]
      }
    }
  }
}

Sauce Plugin

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"
        }
      ]
    }
  }
}

Custom Plugin Development

Plugin Module Structure

// 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}`);
    }
  });
};

Plugin Registration

// Custom plugin loading
const wct = require('web-component-tester');

await wct.test({
  suites: ['test/*.html'],
  plugins: {
    'custom-plugin': {
      customOption: 'value',
      enabled: true
    }
  }
});

Advanced Plugin Examples

// 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
      }
    });
  });
};

Plugin Configuration

Plugin Discovery

WCT discovers plugins in several ways:

  1. Built-in plugins (local, sauce)
  2. NPM modules with wct-plugin in package.json
  3. Local plugins via relative paths
  4. Plugins specified in configuration

Plugin Loading Order

  1. Built-in plugins are loaded first
  2. NPM plugin modules are discovered and loaded
  3. Plugins are initialized in dependency order
  4. Plugin hooks are executed during test lifecycle

Plugin CLI Integration

// 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
      }
    }
  }
}

Plugin Error Handling

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
    }
  });
};

Plugin Examples

Real-world Plugin Usage

{
  "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
    }
  }
}

Plugin Development Workflow

# 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"
    }
  }
}

Types

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

docs

browser-environment.md

build-tools.md

cli.md

configuration.md

index.md

plugin-system.md

test-runner.md

tile.json