or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

advanced-web-features.mdbrowser-page-control.mdcore-debugging.mddevice-testing.mddom-styling.mdindex.mdnetwork-performance.mdstorage-data.md
tile.json

device-testing.mddocs/

Device and Testing Support

Device emulation, input simulation, and testing utilities for cross-platform development. This covers domains that enable testing and debugging across different devices, orientations, and interaction modes.

Capabilities

Emulation Domain

Device and browser emulation for testing responsive designs and mobile experiences.

namespace Protocol.Emulation {
  interface ScreenOrientation {
    type: ('portraitPrimary' | 'portraitSecondary' | 'landscapePrimary' | 'landscapeSecondary');
    angle: integer;
  }

  interface DisplayFeature {
    orientation: ('vertical' | 'horizontal');
    offset: integer;
    maskLength: integer;
  }

  interface MediaFeature {
    name: string;
    value: string;
  }

  interface VirtualTimePolicy = ('advance' | 'pause' | 'pauseIfNetworkFetchesPending');

  interface UserAgentBrandVersion {
    brand: string;
    version: string;
  }

  interface UserAgentMetadata {
    brands?: UserAgentBrandVersion[];
    fullVersionList?: UserAgentBrandVersion[];
    platform?: string;
    platformVersion?: string;
    architecture?: string;
    model?: string;
    mobile?: boolean;
    bitness?: string;
    wow64?: boolean;
  }

  interface SetDeviceMetricsOverrideRequest {
    width: integer;
    height: integer;
    deviceScaleFactor: number;
    mobile: boolean;
    scale?: number;
    screenWidth?: integer;
    screenHeight?: integer;
    positionX?: integer;
    positionY?: integer;
    dontSetVisibleSize?: boolean;
    screenOrientation?: ScreenOrientation;
    viewport?: Page.Viewport;
    displayFeature?: DisplayFeature;
    devicePosture?: DevicePosture;
  }

  interface SetGeolocationOverrideRequest {
    latitude?: number;
    longitude?: number;
    accuracy?: number;
  }

  interface SetUserAgentOverrideRequest {
    userAgent: string;
    acceptLanguage?: string;
    platform?: string;
    userAgentMetadata?: UserAgentMetadata;
  }

  interface SetTimezoneOverrideRequest {
    timezoneId: string;
  }

  interface SetLocaleOverrideRequest {
    locale?: string;
  }

  interface SetVirtualTimePolicyRequest {
    policy: VirtualTimePolicy;
    budget?: number;
    maxVirtualTimeTaskStarvationCount?: integer;
    initialVirtualTime?: Network.TimeSinceEpoch;
  }

  interface SetVirtualTimePolicyResponse {
    virtualTimeTicksBase: number;
  }

  interface SetPageScaleFactorRequest {
    pageScaleFactor: number;
  }

  interface SetScrollbarsHiddenRequest {
    hidden: boolean;
  }

  interface SetDocumentCookieDisabledRequest {
    disabled: boolean;
  }

  interface SetEmitTouchEventsForMouseRequest {
    enabled: boolean;
    configuration?: ('mobile' | 'desktop');
  }

  interface SetEmulatedMediaRequest {
    media?: string;
    features?: MediaFeature[];
  }

  interface SetCPUThrottlingRateRequest {
    rate: number;
  }

  interface SetFocusEmulationEnabledRequest {
    enabled: boolean;
  }

  interface SetAutoDarkModeOverrideRequest {
    enabled?: boolean;
  }

  interface VirtualTimeBudgetExpiredEvent {}

  interface VirtualTimeAdvancedEvent {
    virtualTimeElapsed: number;
  }

  interface VirtualTimePausedEvent {
    virtualTimeElapsed: number;
  }
}

Usage Example:

import Protocol from "devtools-protocol/types/protocol";

class DeviceEmulator {
  async emulateDevice(deviceConfig: DeviceConfig): Promise<void> {
    // Set device metrics
    const metricsRequest: Protocol.Emulation.SetDeviceMetricsOverrideRequest = {
      width: deviceConfig.width,
      height: deviceConfig.height,
      deviceScaleFactor: deviceConfig.deviceScaleFactor,
      mobile: deviceConfig.mobile,
      screenOrientation: {
        type: deviceConfig.orientation,
        angle: deviceConfig.orientationAngle
      }
    };

    // Set user agent
    const userAgentRequest: Protocol.Emulation.SetUserAgentOverrideRequest = {
      userAgent: deviceConfig.userAgent,
      platform: deviceConfig.platform,
      userAgentMetadata: {
        brands: deviceConfig.brands,
        mobile: deviceConfig.mobile,
        platform: deviceConfig.platform
      }
    };

    // Set geolocation if provided
    if (deviceConfig.geolocation) {
      const geoRequest: Protocol.Emulation.SetGeolocationOverrideRequest = {
        latitude: deviceConfig.geolocation.latitude,
        longitude: deviceConfig.geolocation.longitude,
        accuracy: deviceConfig.geolocation.accuracy
      };
      
      // Implementation would send Emulation.setGeolocationOverride
    }

    // Set timezone
    const timezoneRequest: Protocol.Emulation.SetTimezoneOverrideRequest = {
      timezoneId: deviceConfig.timezone || "UTC"
    };

    // Implementation would send respective emulation commands
  }

  async setupNetworkThrottling(profile: NetworkThrottlingProfile): Promise<void> {
    // Set CPU throttling
    const cpuRequest: Protocol.Emulation.SetCPUThrottlingRateRequest = {
      rate: profile.cpuThrottling
    };

    // Implementation would send Emulation.setCPUThrottlingRate
  }

  async enableTouchEmulation(enabled: boolean): Promise<void> {
    const touchRequest: Protocol.Emulation.SetEmitTouchEventsForMouseRequest = {
      enabled,
      configuration: 'mobile'
    };

    // Implementation would send Emulation.setEmitTouchEventsForMouse
  }

  async emulateMediaFeatures(features: Protocol.Emulation.MediaFeature[]): Promise<void> {
    const mediaRequest: Protocol.Emulation.SetEmulatedMediaRequest = {
      features
    };

    // Implementation would send Emulation.setEmulatedMedia
  }

  setupVirtualTimeControl(): VirtualTimeController {
    return new VirtualTimeController();
  }
}

class VirtualTimeController {
  async pauseVirtualTime(): Promise<void> {
    const request: Protocol.Emulation.SetVirtualTimePolicyRequest = {
      policy: 'pause'
    };

    // Implementation would send Emulation.setVirtualTimePolicy
  }

  async advanceVirtualTime(milliseconds: number): Promise<void> {
    const request: Protocol.Emulation.SetVirtualTimePolicyRequest = {
      policy: 'advance',
      budget: milliseconds
    };

    // Implementation would send Emulation.setVirtualTimePolicy
  }

  setupVirtualTimeListeners(): void {
    this.onVirtualTimeBudgetExpired = (event: Protocol.Emulation.VirtualTimeBudgetExpiredEvent) => {
      console.log("Virtual time budget expired");
    };

    this.onVirtualTimeAdvanced = (event: Protocol.Emulation.VirtualTimeAdvancedEvent) => {
      console.log(`Virtual time advanced by ${event.virtualTimeElapsed}ms`);
    };
  }
}

interface DeviceConfig {
  width: number;
  height: number;
  deviceScaleFactor: number;
  mobile: boolean;
  orientation: 'portraitPrimary' | 'portraitSecondary' | 'landscapePrimary' | 'landscapeSecondary';
  orientationAngle: number;
  userAgent: string;
  platform: string;
  brands?: Protocol.Emulation.UserAgentBrandVersion[];
  geolocation?: {
    latitude: number;
    longitude: number;
    accuracy: number;
  };
  timezone?: string;
}

interface NetworkThrottlingProfile {
  cpuThrottling: number;
  downloadThroughput?: number;
  uploadThroughput?: number;
  latency?: number;
}

Input Domain

Input event simulation and handling for automated testing and interaction simulation.

namespace Protocol.Input {
  type TouchPoint = {
    id: integer;
    x: number;
    y: number;
    radiusX?: number;
    radiusY?: number;
    rotationAngle?: number;
    force?: number;
  };

  type GestureSourceType = ('default' | 'touch' | 'mouse');
  type MouseButton = ('none' | 'left' | 'middle' | 'right' | 'back' | 'forward');
  type KeyboardModifiers = integer;

  interface DispatchKeyEventRequest {
    type: ('keyDown' | 'keyUp' | 'rawKeyDown' | 'char');
    modifiers?: integer;
    timestamp?: Network.TimeSinceEpoch;
    text?: string;
    unmodifiedText?: string;
    keyIdentifier?: string;
    code?: string;
    key?: string;
    windowsVirtualKeyCode?: integer;
    nativeVirtualKeyCode?: integer;
    autoRepeat?: boolean;
    isKeypad?: boolean;
    isSystemKey?: boolean;
    location?: integer;
    commands?: string[];
  }

  interface DispatchMouseEventRequest {
    type: ('mousePressed' | 'mouseReleased' | 'mouseMoved' | 'mouseWheel');
    x: number;
    y: number;
    modifiers?: integer;
    timestamp?: Network.TimeSinceEpoch;
    button?: MouseButton;
    buttons?: integer;
    clickCount?: integer;
    force?: number;
    tangentialPressure?: number;
    tiltX?: integer;
    tiltY?: integer;
    twist?: integer;
    deltaX?: number;
    deltaY?: number;
    pointerType?: ('mouse' | 'pen');
  }

  interface DispatchTouchEventRequest {
    type: ('touchStart' | 'touchEnd' | 'touchMove' | 'touchCancel');
    touchPoints: TouchPoint[];
    modifiers?: integer;
    timestamp?: Network.TimeSinceEpoch;
  }

  interface EmulateTouchFromMouseEventRequest {
    type: ('mousePressed' | 'mouseReleased' | 'mouseMoved' | 'mouseWheel');
    x: integer;
    y: integer;
    button: MouseButton;
    timestamp?: Network.TimeSinceEpoch;
    deltaX?: number;
    deltaY?: number;
    modifiers?: integer;
    clickCount?: integer;
  }

  interface SynthesizePinchGestureRequest {
    x: number;
    y: number;
    scaleFactor: number;
    relativeSpeed?: integer;
    gestureSourceType?: GestureSourceType;
  }

  interface SynthesizeScrollGestureRequest {
    x: number;
    y: number;
    xDistance?: number;
    yDistance?: number;
    xOverscroll?: number;
    yOverscroll?: number;
    preventFling?: boolean;
    speed?: integer;
    gestureSourceType?: GestureSourceType;
    repeatCount?: integer;
    repeatDelayMs?: integer;
    interactionMarkerName?: string;
  }

  interface SynthesizeTapGestureRequest {
    x: number;
    y: number;
    duration?: integer;
    tapCount?: integer;
    gestureSourceType?: GestureSourceType;
  }

  interface SetIgnoreInputEventsRequest {
    ignore: boolean;
  }

  interface SetInterceptDragsRequest {
    enabled: boolean;
  }

  interface DragDataItem {
    mimeType: string;
    data: string;
    title?: string;
    baseURL?: string;
  }

  interface DispatchDragEventRequest {
    type: ('dragEnter' | 'dragOver' | 'drop' | 'dragCancel');
    x: number;
    y: number;
    data: DragDataItem[];
    modifiers?: integer;
  }
}

Usage Example:

import Protocol from "devtools-protocol/types/protocol";

class InputSimulator {
  async clickElement(x: number, y: number): Promise<void> {
    // Mouse press
    const pressRequest: Protocol.Input.DispatchMouseEventRequest = {
      type: 'mousePressed',
      x,
      y,
      button: 'left',
      clickCount: 1
    };

    // Mouse release
    const releaseRequest: Protocol.Input.DispatchMouseEventRequest = {
      type: 'mouseReleased',
      x,
      y,
      button: 'left',
      clickCount: 1
    };

    // Implementation would send Input.dispatchMouseEvent for both events
  }

  async typeText(text: string): Promise<void> {
    for (const char of text) {
      // Key down
      const keyDownRequest: Protocol.Input.DispatchKeyEventRequest = {
        type: 'keyDown',
        text: char,
        key: char,
        code: `Key${char.toUpperCase()}`
      };

      // Key up
      const keyUpRequest: Protocol.Input.DispatchKeyEventRequest = {
        type: 'keyUp',
        text: char,
        key: char,
        code: `Key${char.toUpperCase()}`
      };

      // Implementation would send Input.dispatchKeyEvent for both events
      await this.delay(50); // Small delay between characters
    }
  }

  async performTouchGesture(gesture: TouchGesture): Promise<void> {
    switch (gesture.type) {
      case 'tap':
        await this.tap(gesture.x, gesture.y);
        break;
      case 'scroll':
        await this.scroll(gesture.x, gesture.y, gesture.deltaX!, gesture.deltaY!);
        break;
      case 'pinch':
        await this.pinch(gesture.x, gesture.y, gesture.scaleFactor!);
        break;
    }
  }

  private async tap(x: number, y: number): Promise<void> {
    const tapRequest: Protocol.Input.SynthesizeTapGestureRequest = {
      x,
      y,
      duration: 50,
      tapCount: 1,
      gestureSourceType: 'touch'
    };

    // Implementation would send Input.synthesizeTapGesture
  }

  private async scroll(x: number, y: number, deltaX: number, deltaY: number): Promise<void> {
    const scrollRequest: Protocol.Input.SynthesizeScrollGestureRequest = {
      x,
      y,
      xDistance: deltaX,
      yDistance: deltaY,
      gestureSourceType: 'touch'
    };

    // Implementation would send Input.synthesizeScrollGesture
  }

  private async pinch(x: number, y: number, scaleFactor: number): Promise<void> {
    const pinchRequest: Protocol.Input.SynthesizePinchGestureRequest = {
      x,
      y,
      scaleFactor,
      gestureSourceType: 'touch'
    };

    // Implementation would send Input.synthesizePinchGesture
  }

  async simulateKeyboardShortcut(modifiers: string[], key: string): Promise<void> {
    let modifierBits = 0;
    
    // Convert modifier strings to bits
    if (modifiers.includes('ctrl')) modifierBits |= 2;
    if (modifiers.includes('shift')) modifierBits |= 8;
    if (modifiers.includes('alt')) modifierBits |= 1;
    if (modifiers.includes('meta')) modifierBits |= 4;

    const keyRequest: Protocol.Input.DispatchKeyEventRequest = {
      type: 'keyDown',
      modifiers: modifierBits,
      key,
      code: `Key${key.toUpperCase()}`
    };

    // Implementation would send Input.dispatchKeyEvent
  }

  async enableInputInterception(enabled: boolean): Promise<void> {
    const request: Protocol.Input.SetIgnoreInputEventsRequest = {
      ignore: !enabled
    };

    // Implementation would send Input.setIgnoreInputEvents
  }

  private delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

interface TouchGesture {
  type: 'tap' | 'scroll' | 'pinch';
  x: number;
  y: number;
  deltaX?: number;
  deltaY?: number;
  scaleFactor?: number;
}

DeviceOrientation Domain

Device orientation emulation for testing responsive designs with different orientations.

namespace Protocol.DeviceOrientation {
  interface SetDeviceOrientationOverrideRequest {
    alpha: number;
    beta: number;
    gamma: number;
  }
}

BluetoothEmulation Domain

Bluetooth device emulation for testing Bluetooth-related functionality.

namespace Protocol.BluetoothEmulation {
  interface CentralState = ('absent' | 'powered-off' | 'powered-on');

  interface ManufacturerData {
    key: integer;
    data: string;
  }

  interface ServiceData {
    uuid: string;
    data: string;
  }

  interface ScanRecord {
    deviceName?: string;
    uuids?: string[];
    appearance?: integer;
    txPower?: integer;
    manufacturerData?: ManufacturerData[];
    serviceData?: ServiceData[];
  }

  interface ScanEntry {
    deviceAddress: string;
    rssi: integer;
    scanRecord: ScanRecord;
  }

  interface EnableRequest {
    state: CentralState;
  }

  interface SimulatePreconnectedPeripheralRequest {
    address: string;
    name: string;
    manufacturerId: integer;
    knownServiceUuids: string[];
  }

  interface SimulateAdvertisementRequest {
    entry: ScanEntry;
  }
}

Common Usage Patterns

Comprehensive Device Testing

import Protocol from "devtools-protocol/types/protocol";

class DeviceTesting {
  private emulator: DeviceEmulator;
  private inputSimulator: InputSimulator;

  constructor() {
    this.emulator = new DeviceEmulator();
    this.inputSimulator = new InputSimulator();
  }

  async testResponsiveDesign(): Promise<TestResults> {
    const results: TestResults = {
      devices: [],
      issues: []
    };

    const deviceConfigs = this.getCommonDeviceConfigs();

    for (const device of deviceConfigs) {
      console.log(`Testing device: ${device.name}`);
      
      // Emulate device
      await this.emulator.emulateDevice(device.config);
      
      // Take screenshot
      // Implementation would capture screenshot
      
      // Test touch interactions
      const touchResults = await this.testTouchInteractions();
      
      // Test form inputs
      const formResults = await this.testFormInputs();
      
      // Test gestures
      const gestureResults = await this.testGestures();
      
      results.devices.push({
        name: device.name,
        touchResults,
        formResults,
        gestureResults,
        screenshot: `screenshot_${device.name}.png`
      });
    }

    return results;
  }

  private getCommonDeviceConfigs(): Array<{ name: string; config: DeviceConfig }> {
    return [
      {
        name: 'iPhone 12',
        config: {
          width: 390,
          height: 844,
          deviceScaleFactor: 3,
          mobile: true,
          orientation: 'portraitPrimary',
          orientationAngle: 0,
          userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15',
          platform: 'iPhone'
        }
      },
      {
        name: 'iPad Air',
        config: {
          width: 820,
          height: 1180,
          deviceScaleFactor: 2,
          mobile: true,
          orientation: 'portraitPrimary',
          orientationAngle: 0,
          userAgent: 'Mozilla/5.0 (iPad; CPU OS 14_0 like Mac OS X) AppleWebKit/605.1.15',
          platform: 'iPad'
        }
      },
      {
        name: 'Samsung Galaxy S21',
        config: {
          width: 384,
          height: 854,
          deviceScaleFactor: 2.75,
          mobile: true,
          orientation: 'portraitPrimary',
          orientationAngle: 0,
          userAgent: 'Mozilla/5.0 (Linux; Android 11; SM-G991B) AppleWebKit/537.36',
          platform: 'Linux armv7l'
        }
      }
    ];
  }

  private async testTouchInteractions(): Promise<TouchTestResults> {
    const results: TouchTestResults = {
      tapAccuracy: 0,
      scrollSmoothness: 0,
      pinchZoomWorks: false
    };

    // Test tap accuracy
    const tapTargets = [
      { x: 100, y: 100 },
      { x: 200, y: 200 },
      { x: 300, y: 300 }
    ];

    let successfulTaps = 0;
    for (const target of tapTargets) {
      try {
        await this.inputSimulator.performTouchGesture({
          type: 'tap',
          x: target.x,
          y: target.y
        });
        successfulTaps++;
      } catch (error) {
        console.error(`Tap failed at ${target.x}, ${target.y}:`, error);
      }
    }

    results.tapAccuracy = successfulTaps / tapTargets.length;

    // Test scroll smoothness
    try {
      await this.inputSimulator.performTouchGesture({
        type: 'scroll',
        x: 200,
        y: 400,
        deltaX: 0,
        deltaY: -200
      });
      results.scrollSmoothness = 1; // Simplified - would measure actual smoothness
    } catch (error) {
      results.scrollSmoothness = 0;
    }

    // Test pinch zoom
    try {
      await this.inputSimulator.performTouchGesture({
        type: 'pinch',
        x: 200,
        y: 300,
        scaleFactor: 2
      });
      results.pinchZoomWorks = true;
    } catch (error) {
      results.pinchZoomWorks = false;
    }

    return results;
  }

  private async testFormInputs(): Promise<FormTestResults> {
    const results: FormTestResults = {
      textInputWorks: false,
      keyboardShortcutsWork: false,
      virtualKeyboardShows: false
    };

    // Test text input
    try {
      await this.inputSimulator.clickElement(200, 200); // Click on input field
      await this.inputSimulator.typeText("Test input");
      results.textInputWorks = true;
    } catch (error) {
      results.textInputWorks = false;
    }

    // Test keyboard shortcuts
    try {
      await this.inputSimulator.simulateKeyboardShortcut(['ctrl'], 'a');
      results.keyboardShortcutsWork = true;
    } catch (error) {
      results.keyboardShortcutsWork = false;
    }

    return results;
  }

  private async testGestures(): Promise<GestureTestResults> {
    return {
      swipeWorks: true, // Simplified implementation
      longPressWorks: true,
      multiTouchWorks: true
    };
  }

  async testNetworkConditions(): Promise<void> {
    const networkProfiles = [
      { name: 'Fast 3G', cpuThrottling: 4 },
      { name: 'Slow 3G', cpuThrottling: 6 },
      { name: 'Offline', cpuThrottling: 1 }
    ];

    for (const profile of networkProfiles) {
      console.log(`Testing network condition: ${profile.name}`);
      await this.emulator.setupNetworkThrottling(profile);
      
      // Test page load performance under these conditions
      // Implementation would measure load times and performance metrics
    }
  }

  async testAccessibility(): Promise<AccessibilityResults> {
    // Enable focus emulation for accessibility testing
    await this.emulator.enableTouchEmulation(false);
    
    // Test keyboard navigation
    const keyboardNavResults = await this.testKeyboardNavigation();
    
    // Test screen reader compatibility
    const screenReaderResults = await this.testScreenReaderCompatibility();
    
    return {
      keyboardNavigation: keyboardNavResults,
      screenReader: screenReaderResults
    };
  }

  private async testKeyboardNavigation(): Promise<boolean> {
    // Test tab navigation
    try {
      await this.inputSimulator.simulateKeyboardShortcut([], 'Tab');
      await this.inputSimulator.simulateKeyboardShortcut(['shift'], 'Tab');
      return true;
    } catch (error) {
      return false;
    }
  }

  private async testScreenReaderCompatibility(): Promise<boolean> {
    // This would involve checking ARIA attributes and semantic HTML
    // Simplified implementation
    return true;
  }
}

interface TestResults {
  devices: DeviceTestResult[];
  issues: string[];
}

interface DeviceTestResult {
  name: string;
  touchResults: TouchTestResults;
  formResults: FormTestResults;
  gestureResults: GestureTestResults;
  screenshot: string;
}

interface TouchTestResults {
  tapAccuracy: number;
  scrollSmoothness: number;
  pinchZoomWorks: boolean;
}

interface FormTestResults {
  textInputWorks: boolean;
  keyboardShortcutsWork: boolean;
  virtualKeyboardShows: boolean;
}

interface GestureTestResults {
  swipeWorks: boolean;
  longPressWorks: boolean;
  multiTouchWorks: boolean;
}

interface AccessibilityResults {
  keyboardNavigation: boolean;
  screenReader: boolean;
}

Automated Test Execution

import Protocol from "devtools-protocol/types/protocol";

class AutomatedTester {
  private inputSimulator: InputSimulator;
  
  constructor() {
    this.inputSimulator = new InputSimulator();
  }

  async runTestSuite(tests: TestCase[]): Promise<TestSuiteResults> {
    const results: TestSuiteResults = {
      passed: 0,
      failed: 0,
      details: []
    };

    for (const test of tests) {
      console.log(`Running test: ${test.name}`);
      
      try {
        const startTime = Date.now();
        await this.executeTest(test);
        const endTime = Date.now();
        
        results.passed++;
        results.details.push({
          name: test.name,
          status: 'passed',
          duration: endTime - startTime
        });
      } catch (error) {
        results.failed++;
        results.details.push({
          name: test.name,
          status: 'failed',
          error: error.message,
          duration: 0
        });
      }
    }

    return results;
  }

  private async executeTest(test: TestCase): Promise<void> {
    for (const action of test.actions) {
      await this.executeAction(action);
      
      if (action.waitAfter) {
        await this.delay(action.waitAfter);
      }
    }
  }

  private async executeAction(action: TestAction): Promise<void> {
    switch (action.type) {
      case 'click':
        await this.inputSimulator.clickElement(action.x!, action.y!);
        break;
      case 'type':
        await this.inputSimulator.typeText(action.text!);
        break;
      case 'scroll':
        await this.inputSimulator.performTouchGesture({
          type: 'scroll',
          x: action.x!,
          y: action.y!,
          deltaX: action.deltaX,
          deltaY: action.deltaY
        });
        break;
      case 'wait':
        await this.delay(action.duration!);
        break;
    }
  }

  private delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

interface TestCase {
  name: string;
  actions: TestAction[];
}

interface TestAction {
  type: 'click' | 'type' | 'scroll' | 'wait';
  x?: number;
  y?: number;
  text?: string;
  deltaX?: number;
  deltaY?: number;
  duration?: number;
  waitAfter?: number;
}

interface TestSuiteResults {
  passed: number;
  failed: number;
  details: TestResult[];
}

interface TestResult {
  name: string;
  status: 'passed' | 'failed';
  duration: number;
  error?: string;
}