CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-playwright-extra

A modular plugin framework for Playwright to enable enhanced browser automation through plugins.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

plugin-development.mddocs/

Plugin Development

Interfaces and types for creating compatible plugins that work with both Playwright Extra and puppeteer-extra. The plugin system provides comprehensive lifecycle hooks and compatibility features for extending browser automation capabilities.

Capabilities

Plugin Interface

The core interface that all plugins must implement to be compatible with Playwright Extra.

/**
 * PuppeteerExtraPlugin interface, strongly typed for internal use
 */
interface PuppeteerExtraPlugin extends Partial<PluginLifecycleMethods> {
  /** Plugin identification flag - must be true */
  _isPuppeteerExtraPlugin: boolean;
  /** Unique plugin name */
  name: string;
  /** Disable the puppeteer compatibility shim for this plugin */
  noPuppeteerShim?: boolean;
  /** Plugin requirements and capabilities */
  requirements?: PluginRequirements;
  /** Plugin dependencies that should be auto-loaded */
  dependencies?: PluginDependencies; 
  /** Data that can be shared with other plugins */
  data?: PluginData[];
  /** Access data from other plugins (if dataFromPlugins requirement is set) */
  getDataFromPlugins?(name?: string): void;
  /** Method for registering child class members (PuppeteerExtraPlugin compatibility) */
  _registerChildClassMembers?(prototype: any): void;
  /** List of child class members (PuppeteerExtraPlugin compatibility) */
  _childClassMembers?: string[];
  /** Sub-plugins that should be registered automatically */
  plugins?: CompatiblePlugin[];
}

Plugin Lifecycle Methods

Abstract class defining all available lifecycle hooks that plugins can implement.

/**
 * Strongly typed plugin lifecycle events for internal use
 */
abstract class PluginLifecycleMethods {
  /** Called when plugin is registered with the framework */
  async onPluginRegistered(env?: PluginEnv): Promise<void> {}
  
  /** Called before browser launch, can modify launch options */
  async beforeLaunch(options: LaunchOptions): Promise<LaunchOptions | void> {}
  
  /** Called after browser launch, receives browser or context instance */
  async afterLaunch(browserOrContext?: Browser | BrowserContext): Promise<void> {}
  
  /** Called before connecting to existing browser, can modify connect options */
  async beforeConnect(options: ConnectOptions): Promise<ConnectOptions | void> {}
  
  /** Called after connecting to existing browser */
  async afterConnect(browser: Browser): Promise<void> {}
  
  /** Called when browser instance is available */
  async onBrowser(browser: Browser): Promise<void> {}
  
  /** Called when a new page is created */
  async onPageCreated(page: Page): Promise<void> {}
  
  /** Called when a page is closed */
  async onPageClose(page: Page): Promise<void> {}
  
  /** Called when browser is disconnected */
  async onDisconnected(browser?: Browser): Promise<void> {}
  
  /** Called before creating browser context, can modify context options */
  async beforeContext(
    options?: BrowserContextOptions,
    browser?: Browser
  ): Promise<BrowserContextOptions | void> {}
  
  /** Called when a new browser context is created */
  async onContextCreated(
    context?: BrowserContext,
    options?: BrowserContextOptions
  ): Promise<void> {}
}

Basic Plugin Example

Simple plugin implementation demonstrating the core structure:

import { PuppeteerExtraPlugin } from 'puppeteer-extra-plugin';

class MyPlugin extends PuppeteerExtraPlugin {
  constructor(opts = {}) {
    super(opts);
  }

  get name() {
    return 'my-plugin';
  }

  async onPluginRegistered() {
    console.log('Plugin registered with Playwright Extra');
  }

  async beforeLaunch(options) {
    console.log('Before launch:', options);
    // Modify launch options
    options.args = options.args || [];
    options.args.push('--custom-flag');
    return options;
  }

  async onPageCreated(page) {
    console.log('New page created');
    // Add custom functionality to pages
    await page.addInitScript(() => {
      console.log('Custom script injected');
    });
  }
}

module.exports = (pluginConfig) => new MyPlugin(pluginConfig);

Plugin Requirements

Plugins can declare requirements that affect their behavior and execution order:

type PluginRequirements = Set<
  | 'launch'           // Plugin requires browser launch
  | 'headful'          // Plugin requires headful (non-headless) mode
  | 'dataFromPlugins'  // Plugin needs access to data from other plugins
  | 'runLast'          // Plugin should run after all other plugins
>;

Usage Examples:

class DataCollectorPlugin extends PuppeteerExtraPlugin {
  constructor(opts = {}) {
    super(opts);
    this.requirements = new Set(['dataFromPlugins', 'runLast']);
  }

  get name() {
    return 'data-collector';
  }

  async onPluginRegistered() {
    // This plugin will have access to getDataFromPlugins method
    const allData = this.getDataFromPlugins();
    console.log('Data from other plugins:', allData);
  }
}

Plugin Dependencies

Plugins can declare dependencies that will be automatically loaded:

type PluginDependencies = Set<string> | Map<string, any> | string[];

Usage Examples:

class CompositePlugin extends PuppeteerExtraPlugin {
  constructor(opts = {}) {
    super(opts);
    // Declare dependencies as Set
    this.dependencies = new Set([
      'stealth/evasions/webgl.vendor',
      'stealth/evasions/user-agent-override'
    ]);
  }

  get name() {
    return 'composite-plugin';
  }
}

class ConfigurablePlugin extends PuppeteerExtraPlugin {
  constructor(opts = {}) {
    super(opts);
    // Declare dependencies with options as Map
    this.dependencies = new Map([
      ['stealth/evasions/webgl.vendor', { 
        vendor: 'Custom',
        renderer: 'Custom'
      }],
      ['stealth/evasions/user-agent-override', {
        userAgent: 'Custom User Agent'
      }]
    ]);
  }

  get name() {
    return 'configurable-plugin';
  }
}

Plugin Data Sharing

Plugins can expose data that other plugins can access:

interface PluginData {
  name: string | { [key: string]: any };
  value: { [key: string]: any };
}

Usage Examples:

class DataProviderPlugin extends PuppeteerExtraPlugin {
  constructor(opts = {}) {
    super(opts);
    this.data = [
      {
        name: 'config',
        value: { apiKey: 'secret-key', endpoint: 'https://api.example.com' }
      },
      {
        name: 'cache',
        value: { results: [], timestamps: [] }
      }
    ];
  }

  get name() {
    return 'data-provider';
  }
}

class DataConsumerPlugin extends PuppeteerExtraPlugin {
  constructor(opts = {}) {
    super(opts);
    this.requirements = new Set(['dataFromPlugins']);
  }

  get name() {
    return 'data-consumer';
  }

  async onPluginRegistered() {
    // Access data from other plugins
    const configData = this.getDataFromPlugins('config');
    const cacheData = this.getDataFromPlugins('cache');
    
    console.log('Config:', configData);
    console.log('Cache:', cacheData);
  }
}

Advanced Plugin Pattern

Complex plugin with multiple lifecycle hooks and error handling:

class AdvancedPlugin extends PuppeteerExtraPlugin {
  constructor(opts = {}) {
    super(opts);
    this.options = { timeout: 30000, retry: 3, ...opts };
  }

  get name() {
    return 'advanced-plugin';
  }

  async beforeLaunch(options) {
    try {
      // Pre-launch setup
      console.log('Configuring browser launch');
      options.args = options.args || [];
      options.args.push(`--timeout=${this.options.timeout}`);
      return options;
    } catch (error) {
      console.error('Error in beforeLaunch:', error);
      throw error;
    }
  }

  async onBrowser(browser) {
    // Set up browser-level monitoring
    browser.on('disconnected', () => {
      console.log('Browser disconnected');
    });
  }

  async beforeContext(options, browser) {
    // Configure context options
    options = options || {};
    options.userAgent = 'Custom User Agent';
    return options;
  }

  async onPageCreated(page) {
    // Set up page-level functionality
    await page.addInitScript(() => {
      // Inject custom functionality
      window.customAPI = {
        version: '1.0.0',
        initialized: true
      };
    });

    // Add error handling
    page.on('pageerror', error => {
      console.error('Page error:', error);
    });
  }
}

module.exports = (pluginConfig) => new AdvancedPlugin(pluginConfig);

Core Types

type PluginMethodName = keyof PluginLifecycleMethods;

interface PluginEnv {
  framework: 'playwright';
}

type CompatiblePlugin = 
  | CompatiblePuppeteerPlugin 
  | CompatiblePlaywrightPlugin 
  | CompatibleExtraPlugin;

interface CompatiblePuppeteerPlugin {
  _isPuppeteerExtraPlugin: boolean;
  name?: string;
}

interface CompatiblePlaywrightPlugin {
  _isPlaywrightExtraPlugin: boolean;
  name?: string;
}

interface CompatibleExtraPlugin {
  _isExtraPlugin: boolean;
  name?: string;
}

Install with Tessl CLI

npx tessl i tessl/npm-playwright-extra

docs

browser-launchers.md

custom-integration.md

index.md

plugin-development.md

plugin-management.md

puppeteer-compatibility.md

tile.json