Skip to content

feat: Plugin architecture for extensibility#3221

Open
ankushss wants to merge 3 commits intomobile-dev-inc:mainfrom
ankushss:feature/plugin-architecture
Open

feat: Plugin architecture for extensibility#3221
ankushss wants to merge 3 commits intomobile-dev-inc:mainfrom
ankushss:feature/plugin-architecture

Conversation

@ankushss
Copy link
Copy Markdown

@ankushss ankushss commented Apr 26, 2026

Summary

Adds a complete plugin system to Maestro that allows extending functionality through JAR-based plugins with lifecycle hooks.

Plugin Management Commands

  • maestro add-plugin <plugin.jar> - Install plugin to ~/.maestro/plugins/
  • maestro list-plugins - List installed plugins
  • maestro remove-plugin <name> - Remove plugin

Plugin Loading

  • --plugin flag supports plugin names or direct paths
  • Example: maestro test flow.yaml --plugin aria-plugin
  • Multiple plugins: --plugin plugin1 --plugin plugin2
  • ServiceLoader-based discovery from JAR files

Plugin Lifecycle Hooks

  • onInit() - Initialize plugin with context (Maestro instance, output dirs, config)
  • onFlowStart() - Called when flow starts
  • onCommandStart/Complete/Failed/Skipped() - Command lifecycle hooks
  • onFlowComplete() - Called when flow completes (with success/failure status)
  • onShutdown() - Cleanup resources

Architecture

  • PluginRegistry - Manages plugin lifecycle, broadcasts events, isolates errors
  • PluginLoader - Resolves and loads plugins from JAR files using ServiceLoader
  • PluginManager - Handles installation/removal in ~/.maestro/plugins/
  • MaestroContext - Provides plugins access to Maestro driver, output directories, flow config

Integration

  • Integrated with TestRunner, MaestroCommandRunner, TestSuiteInteractor
  • Works with single flow, multiple flows, continuous mode, and sharded execution
  • Plugin errors are isolated - failures don't crash tests or affect other plugins

Problem Statement

Currently, extending Maestro requires forking the repository. For example, we built an accessibility scanner that needed to:

  • Hook into command execution lifecycle
  • Add custom CLI flags
  • Integrate native SDKs (Swift/Kotlin) to extract view properties
  • Generate custom reports

This forking approach creates maintenance burden and prevents ecosystem growth.

Implementation Details

What's Included:

  • ✅ Core plugin interfaces (MaestroPlugin, MaestroContext)
  • ✅ Plugin registry with error isolation
  • ✅ Orchestra integration with lifecycle hooks
  • ✅ ServiceLoader-based plugin discovery
  • ✅ CLI commands (add-plugin, list-plugins, remove-plugin)
  • ✅ --plugin flag integration across all test modes
  • ✅ Plugin manager for ~/.maestro/plugins/ directory
  • ✅ Unit tests with error isolation verification

Files Changed:

  • 15 files modified
  • 599 insertions, 18 deletions
  • New commands: AddPluginCommand, ListPluginsCommand, RemovePluginCommand
  • Core plugin system: PluginLoader, PluginManager, PluginRegistry
  • Integration across: TestRunner, MaestroCommandRunner, TestSuiteInteractor, Orchestra

Use Cases Enabled

  • Accessibility scanning: WCAG compliance checks during test runs (our use case)
  • Performance monitoring: Track command execution timing, memory usage
  • Visual regression: Capture and compare screenshots automatically
  • Custom analytics: Send test metrics to external dashboards
  • Custom reporters: Generate specialized test reports

Example Plugin

class AccessibilityPlugin : MaestroPlugin {
    override val id = "aria-scanner"
    override val name = "ARIA Accessibility Scanner"
    
    private lateinit var context: MaestroContext
    
    override fun onInit(context: MaestroContext) {
        this.context = context
        // Initialize ARIA SDK
    }
    
    override fun onCommandComplete(index: Int, command: MaestroCommand) {
        // Scan after UI commands
        val hierarchy = context.maestro.viewHierarchy()
        val screenshot = context.maestro.takeScreenshot()
        // Run WCAG checks and record violations
    }
    
    override fun onFlowComplete(config: MaestroConfig?, success: Boolean) {
        // Generate HTML report to context.outputDirectory
    }
}

Package as JAR with META-INF/services/maestro.orchestra.plugin.MaestroPlugin file listing implementation.

Usage

# Install plugin
maestro add-plugin aria-scanner-1.0.0.jar

# List installed plugins
maestro list-plugins

# Run tests with plugin
maestro test flow.yaml --plugin aria-scanner

# Use multiple plugins
maestro test flow.yaml --plugin aria-scanner --plugin perf-monitor

# Use plugin by path (for development)
maestro test flow.yaml --plugin /path/to/plugin.jar

# Remove plugin
maestro remove-plugin aria-scanner

Testing

  • ✅ Unit tests for PluginRegistry (lifecycle, error isolation)
  • ✅ Tests verify plugin errors don't crash tests
  • ✅ Tests verify multiple plugin coordination
  • ✅ Manual testing with example plugin stub

Questions for Maintainers

  1. Distribution: Should we support Phase 2/3 features like URL-based installation or a plugin registry?
  2. Native SDK integration: Plugins may need native code (Swift/Kotlin). Current approach: plugin JARs can include SDK communication logic, native SDK installed separately
  3. API stability: Should we mark the plugin API as experimental initially?
  4. Documentation: Where should plugin development guide live?

Co-Authored-By: Claude Sonnet 4.5 noreply@anthropic.com

Add core plugin system interfaces and registry:
- MaestroPlugin: Base interface for lifecycle hooks
- MaestroContext: Provides plugins access to Maestro, config, output
- PluginRegistry: Manages plugins and broadcasts lifecycle events

This enables extensions like accessibility scanners (ARIA) and
performance monitors without forking the repository.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@ankushss ankushss marked this pull request as ready for review April 27, 2026 10:33
Add plugin system to extend Maestro functionality:

Plugin Management:
- maestro add-plugin <jar> - Install plugin to ~/.maestro/plugins/
- maestro list-plugins - List installed plugins
- maestro remove-plugin <name> - Remove plugin

Plugin Loading:
- --plugin flag supports names (aria-plugin) or paths (/path/to/plugin.jar)
- Multiple plugins: --plugin plugin1 --plugin plugin2
- ServiceLoader-based discovery from JAR files

Plugin Lifecycle:
- onInit() - Initialize plugin with context
- onFlowStart() - Called at flow start
- onCommandStart/Complete/Failed/Skipped() - Command hooks
- onFlowComplete() - Called at flow end
- onShutdown() - Cleanup resources

Architecture:
- PluginRegistry manages lifecycle and isolates errors
- PluginLoader resolves and loads plugins from JARs
- PluginManager handles installation in ~/.maestro/plugins/
- MaestroContext provides access to Maestro, output dirs, config

Integration:
- Works with single flow, multiple flows, continuous, and sharded execution
- Plugin errors isolated - failures don't crash tests
- Passes through TestRunner, MaestroCommandRunner, TestSuiteInteractor

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@ankushss ankushss changed the title feat: Plugin architecture for extensibility feat: Implement plugin architecture with npm-like installation May 3, 2026
@ankushss ankushss changed the title feat: Implement plugin architecture with npm-like installation feat: Plugin architecture for extensibility May 3, 2026
The pluginRegistry variable was defined in call() but needed to be passed
through handleSessions() and runShardSuite() methods to be accessible in
the session lambda where it's used.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant