Files
Profa 7a74717a81 feat(v3): integrate @claude-flow/browser into AQE v3
Multi-agent implementation of browser automation integration:

Adapters:
- BrowserResultAdapter: Transform browser results to QE format
- TrajectoryAdapter: Capture learning patterns from browser sessions

Services:
- BrowserSecurityScanner: OWASP-based security scanning with browser
- BrowserSwarmCoordinator: Multi-viewport parallel testing

Workflows:
- 9 browser workflow templates (login, OAuth, accessibility, etc.)
- WorkflowLoader for dynamic template loading

Skills:
- security-visual-testing: Combined security + visual regression skill

Tests:
- Unit tests for adapters and services
- Integration tests for parallel viewports and trajectory learning

Documentation:
- API docs for all new components
- Integration guide for claude-flow-browser

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 18:08:06 +00:00

20 KiB
Raw Permalink Blame History

Browser Swarm Coordination Guide

Package: @agentic-qe/v3/domains/visual-accessibility Source: /v3/src/domains/visual-accessibility/services/browser-swarm-coordinator.ts Purpose: Multi-session browser coordination for parallel viewport testing

Overview

The BrowserSwarmCoordinator orchestrates multiple browser sessions running in parallel across different viewport configurations. This enables 3x+ speedup for visual regression testing, accessibility audits, and responsive design validation.


Architecture

┌─────────────────────────────────────────────────────────────┐
│              BrowserSwarmCoordinator                        │
│  ┌────────────────────────────────────────────────────┐    │
│  │           Session Pool Manager                      │    │
│  │  - Max concurrent sessions: 5 (configurable)       │    │
│  │  - Session lifecycle management                     │    │
│  │  - Resource cleanup                                 │    │
│  └────────────────────────────────────────────────────┘    │
│                           │                                 │
│         ┌─────────────────┼─────────────────┐              │
│         │                 │                 │              │
│  ┌──────▼──────┐  ┌──────▼──────┐  ┌──────▼──────┐       │
│  │  Session 1  │  │  Session 2  │  │  Session 3  │       │
│  │  320×568    │  │  768×1024   │  │  1920×1080  │       │
│  │  (Mobile)   │  │  (Tablet)   │  │  (Desktop)  │       │
│  └─────────────┘  └─────────────┘  └─────────────┘       │
│         │                 │                 │              │
└─────────┼─────────────────┼─────────────────┼──────────────┘
          │                 │                 │
          ▼                 ▼                 ▼
    ┌─────────┐       ┌─────────┐       ┌─────────┐
    │ Browser │       │ Browser │       │ Browser │
    │ Process │       │ Process │       │ Process │
    └─────────┘       └─────────┘       └─────────┘

Standard Viewports

Desktop Viewports

const DESKTOP_VIEWPORTS: Viewport[] = [
  {
    width: 1280,
    height: 800,
    deviceScaleFactor: 1,
    isMobile: false,
    hasTouch: false,
    name: 'laptop',
  },
  {
    width: 1920,
    height: 1080,
    deviceScaleFactor: 1,
    isMobile: false,
    hasTouch: false,
    name: 'desktop-hd',
  },
  {
    width: 2560,
    height: 1440,
    deviceScaleFactor: 2,
    isMobile: false,
    hasTouch: false,
    name: 'desktop-2k',
  },
];

Mobile Viewports

const MOBILE_VIEWPORTS: Viewport[] = [
  {
    width: 320,
    height: 568,
    deviceScaleFactor: 2,
    isMobile: true,
    hasTouch: true,
    name: 'iphone-se',
    userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X)...',
  },
  {
    width: 375,
    height: 812,
    deviceScaleFactor: 3,
    isMobile: true,
    hasTouch: true,
    name: 'iphone-x',
    userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X)...',
  },
  {
    width: 390,
    height: 844,
    deviceScaleFactor: 3,
    isMobile: true,
    hasTouch: true,
    name: 'iphone-12-pro',
    userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X)...',
  },
  {
    width: 360,
    height: 740,
    deviceScaleFactor: 3,
    isMobile: true,
    hasTouch: true,
    name: 'android-pixel',
    userAgent: 'Mozilla/5.0 (Linux; Android 12)...',
  },
];

Tablet Viewports

const TABLET_VIEWPORTS: Viewport[] = [
  {
    width: 768,
    height: 1024,
    deviceScaleFactor: 2,
    isMobile: false,
    hasTouch: true,
    name: 'ipad',
    userAgent: 'Mozilla/5.0 (iPad; CPU OS 15_0 like Mac OS X)...',
  },
  {
    width: 1024,
    height: 1366,
    deviceScaleFactor: 2,
    isMobile: false,
    hasTouch: true,
    name: 'ipad-pro',
    userAgent: 'Mozilla/5.0 (iPad; CPU OS 15_0 like Mac OS X)...',
  },
];

Predefined Sets

const VIEWPORT_SETS = {
  minimal: [MOBILE_VIEWPORTS[0], DESKTOP_VIEWPORTS[0]], // 2 viewports
  standard: [
    MOBILE_VIEWPORTS[0],   // iPhone SE
    TABLET_VIEWPORTS[0],   // iPad
    DESKTOP_VIEWPORTS[0],  // Laptop
  ], // 3 viewports
  comprehensive: [
    ...MOBILE_VIEWPORTS,   // All mobile
    ...TABLET_VIEWPORTS,   // All tablet
    ...DESKTOP_VIEWPORTS,  // All desktop
  ], // 9 viewports
};

Class: BrowserSwarmCoordinator

Constructor

class BrowserSwarmCoordinator {
  constructor(config?: SwarmConfig);
}

Configuration:

interface SwarmConfig {
  // Max concurrent browser sessions
  maxConcurrentSessions?: number;  // Default: 5

  // Session timeout (ms)
  sessionTimeout?: number;         // Default: 30000

  // Process cleanup strategy
  cleanupStrategy?: 'graceful' | 'aggressive';  // Default: 'graceful'

  // Cleanup timeout (ms)
  cleanupTimeout?: number;         // Default: 5000

  // Enable headless mode
  headless?: boolean;              // Default: true

  // Browser type
  browserType?: 'chromium' | 'firefox' | 'webkit';  // Default: 'chromium'

  // Retry configuration
  retryConfig?: {
    maxAttempts: number;
    backoff: 'linear' | 'exponential';
  };
}

Methods

initialize()

Initialize the swarm with viewport configurations.

async initialize(
  viewports: Viewport[] | 'minimal' | 'standard' | 'comprehensive'
): Promise<Result<void, Error>>

Parameters:

  • viewports: Array of viewport configs or predefined set name

Example:

const swarm = new BrowserSwarmCoordinator({
  maxConcurrentSessions: 5,
  headless: true,
});

// Use predefined set
await swarm.initialize('standard');

// Or custom viewports
await swarm.initialize([
  { width: 320, height: 568, name: 'mobile' },
  { width: 1920, height: 1080, name: 'desktop' },
]);

executeParallel()

Execute a task across all viewports in parallel.

async executeParallel<T>(
  task: (session: BrowserSession, viewport: Viewport) => Promise<T>
): Promise<Map<Viewport, Result<T, Error>>>

Parameters:

  • task: Function to execute for each viewport

Returns: Map of viewport to task result

Example:

const results = await swarm.executeParallel(async (session, viewport) => {
  await session.goto('https://example.com');

  return {
    screenshot: await session.screenshot(),
    title: await session.title(),
    viewport: viewport.name,
  };
});

// Process results
for (const [viewport, result] of results) {
  if (result.ok) {
    console.log(`${viewport.name}: ${result.value.title}`);
  } else {
    console.error(`${viewport.name}: ${result.error.message}`);
  }
}

captureAllViewports()

Capture screenshots across all viewports in parallel.

async captureAllViewports(
  url: string,
  options?: CaptureOptions
): Promise<Map<Viewport, Result<Screenshot, Error>>>

Parameters:

interface CaptureOptions {
  fullPage?: boolean;           // Capture full page (default: true)
  waitUntil?: 'load' | 'domcontentloaded' | 'networkidle';
  timeout?: number;             // Navigation timeout (ms)
  savePath?: string;            // Save directory
}

Returns: Map of viewport to screenshot

Example:

const screenshots = await swarm.captureAllViewports(
  'https://example.com',
  {
    fullPage: true,
    waitUntil: 'networkidle',
    savePath: './screenshots',
  }
);

// Check results
for (const [viewport, result] of screenshots) {
  if (result.ok) {
    console.log(`✓ ${viewport.name}: ${result.value.path}`);
  } else {
    console.error(`✗ ${viewport.name}: ${result.error.message}`);
  }
}

Performance:

Sequential: 5 viewports × 5s each = 25s
Parallel:   5 viewports / 5 concurrent = 5s
Speedup:    5x

auditAllViewports()

Run accessibility audits across all viewports in parallel.

async auditAllViewports(
  url: string,
  options?: AuditOptions
): Promise<Map<Viewport, Result<AccessibilityReport, Error>>>

Parameters:

interface AuditOptions {
  standard?: 'WCAG21A' | 'WCAG21AA' | 'WCAG21AAA';
  rules?: string[];             // Specific rules to check
  timeout?: number;
}

Returns: Map of viewport to accessibility report

Example:

const audits = await swarm.auditAllViewports(
  'https://example.com',
  {
    standard: 'WCAG21AA',
    rules: ['color-contrast', 'image-alt', 'label'],
  }
);

// Aggregate violations
const allViolations = [];
for (const [viewport, result] of audits) {
  if (result.ok) {
    allViolations.push(...result.value.violations.map(v => ({
      ...v,
      viewport: viewport.name,
    })));
  }
}

console.log(`Total violations: ${allViolations.length}`);

getSwarmStatus()

Get current swarm status and metrics.

async getSwarmStatus(): Promise<SwarmStatus>

Returns:

interface SwarmStatus {
  initialized: boolean;
  activeSessions: number;
  totalSessions: number;
  viewports: Viewport[];
  metrics: {
    totalTasks: number;
    successfulTasks: number;
    failedTasks: number;
    avgTaskDuration: number;    // ms
    memoryUsage: number;        // MB
  };
  health: 'healthy' | 'degraded' | 'unhealthy';
}

Example:

const status = await swarm.getSwarmStatus();

console.log('Swarm status:', status.health);
console.log('Active sessions:', status.activeSessions, '/', status.totalSessions);
console.log('Success rate:', (status.metrics.successfulTasks / status.metrics.totalTasks * 100).toFixed(1), '%');
console.log('Memory usage:', status.metrics.memoryUsage, 'MB');

shutdown()

Gracefully shutdown all browser sessions.

async shutdown(): Promise<Result<void, Error>>

Example:

// Always shutdown in cleanup
try {
  await swarm.initialize('standard');
  await swarm.captureAllViewports('https://example.com');
} finally {
  await swarm.shutdown();
}

Resource Management

Session Pooling

The coordinator maintains a pool of browser sessions:

class SessionPool {
  private sessions: Map<Viewport, BrowserSession>;
  private maxSize: number;

  async acquire(viewport: Viewport): Promise<BrowserSession> {
    // Reuse existing session or create new one
    if (this.sessions.has(viewport)) {
      return this.sessions.get(viewport)!;
    }

    if (this.sessions.size >= this.maxSize) {
      // Wait for available slot
      await this.waitForSlot();
    }

    const session = await this.createSession(viewport);
    this.sessions.set(viewport, session);
    return session;
  }

  async release(viewport: Viewport): Promise<void> {
    const session = this.sessions.get(viewport);
    if (session) {
      await session.close();
      this.sessions.delete(viewport);
    }
  }
}

Memory Management

interface MemoryMonitor {
  // Current memory usage
  current: number;              // MB

  // Memory limit
  limit: number;                // MB

  // Memory pressure (0-1)
  pressure: number;

  // Actions taken
  actions: Array<{
    timestamp: number;
    action: 'gc' | 'session-close' | 'fallback';
    reason: string;
  }>;
}

// Auto-scaling based on memory
if (monitor.pressure > 0.8) {
  // Close oldest sessions
  await pool.closeOldest();

  // Or fall back to sequential execution
  config.maxConcurrentSessions = 1;
}

Process Cleanup

// Cleanup strategies
const CLEANUP_STRATEGIES = {
  graceful: {
    // Wait for pending tasks
    timeout: 5000,
    killAfterTimeout: true,
  },
  aggressive: {
    // Kill immediately
    timeout: 0,
    killAfterTimeout: true,
  },
};

// Cleanup on process exit
process.on('SIGINT', async () => {
  await swarm.shutdown();
  process.exit(0);
});

process.on('SIGTERM', async () => {
  await swarm.shutdown();
  process.exit(0);
});

Practical Examples

Example 1: Visual Regression Testing

import { BrowserSwarmCoordinator } from '@agentic-qe/v3/domains/visual-accessibility';
import { compareImages } from 'pixelmatch';

async function visualRegressionTest(urls: string[]) {
  const swarm = new BrowserSwarmCoordinator({ maxConcurrentSessions: 5 });

  try {
    await swarm.initialize('standard');

    for (const url of urls) {
      console.log(`Testing ${url}...`);

      const screenshots = await swarm.captureAllViewports(url, {
        fullPage: true,
        waitUntil: 'networkidle',
      });

      // Compare with baselines
      for (const [viewport, result] of screenshots) {
        if (!result.ok) {
          console.error(`Failed to capture ${viewport.name}:`, result.error);
          continue;
        }

        const baselinePath = `./baselines/${viewport.name}.png`;
        const currentPath = result.value.path;

        const diff = await compareImages(baselinePath, currentPath);

        if (diff > 0.01) {
          console.error(`Visual regression detected in ${viewport.name}: ${(diff * 100).toFixed(2)}% difference`);
        } else {
          console.log(`✓ ${viewport.name}: No regression`);
        }
      }
    }
  } finally {
    await swarm.shutdown();
  }
}

// Run test
await visualRegressionTest([
  'https://example.com',
  'https://example.com/products',
  'https://example.com/about',
]);

Example 2: Responsive Design Validation

async function validateResponsiveDesign(url: string) {
  const swarm = new BrowserSwarmCoordinator();

  // Test mobile, tablet, desktop
  await swarm.initialize('comprehensive');

  const results = await swarm.executeParallel(async (session, viewport) => {
    await session.goto(url);

    // Check critical elements
    const header = await session.locator('header').boundingBox();
    const nav = await session.locator('nav').boundingBox();
    const content = await session.locator('main').boundingBox();

    // Validate layout
    const issues = [];

    if (!header) {
      issues.push('Header not visible');
    }

    if (viewport.isMobile && nav && nav.height > viewport.height * 0.3) {
      issues.push('Navigation takes up >30% of viewport');
    }

    if (content && content.width > viewport.width) {
      issues.push('Content overflows viewport');
    }

    return {
      viewport: viewport.name,
      issues,
      screenshot: await session.screenshot(),
    };
  });

  // Report issues
  for (const [viewport, result] of results) {
    if (result.ok && result.value.issues.length > 0) {
      console.error(`Issues on ${viewport.name}:`);
      result.value.issues.forEach(issue => console.error(`  - ${issue}`));
    }
  }

  await swarm.shutdown();
}

Example 3: Performance Comparison

async function comparePerformance(url: string) {
  const swarm = new BrowserSwarmCoordinator();

  await swarm.initialize([
    { width: 320, height: 568, name: 'mobile-3g' }, // Slow connection
    { width: 1920, height: 1080, name: 'desktop-fast' },
  ]);

  const results = await swarm.executeParallel(async (session, viewport) => {
    // Emulate network conditions
    if (viewport.name === 'mobile-3g') {
      await session.emulateNetworkConditions({
        downloadThroughput: 375 * 1024 / 8,  // 3G speed
        uploadThroughput: 375 * 1024 / 8,
        latency: 100,
      });
    }

    const start = Date.now();
    await session.goto(url);
    const loadTime = Date.now() - start;

    const metrics = await session.metrics();

    return {
      viewport: viewport.name,
      loadTime,
      metrics,
    };
  });

  // Compare results
  console.log('Performance comparison:');
  for (const [viewport, result] of results) {
    if (result.ok) {
      console.log(`${viewport.name}: ${result.value.loadTime}ms`);
    }
  }

  await swarm.shutdown();
}

Performance Benchmarks

Speedup Analysis

Viewports Sequential Time Parallel Time (5 sessions) Speedup
2 10s 5s 2.0x
3 15s 5s 3.0x
5 25s 8s 3.1x
9 45s 12s 3.8x

Formula:

Speedup = SequentialTime / ParallelTime
ParallelTime ≈ max(TaskTime) + (n - maxConcurrent) × TaskTime / maxConcurrent

Resource Usage

Sessions CPU Usage Memory Usage Recommendations
1 20% 150 MB Baseline
3 40% 400 MB Optimal for most systems
5 65% 650 MB Default configuration
9 95% 1.2 GB Requires 8+ GB RAM

Error Handling

Retry Logic

const swarm = new BrowserSwarmCoordinator({
  retryConfig: {
    maxAttempts: 3,
    backoff: 'exponential',
  },
});

// Automatically retries failed tasks
const results = await swarm.captureAllViewports(url);

// Check which viewports succeeded
for (const [viewport, result] of results) {
  if (!result.ok) {
    console.error(`Failed after retries: ${viewport.name}`);
  }
}

Fallback to Sequential

try {
  await swarm.initialize('standard');
  await swarm.captureAllViewports(url);
} catch (error) {
  console.warn('Swarm failed, falling back to sequential');

  // Sequential fallback
  for (const viewport of viewports) {
    const session = await createSession(viewport);
    await session.goto(url);
    await session.screenshot();
    await session.close();
  }
}

Testing

import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { BrowserSwarmCoordinator } from './browser-swarm-coordinator';

describe('BrowserSwarmCoordinator', () => {
  let swarm: BrowserSwarmCoordinator;

  beforeAll(async () => {
    swarm = new BrowserSwarmCoordinator();
    await swarm.initialize('minimal');
  });

  afterAll(async () => {
    await swarm.shutdown();
  });

  it('should capture screenshots in parallel', async () => {
    const results = await swarm.captureAllViewports('https://example.com');

    expect(results.size).toBeGreaterThan(0);

    for (const [viewport, result] of results) {
      expect(result.ok).toBe(true);
      expect(result.value.path).toBeDefined();
    }
  });

  it('should be faster than sequential execution', async () => {
    const parallelStart = Date.now();
    await swarm.captureAllViewports('https://example.com');
    const parallelTime = Date.now() - parallelStart;

    // Sequential execution would take ~10s (2 viewports × 5s each)
    expect(parallelTime).toBeLessThan(8000); // Should complete in <8s
  });
});

See Also