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

advanced-web-features.mddocs/

Advanced Web Platform Features

Modern web platform APIs including WebAuthn, Service Workers, Progressive Web App features, and other advanced browser capabilities. This covers domains that enable debugging and testing of cutting-edge web technologies.

Capabilities

WebAuthn Domain

WebAuthn/FIDO2 authentication debugging for testing passwordless authentication flows.

namespace Protocol.WebAuthn {
  type AuthenticatorId = string;
  type AuthenticatorProtocol = ('u2f' | 'ctap2');
  type AuthenticatorTransport = ('usb' | 'nfc' | 'ble' | 'cable' | 'internal');
  type Ctap2Version = ('ctap2_0' | 'ctap2_1');

  interface VirtualAuthenticatorOptions {
    protocol: AuthenticatorProtocol;
    ctap2Version?: Ctap2Version;
    transport: AuthenticatorTransport;
    hasResidentKey?: boolean;
    hasUserVerification?: boolean;
    hasLargeBlob?: boolean;
    hasCredBlob?: boolean;
    hasMinPinLength?: boolean;
    hasPrf?: boolean;
    automaticPresenceSimulation?: boolean;
    isUserVerified?: boolean;
    defaultBackupEligibility?: boolean;
    defaultBackupState?: boolean;
  }

  interface Credential {
    credentialId: string;
    isResidentCredential: boolean;
    rpId?: string;
    privateKey: string;
    userHandle?: string;
    signCount: integer;
    largeBlob?: string;
    backupEligibility?: boolean;
    backupState?: boolean;
  }

  interface AddVirtualAuthenticatorRequest {
    options: VirtualAuthenticatorOptions;
  }

  interface AddVirtualAuthenticatorResponse {
    authenticatorId: AuthenticatorId;
  }

  interface RemoveVirtualAuthenticatorRequest {
    authenticatorId: AuthenticatorId;
  }

  interface AddCredentialRequest {
    authenticatorId: AuthenticatorId;
    credential: Credential;
  }

  interface GetCredentialRequest {
    authenticatorId: AuthenticatorId;
    credentialId: string;
  }

  interface GetCredentialResponse {
    credential: Credential;
  }

  interface GetCredentialsRequest {
    authenticatorId: AuthenticatorId;
  }

  interface GetCredentialsResponse {
    credentials: Credential[];
  }

  interface RemoveCredentialRequest {
    authenticatorId: AuthenticatorId;
    credentialId: string;
  }

  interface ClearCredentialsRequest {
    authenticatorId: AuthenticatorId;
  }

  interface SetUserVerifiedRequest {
    authenticatorId: AuthenticatorId;
    isUserVerified: boolean;
  }

  interface SetAutomaticPresenceSimulationRequest {
    authenticatorId: AuthenticatorId;
    enabled: boolean;
  }

  interface CredentialAddedEvent {
    authenticatorId: AuthenticatorId;
    credential: Credential;
  }

  interface CredentialAssertedEvent {
    authenticatorId: AuthenticatorId;
    credential: Credential;
  }
}

Usage Example:

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

class WebAuthnTester {
  private authenticators: Map<string, Protocol.WebAuthn.AuthenticatorId> = new Map();

  async setupVirtualAuthenticator(name: string, options: Partial<Protocol.WebAuthn.VirtualAuthenticatorOptions> = {}): Promise<Protocol.WebAuthn.AuthenticatorId> {
    const defaultOptions: Protocol.WebAuthn.VirtualAuthenticatorOptions = {
      protocol: 'ctap2',
      ctap2Version: 'ctap2_1',
      transport: 'internal',
      hasResidentKey: true,
      hasUserVerification: true,
      automaticPresenceSimulation: true,
      isUserVerified: true,
      ...options
    };

    const request: Protocol.WebAuthn.AddVirtualAuthenticatorRequest = {
      options: defaultOptions
    };

    // Implementation would send WebAuthn.addVirtualAuthenticator
    const response: Protocol.WebAuthn.AddVirtualAuthenticatorResponse = {
      authenticatorId: `auth_${Date.now()}`
    };

    this.authenticators.set(name, response.authenticatorId);
    return response.authenticatorId;
  }

  async addTestCredential(authenticatorName: string, credential: Partial<Protocol.WebAuthn.Credential>): Promise<void> {
    const authenticatorId = this.authenticators.get(authenticatorName);
    if (!authenticatorId) {
      throw new Error(`Authenticator ${authenticatorName} not found`);
    }

    const fullCredential: Protocol.WebAuthn.Credential = {
      credentialId: credential.credentialId || this.generateCredentialId(),
      isResidentCredential: credential.isResidentCredential ?? true,
      rpId: credential.rpId || 'example.com',
      privateKey: credential.privateKey || this.generatePrivateKey(),
      userHandle: credential.userHandle || this.generateUserHandle(),
      signCount: credential.signCount ?? 0,
      backupEligibility: credential.backupEligibility ?? false,
      backupState: credential.backupState ?? false
    };

    const request: Protocol.WebAuthn.AddCredentialRequest = {
      authenticatorId,
      credential: fullCredential
    };

    // Implementation would send WebAuthn.addCredential
  }

  async testAuthenticationFlow(authenticatorName: string, rpId: string): Promise<AuthenticationTestResult> {
    const authenticatorId = this.authenticators.get(authenticatorName);
    if (!authenticatorId) {
      throw new Error(`Authenticator ${authenticatorName} not found`);
    }

    // Get all credentials for this authenticator
    const credentialsRequest: Protocol.WebAuthn.GetCredentialsRequest = {
      authenticatorId
    };

    // Implementation would get credentials and test authentication
    const result: AuthenticationTestResult = {
      success: true,
      credentialCount: 1,
      authenticatorResponse: "mock_response",
      errors: []
    };

    return result;
  }

  setupWebAuthnListeners(): void {
    this.onCredentialAdded = (event: Protocol.WebAuthn.CredentialAddedEvent) => {
      console.log(`Credential added to authenticator ${event.authenticatorId}:`, event.credential.credentialId);
    };

    this.onCredentialAsserted = (event: Protocol.WebAuthn.CredentialAssertedEvent) => {
      console.log(`Credential asserted on authenticator ${event.authenticatorId}:`, event.credential.credentialId);
    };
  }

  async removeVirtualAuthenticator(name: string): Promise<void> {
    const authenticatorId = this.authenticators.get(name);
    if (!authenticatorId) return;

    const request: Protocol.WebAuthn.RemoveVirtualAuthenticatorRequest = {
      authenticatorId
    };

    // Implementation would send WebAuthn.removeVirtualAuthenticator
    this.authenticators.delete(name);
  }

  private generateCredentialId(): string {
    return btoa(String.fromCharCode(...Array.from(crypto.getRandomValues(new Uint8Array(32)))));
  }

  private generatePrivateKey(): string {
    return btoa(String.fromCharCode(...Array.from(crypto.getRandomValues(new Uint8Array(32)))));
  }

  private generateUserHandle(): string {
    return btoa(String.fromCharCode(...Array.from(crypto.getRandomValues(new Uint8Array(16)))));
  }
}

interface AuthenticationTestResult {
  success: boolean;
  credentialCount: number;
  authenticatorResponse: string;
  errors: string[];
}

ServiceWorker Domain

Service worker debugging and management for testing offline functionality and background processing.

namespace Protocol.ServiceWorker {
  type RegistrationID = string;
  type ServiceWorkerVersionRunningStatus = ('stopped' | 'starting' | 'running' | 'stopping');
  type ServiceWorkerVersionStatus = ('new' | 'installing' | 'installed' | 'activating' | 'activated' | 'redundant');

  interface ServiceWorkerRegistration {
    registrationId: RegistrationID;
    scopeURL: string;
    isDeleted: boolean;
  }

  interface ServiceWorkerVersion {
    versionId: string;
    registrationId: RegistrationID;
    scriptURL: string;
    runningStatus: ServiceWorkerVersionRunningStatus;
    status: ServiceWorkerVersionStatus;
    scriptLastModified?: number;
    scriptResponseTime?: number;
    controlledClients?: Target.TargetID[];
    targetId?: Target.TargetID;
    routerRules?: string;
  }

  interface ServiceWorkerErrorMessage {
    errorMessage: string;
    registrationId: RegistrationID;
    versionId: string;
    sourceURL: string;
    lineNumber: integer;
    columnNumber: integer;
  }

  interface GetVersionsResponse {
    versions: ServiceWorkerVersion[];
  }

  interface StartWorkerRequest {
    scopeURL: string;
  }

  interface StopWorkerRequest {
    versionId: string;
  }

  interface InspectWorkerRequest {
    versionId: string;
  }

  interface SetForceUpdateOnPageLoadRequest {
    forceUpdateOnPageLoad: boolean;
  }

  interface DeliverPushMessageRequest {
    origin: string;
    registrationId: RegistrationID;
    data: string;
  }

  interface DispatchSyncEventRequest {
    origin: string;
    registrationId: RegistrationID;
    tag: string;
    lastChance: boolean;
  }

  interface DispatchPeriodicSyncEventRequest {
    origin: string;
    registrationId: RegistrationID;
    tag: string;
  }

  interface SkipWaitingRequest {
    scopeURL: string;
  }

  interface UnregisterRequest {
    scopeURL: string;
  }

  interface UpdateRegistrationRequest {
    scopeURL: string;
  }

  interface WorkerErrorReportedEvent {
    errorMessage: ServiceWorkerErrorMessage;
  }

  interface WorkerRegistrationUpdatedEvent {
    registrations: ServiceWorkerRegistration[];
  }

  interface WorkerVersionUpdatedEvent {
    versions: ServiceWorkerVersion[];
  }
}

Usage Example:

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

class ServiceWorkerTester {
  private registrations: Map<string, Protocol.ServiceWorker.ServiceWorkerRegistration> = new Map();
  private versions: Map<string, Protocol.ServiceWorker.ServiceWorkerVersion> = new Map();

  async getAllServiceWorkers(): Promise<ServiceWorkerAnalysis> {
    // Get all service worker versions
    const versionsResponse = await this.getVersions();
    
    const analysis: ServiceWorkerAnalysis = {
      totalRegistrations: 0,
      totalVersions: versionsResponse.versions.length,
      activeWorkers: [],
      inactiveWorkers: [],
      erroredWorkers: []
    };

    versionsResponse.versions.forEach(version => {
      this.versions.set(version.versionId, version);
      
      if (version.runningStatus === 'running' && version.status === 'activated') {
        analysis.activeWorkers.push(version);
      } else if (version.status === 'redundant') {
        analysis.inactiveWorkers.push(version);
      }
    });

    // Group by registrations
    const registrationMap = new Map<string, Protocol.ServiceWorker.ServiceWorkerVersion[]>();
    versionsResponse.versions.forEach(version => {
      if (!registrationMap.has(version.registrationId)) {
        registrationMap.set(version.registrationId, []);
      }
      registrationMap.get(version.registrationId)!.push(version);
    });

    analysis.totalRegistrations = registrationMap.size;

    return analysis;
  }

  private async getVersions(): Promise<Protocol.ServiceWorker.GetVersionsResponse> {
    // Implementation would send ServiceWorker.getVersions
    return {
      versions: [
        {
          versionId: "version_1",
          registrationId: "reg_1",
          scriptURL: "https://example.com/sw.js",
          runningStatus: 'running',
          status: 'activated',
          controlledClients: ["client_1", "client_2"]
        }
      ]
    };
  }

  async testServiceWorkerLifecycle(scopeURL: string): Promise<LifecycleTestResult> {
    const result: LifecycleTestResult = {
      registrationSucceeded: false,
      installationSucceeded: false,
      activationSucceeded: false,
      errors: []
    };

    try {
      // Start the worker
      const startRequest: Protocol.ServiceWorker.StartWorkerRequest = { scopeURL };
      // Implementation would send ServiceWorker.startWorker
      result.registrationSucceeded = true;

      // Wait for installation and activation events
      await this.waitForWorkerStates(scopeURL, ['installed', 'activated']);
      result.installationSucceeded = true;
      result.activationSucceeded = true;

    } catch (error) {
      result.errors.push(error.message);
    }

    return result;
  }

  async testPushMessage(origin: string, registrationId: string, data: any): Promise<PushTestResult> {
    const request: Protocol.ServiceWorker.DeliverPushMessageRequest = {
      origin,
      registrationId,
      data: JSON.stringify(data)
    };

    try {
      // Implementation would send ServiceWorker.deliverPushMessage
      return {
        success: true,
        delivered: true,
        response: "Push message delivered successfully"
      };
    } catch (error) {
      return {
        success: false,
        delivered: false,
        error: error.message
      };
    }
  }

  async testSyncEvent(origin: string, registrationId: string, tag: string): Promise<SyncTestResult> {
    const request: Protocol.ServiceWorker.DispatchSyncEventRequest = {
      origin,
      registrationId,
      tag,
      lastChance: false
    };

    try {
      // Implementation would send ServiceWorker.dispatchSyncEvent
      return {
        success: true,
        executed: true,
        tag
      };
    } catch (error) {
      return {
        success: false,
        executed: false,
        tag,
        error: error.message
      };
    }
  }

  async updateServiceWorker(scopeURL: string): Promise<void> {
    const request: Protocol.ServiceWorker.UpdateRegistrationRequest = { scopeURL };
    // Implementation would send ServiceWorker.updateRegistration
  }

  async unregisterServiceWorker(scopeURL: string): Promise<void> {
    const request: Protocol.ServiceWorker.UnregisterRequest = { scopeURL };
    // Implementation would send ServiceWorker.unregister
  }

  setupServiceWorkerListeners(): void {
    this.onWorkerErrorReported = (event: Protocol.ServiceWorker.WorkerErrorReportedEvent) => {
      console.error(`Service Worker error in ${event.errorMessage.sourceURL}:${event.errorMessage.lineNumber}: ${event.errorMessage.errorMessage}`);
    };

    this.onWorkerRegistrationUpdated = (event: Protocol.ServiceWorker.WorkerRegistrationUpdatedEvent) => {
      console.log(`Service Worker registrations updated: ${event.registrations.length} registrations`);
      event.registrations.forEach(reg => {
        this.registrations.set(reg.scopeURL, reg);
      });
    };

    this.onWorkerVersionUpdated = (event: Protocol.ServiceWorker.WorkerVersionUpdatedEvent) => {
      console.log(`Service Worker versions updated: ${event.versions.length} versions`);
      event.versions.forEach(version => {
        this.versions.set(version.versionId, version);
      });
    };
  }

  private async waitForWorkerStates(scopeURL: string, targetStates: Protocol.ServiceWorker.ServiceWorkerVersionStatus[]): Promise<void> {
    // Implementation would wait for specific worker states
    return new Promise((resolve, reject) => {
      const timeout = setTimeout(() => {
        reject(new Error(`Timeout waiting for worker states: ${targetStates.join(', ')}`));
      }, 10000);

      // Monitor version updates and resolve when target states are reached
      const checkStates = () => {
        const relevantVersions = Array.from(this.versions.values())
          .filter(v => v.scriptURL.includes(scopeURL));
        
        const hasTargetStates = targetStates.every(state => 
          relevantVersions.some(v => v.status === state)
        );

        if (hasTargetStates) {
          clearTimeout(timeout);
          resolve();
        }
      };

      // In a real implementation, this would listen to worker events
      setTimeout(checkStates, 1000);
    });
  }
}

interface ServiceWorkerAnalysis {
  totalRegistrations: number;
  totalVersions: number;
  activeWorkers: Protocol.ServiceWorker.ServiceWorkerVersion[];
  inactiveWorkers: Protocol.ServiceWorker.ServiceWorkerVersion[];
  erroredWorkers: Protocol.ServiceWorker.ServiceWorkerVersion[];
}

interface LifecycleTestResult {
  registrationSucceeded: boolean;
  installationSucceeded: boolean;
  activationSucceeded: boolean;
  errors: string[];
}

interface PushTestResult {
  success: boolean;
  delivered: boolean;
  response?: string;
  error?: string;
}

interface SyncTestResult {
  success: boolean;
  executed: boolean;
  tag: string;
  error?: string;
}

PWA Domain

Progressive Web App features and debugging for testing app-like functionality.

namespace Protocol.PWA {
  interface FileHandler {
    action: string;
    accepts: FileHandlerAccept[];
  }

  interface FileHandlerAccept {
    mediaType: string;
    fileExtensions: string[];
  }

  interface DisplayMode = ('fullscreen' | 'standalone' | 'minimal-ui' | 'browser');

  interface GetOsAppStateRequest {
    manifestId: string;
  }

  interface GetOsAppStateResponse {
    badgeCount: integer;
    fileHandlers: FileHandler[];
  }

  interface InstallRequest {
    manifestId: string;
    installUrlOrBundleUrl?: string;
  }

  interface UninstallRequest {
    manifestId: string;
  }

  interface LaunchFilesInAppRequest {
    manifestId: string;
    files: string[];
  }

  interface OpenCurrentPageInAppRequest {
    manifestId: string;
  }

  interface ChangeAppUserSettingsRequest {
    manifestId: string;
    linkCapturing?: boolean;
    displayMode?: DisplayMode;
  }
}

Media Domain

Media element debugging and inspection for analyzing audio/video playback.

namespace Protocol.Media {
  type PlayerId = string;
  type Timestamp = number;

  interface PlayerMessage {
    level: ('error' | 'warning' | 'info' | 'debug');
    message: string;
  }

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

  interface PlayerEvent {
    timestamp: Timestamp;
    value: string;
  }

  interface PlayerErrorSourceLocation {
    file: string;
    line: integer;
  }

  interface PlayerError {
    errorType: string;
    code: integer;
    stack: PlayerErrorSourceLocation[];
    cause: PlayerError[];
    data: Record<string, any>;
  }

  interface PlayersCreatedEvent {
    players: PlayerId[];
  }

  interface PlayerPropertiesChangedEvent {
    playerId: PlayerId;
    properties: PlayerProperty[];
  }

  interface PlayerEventsAddedEvent {
    playerId: PlayerId;
    events: PlayerEvent[];
  }

  interface PlayerMessagesLoggedEvent {
    playerId: PlayerId;
    messages: PlayerMessage[];
  }

  interface PlayerErrorsRaisedEvent {
    playerId: PlayerId;
    errors: PlayerError[];
  }
}

WebAudio Domain

Web Audio API debugging for analyzing audio context and node graphs.

namespace Protocol.WebAudio {
  type GraphObjectId = string;
  type ContextType = ('realtime' | 'offline');
  type ContextState = ('suspended' | 'running' | 'closed');
  type NodeType = string;
  type ChannelCountMode = ('max' | 'clamped-max' | 'explicit');
  type ChannelInterpretation = ('speakers' | 'discrete');

  interface BaseAudioContext {
    contextId: GraphObjectId;
    contextType: ContextType;
    contextState: ContextState;
    realtimeData?: ContextRealtimeData;
    callbackBufferSize?: number;
    maxOutputChannelCount?: number;
    sampleRate?: number;
  }

  interface AudioListener {
    listenerId: GraphObjectId;
    contextId: GraphObjectId;
  }

  interface AudioNode {
    nodeId: GraphObjectId;
    contextId: GraphObjectId;
    nodeType: NodeType;
    numberOfInputs: number;
    numberOfOutputs: number;
    channelCount?: number;
    channelCountMode?: ChannelCountMode;
    channelInterpretation?: ChannelInterpretation;
  }

  interface AudioParam {
    paramId: GraphObjectId;
    nodeId: GraphObjectId;
    contextId: GraphObjectId;
    paramType: string;
    rate: ('a-rate' | 'k-rate');
    defaultValue: number;
    minValue: number;
    maxValue: number;
  }

  interface ContextRealtimeData {
    currentTime: number;
    renderCapacity: number;
    callbackIntervalMean: number;
    callbackIntervalVariance: number;
  }

  interface GetRealtimeDataRequest {
    contextId: GraphObjectId;
  }

  interface GetRealtimeDataResponse {
    realtimeData: ContextRealtimeData;
  }

  interface ContextCreatedEvent {
    context: BaseAudioContext;
  }

  interface ContextWillBeDestroyedEvent {
    contextId: GraphObjectId;
  }

  interface ContextChangedEvent {
    context: BaseAudioContext;
  }

  interface AudioListenerCreatedEvent {
    listener: AudioListener;
  }

  interface AudioListenerWillBeDestroyedEvent {
    contextId: GraphObjectId;
    listenerId: GraphObjectId;
  }

  interface AudioNodeCreatedEvent {
    node: AudioNode;
  }

  interface AudioNodeWillBeDestroyedEvent {
    contextId: GraphObjectId;
    nodeId: GraphObjectId;
  }

  interface AudioParamCreatedEvent {
    param: AudioParam;
  }

  interface AudioParamWillBeDestroyedEvent {
    contextId: GraphObjectId;
    nodeId: GraphObjectId;
    paramId: GraphObjectId;
  }

  interface NodesConnectedEvent {
    contextId: GraphObjectId;
    sourceId: GraphObjectId;
    destinationId: GraphObjectId;
    sourceOutputIndex?: number;
    destinationInputIndex?: number;
  }

  interface NodesDisconnectedEvent {
    contextId: GraphObjectId;
    sourceId: GraphObjectId;
    destinationId: GraphObjectId;
    sourceOutputIndex?: number;
    destinationInputIndex?: number;
  }

  interface NodeParamConnectedEvent {
    contextId: GraphObjectId;
    sourceId: GraphObjectId;
    destinationId: GraphObjectId;
    sourceOutputIndex?: number;
  }

  interface NodeParamDisconnectedEvent {
    contextId: GraphObjectId;
    sourceId: GraphObjectId;
    destinationId: GraphObjectId;
    sourceOutputIndex?: number;
  }
}

Common Usage Patterns

Comprehensive PWA Testing

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

class PWATester {
  private webAuthnTester: WebAuthnTester;
  private serviceWorkerTester: ServiceWorkerTester;

  constructor() {
    this.webAuthnTester = new WebAuthnTester();
    this.serviceWorkerTester = new ServiceWorkerTester();
  }

  async performCompletePWATest(manifestId: string, origin: string): Promise<PWATestResults> {
    const results: PWATestResults = {
      manifestId,
      origin,
      serviceWorkerTest: null,
      webAuthnTest: null,
      offlineTest: null,
      installabilityTest: null,
      pushNotificationTest: null,
      overallScore: 0,
      recommendations: []
    };

    try {
      // Test Service Worker functionality
      results.serviceWorkerTest = await this.testServiceWorkerFeatures(origin);
      
      // Test WebAuthn if available
      results.webAuthnTest = await this.testWebAuthnFeatures();
      
      // Test offline functionality
      results.offlineTest = await this.testOfflineFunctionality(origin);
      
      // Test PWA installability
      results.installabilityTest = await this.testInstallability(manifestId);
      
      // Test push notifications
      results.pushNotificationTest = await this.testPushNotifications(origin);
      
      results.overallScore = this.calculateOverallScore(results);
      results.recommendations = this.generateRecommendations(results);
      
    } catch (error) {
      console.error("PWA testing failed:", error);
      results.recommendations.push("PWA testing encountered errors - check console for details");
    }

    return results;
  }

  private async testServiceWorkerFeatures(origin: string): Promise<ServiceWorkerTestResults> {
    const analysis = await this.serviceWorkerTester.getAllServiceWorkers();
    
    // Test service worker lifecycle
    const lifecycleResults = await this.serviceWorkerTester.testServiceWorkerLifecycle(`${origin}/sw.js`);
    
    return {
      hasServiceWorker: analysis.activeWorkers.length > 0,
      isActive: analysis.activeWorkers.length > 0,
      lifecycleTest: lifecycleResults,
      controlledClients: analysis.activeWorkers.reduce((sum, sw) => sum + (sw.controlledClients?.length || 0), 0)
    };
  }

  private async testWebAuthnFeatures(): Promise<WebAuthnTestResults> {
    try {
      // Setup virtual authenticator for testing
      const authenticatorId = await this.webAuthnTester.setupVirtualAuthenticator('test-auth', {
        protocol: 'ctap2',
        transport: 'internal',
        hasUserVerification: true
      });
      
      // Add test credential
      await this.webAuthnTester.addTestCredential('test-auth', {
        rpId: 'example.com',
        isResidentCredential: true
      });
      
      // Test authentication flow
      const authResult = await this.webAuthnTester.testAuthenticationFlow('test-auth', 'example.com');
      
      // Cleanup
      await this.webAuthnTester.removeVirtualAuthenticator('test-auth');
      
      return {
        supported: true,
        authenticationWorking: authResult.success,
        credentialCount: authResult.credentialCount,
        errors: authResult.errors
      };
    } catch (error) {
      return {
        supported: false,
        authenticationWorking: false,
        credentialCount: 0,
        errors: [error.message]
      };
    }
  }

  private async testOfflineFunctionality(origin: string): Promise<OfflineTestResults> {
    // This would involve testing cache strategies and offline page functionality
    return {
      cacheStrategyWorks: true,
      offlinePageExists: true,
      criticalResourcesCached: true,
      offlineNavigation: true
    };
  }

  private async testInstallability(manifestId: string): Promise<InstallabilityTestResults> {
    try {
      // Get PWA state
      const stateRequest: Protocol.PWA.GetOsAppStateRequest = { manifestId };
      // Implementation would check PWA state
      
      return {
        isInstallable: true,
        hasValidManifest: true,
        hasServiceWorker: true,
        httpsRequired: true,
        meets256IconRequirement: true
      };
    } catch (error) {
      return {
        isInstallable: false,
        hasValidManifest: false,
        hasServiceWorker: false,
        httpsRequired: false,
        meets256IconRequirement: false,
        error: error.message
      };
    }
  }

  private async testPushNotifications(origin: string): Promise<PushTestResults> {
    const serviceWorkers = await this.serviceWorkerTester.getAllServiceWorkers();
    
    if (serviceWorkers.activeWorkers.length === 0) {
      return {
        supported: false,
        subscriptionWorks: false,
        messageDelivery: false,
        error: "No active service worker found"
      };
    }
    
    const activeWorker = serviceWorkers.activeWorkers[0];
    
    try {
      const pushResult = await this.serviceWorkerTester.testPushMessage(
        origin,
        activeWorker.registrationId,
        { title: "Test Push", body: "This is a test push notification" }
      );
      
      return {
        supported: true,
        subscriptionWorks: true,
        messageDelivery: pushResult.success,
        error: pushResult.error
      };
    } catch (error) {
      return {
        supported: false,
        subscriptionWorks: false,
        messageDelivery: false,
        error: error.message
      };
    }
  }

  private calculateOverallScore(results: PWATestResults): number {
    let score = 0;
    let maxScore = 0;

    // Service Worker (20 points)
    maxScore += 20;
    if (results.serviceWorkerTest?.hasServiceWorker) score += 20;

    // WebAuthn (10 points)
    maxScore += 10;
    if (results.webAuthnTest?.supported) score += 10;

    // Offline functionality (25 points)
    maxScore += 25;
    if (results.offlineTest?.cacheStrategyWorks) score += 10;
    if (results.offlineTest?.offlinePageExists) score += 10;
    if (results.offlineTest?.criticalResourcesCached) score += 5;

    // Installability (25 points)
    maxScore += 25;
    if (results.installabilityTest?.isInstallable) score += 25;

    // Push notifications (20 points)
    maxScore += 20;
    if (results.pushNotificationTest?.messageDelivery) score += 20;

    return Math.round((score / maxScore) * 100);
  }

  private generateRecommendations(results: PWATestResults): string[] {
    const recommendations: string[] = [];

    if (!results.serviceWorkerTest?.hasServiceWorker) {
      recommendations.push("Add a Service Worker to enable offline functionality and push notifications");
    }

    if (!results.webAuthnTest?.supported) {
      recommendations.push("Consider implementing WebAuthn for passwordless authentication");
    }

    if (!results.offlineTest?.offlinePageExists) {
      recommendations.push("Create an offline page to improve user experience when network is unavailable");
    }

    if (!results.installabilityTest?.isInstallable) {
      recommendations.push("Ensure your PWA meets installability requirements");
    }

    if (!results.pushNotificationTest?.supported) {
      recommendations.push("Implement push notifications to re-engage users");
    }

    return recommendations;
  }
}

interface PWATestResults {
  manifestId: string;
  origin: string;
  serviceWorkerTest: ServiceWorkerTestResults | null;
  webAuthnTest: WebAuthnTestResults | null;
  offlineTest: OfflineTestResults | null;
  installabilityTest: InstallabilityTestResults | null;
  pushNotificationTest: PushTestResults | null;
  overallScore: number;
  recommendations: string[];
}

interface ServiceWorkerTestResults {
  hasServiceWorker: boolean;
  isActive: boolean;
  lifecycleTest: LifecycleTestResult;
  controlledClients: number;
}

interface WebAuthnTestResults {
  supported: boolean;
  authenticationWorking: boolean;
  credentialCount: number;
  errors: string[];
}

interface OfflineTestResults {
  cacheStrategyWorks: boolean;
  offlinePageExists: boolean;
  criticalResourcesCached: boolean;
  offlineNavigation: boolean;
}

interface InstallabilityTestResults {
  isInstallable: boolean;
  hasValidManifest: boolean;
  hasServiceWorker: boolean;
  httpsRequired: boolean;
  meets256IconRequirement: boolean;
  error?: string;
}

interface PushTestResults {
  supported: boolean;
  subscriptionWorks: boolean;
  messageDelivery: boolean;
  error?: string;
}

Web Audio Analysis

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

class WebAudioAnalyzer {
  private contexts: Map<string, Protocol.WebAudio.BaseAudioContext> = new Map();
  private nodes: Map<string, Protocol.WebAudio.AudioNode> = new Map();
  private connections: Array<{ source: string; destination: string }> = [];

  setupWebAudioListeners(): void {
    this.onContextCreated = (event: Protocol.WebAudio.ContextCreatedEvent) => {
      console.log(`Audio context created: ${event.context.contextId} (${event.context.contextType})`);
      this.contexts.set(event.context.contextId, event.context);
    };

    this.onAudioNodeCreated = (event: Protocol.WebAudio.AudioNodeCreatedEvent) => {
      console.log(`Audio node created: ${event.node.nodeType} (${event.node.nodeId})`);
      this.nodes.set(event.node.nodeId, event.node);
    };

    this.onNodesConnected = (event: Protocol.WebAudio.NodesConnectedEvent) => {
      console.log(`Nodes connected: ${event.sourceId} -> ${event.destinationId}`);
      this.connections.push({ source: event.sourceId, destination: event.destinationId });
    };
  }

  async analyzeAudioGraph(): Promise<AudioGraphAnalysis> {
    const analysis: AudioGraphAnalysis = {
      contexts: Array.from(this.contexts.values()),
      nodes: Array.from(this.nodes.values()),
      connections: this.connections,
      performance: []
    };

    // Analyze performance for realtime contexts
    for (const context of analysis.contexts) {
      if (context.contextType === 'realtime') {
        const performanceData = await this.getRealtimeData(context.contextId);
        analysis.performance.push(performanceData);
      }
    }

    return analysis;
  }

  private async getRealtimeData(contextId: string): Promise<Protocol.WebAudio.ContextRealtimeData> {
    const request: Protocol.WebAudio.GetRealtimeDataRequest = { contextId };
    
    // Implementation would send WebAudio.getRealtimeData
    return {
      currentTime: 10.5,
      renderCapacity: 0.15,
      callbackIntervalMean: 0.0023,
      callbackIntervalVariance: 0.0001
    };
  }

  generateAudioGraphVisualization(): AudioGraphVisualization {
    const nodesByContext = new Map<string, Protocol.WebAudio.AudioNode[]>();
    
    // Group nodes by context
    for (const node of this.nodes.values()) {
      if (!nodesByContext.has(node.contextId)) {
        nodesByContext.set(node.contextId, []);
      }
      nodesByContext.get(node.contextId)!.push(node);
    }

    const visualization: AudioGraphVisualization = {
      contexts: [],
      issues: []
    };

    // Create visualization for each context
    for (const [contextId, nodes] of nodesByContext.entries()) {
      const context = this.contexts.get(contextId);
      if (!context) continue;

      const contextViz: ContextVisualization = {
        contextId,
        contextType: context.contextType,
        nodes: nodes.map(node => ({
          id: node.nodeId,
          type: node.nodeType,
          inputs: node.numberOfInputs,
          outputs: node.numberOfOutputs
        })),
        connections: this.connections.filter(conn => 
          nodes.some(n => n.nodeId === conn.source) && 
          nodes.some(n => n.nodeId === conn.destination)
        )
      };

      visualization.contexts.push(contextViz);

      // Check for potential issues
      if (nodes.length > 50) {
        visualization.issues.push(`Context ${contextId} has ${nodes.length} nodes - consider optimization`);
      }
    }

    return visualization;
  }
}

interface AudioGraphAnalysis {
  contexts: Protocol.WebAudio.BaseAudioContext[];
  nodes: Protocol.WebAudio.AudioNode[];
  connections: Array<{ source: string; destination: string }>;
  performance: Protocol.WebAudio.ContextRealtimeData[];
}

interface AudioGraphVisualization {
  contexts: ContextVisualization[];
  issues: string[];
}

interface ContextVisualization {
  contextId: string;
  contextType: Protocol.WebAudio.ContextType;
  nodes: Array<{
    id: string;
    type: string;
    inputs: number;
    outputs: number;
  }>;
  connections: Array<{ source: string; destination: string }>;
}