Skip to main content
Glama
failure-bundle.ts7.88 kB
/** * Failure Bundle Models * Comprehensive failure bundles for E2E test debugging */ import { ScreenshotData } from './ui-context.js'; import { Platform } from './constants.js'; /** * Maestro flow step result */ export interface FlowStep { /** Step index (0-based) */ index: number; /** Step command (e.g., "tapOn", "assertVisible") */ command: string; /** Step arguments */ args: Record<string, unknown>; /** Step status */ status: 'passed' | 'failed' | 'skipped'; /** Duration in milliseconds */ durationMs: number; /** Error message if failed */ error?: string; /** Screenshot at this step (if captured) */ screenshot?: ScreenshotData; } /** * Maestro flow result */ export interface FlowResult { /** Flow file name */ flowName: string; /** Flow file path */ flowPath: string; /** Overall success */ success: boolean; /** Total steps */ totalSteps: number; /** Passed steps */ passedSteps: number; /** Failed step index (-1 if all passed) */ failedAtStep: number; /** Total duration in milliseconds */ durationMs: number; /** Individual step results */ steps: FlowStep[]; /** Error message if failed */ error?: string; } /** * Log entries captured during test */ export interface CapturedLog { /** Log timestamp */ timestamp: number; /** Log level */ level: 'verbose' | 'debug' | 'info' | 'warn' | 'error'; /** Log tag/source */ tag: string; /** Log message */ message: string; /** Process ID */ pid?: number; } /** * Failure bundle for comprehensive debugging * Contains all context needed to diagnose E2E test failures */ export interface FailureBundle { /** Unique bundle ID */ id: string; /** Creation timestamp */ timestamp: number; /** Target platform */ platform: Platform; /** Device ID */ deviceId: string; /** Flow result */ flowResult: FlowResult; /** Screenshot at failure point */ failureScreenshot?: ScreenshotData; /** Screenshot before failure (if available) */ previousScreenshot?: ScreenshotData; /** Relevant logs around failure time */ logs: CapturedLog[]; /** UI hierarchy at failure (if captured) */ uiHierarchy?: string; /** App package/bundle ID */ appIdentifier?: string; /** Device info */ deviceInfo?: { model: string; osVersion: string; screenSize: { width: number; height: number }; }; /** Analysis suggestions */ suggestions: string[]; } /** * Generate unique bundle ID */ export function generateBundleId(): string { const timestamp = Date.now().toString(36); const random = Math.random().toString(36).substring(2, 8); return `fb-${timestamp}-${random}`; } /** * Analyze failure and generate suggestions */ export function analyzeFailure(bundle: FailureBundle): string[] { const suggestions: string[] = []; const flowResult = bundle.flowResult; if (!flowResult.success && flowResult.failedAtStep >= 0) { const failedStep = flowResult.steps[flowResult.failedAtStep]; if (failedStep) { // Analyze based on command type switch (failedStep.command) { case 'tapOn': case 'tap': suggestions.push( 'Element may not be visible or clickable. Check if the element exists in the UI hierarchy.' ); suggestions.push( 'Consider adding a wait before the tap to ensure the element is rendered.' ); break; case 'assertVisible': case 'assertExists': suggestions.push( 'Expected element not found on screen. Verify the element identifier matches the app.' ); suggestions.push( 'Check if the app navigated to the expected screen before this assertion.' ); break; case 'inputText': case 'enterText': suggestions.push( 'Text input may have failed. Check if the input field is focused and editable.' ); suggestions.push( 'Verify no keyboard overlay is blocking the input field.' ); break; case 'scroll': case 'swipe': suggestions.push( 'Scroll/swipe gesture may not have reached the target. Try adjusting scroll distance.' ); break; case 'launchApp': suggestions.push( 'App launch failed. Check if the app is installed and the bundle ID is correct.' ); break; default: suggestions.push( `Step "${failedStep.command}" failed. Review the error message for details.` ); } // Check error message patterns if (failedStep.error) { const error = failedStep.error.toLowerCase(); if (error.includes('timeout')) { suggestions.push( 'Operation timed out. The app may be slow or unresponsive. Consider increasing timeouts.' ); } if (error.includes('not found') || error.includes('no match')) { suggestions.push( 'Element selector may need adjustment. Check for dynamic IDs or changed element structure.' ); } if (error.includes('crash') || error.includes('terminated')) { suggestions.push( 'App appears to have crashed. Check crash logs for the root cause.' ); } } } } // Analyze logs for additional context const errorLogs = bundle.logs.filter((l) => l.level === 'error'); if (errorLogs.length > 0) { suggestions.push( `Found ${errorLogs.length} error log entries. Review logs for crash or exception details.` ); } // Check for common patterns in logs const logText = bundle.logs.map((l) => l.message).join(' ').toLowerCase(); if (logText.includes('out of memory') || logText.includes('oom') || logText.includes('outofmemory')) { suggestions.push('Memory issue detected. The app may be running out of memory.'); } if (logText.includes('network') || logText.includes('connection')) { suggestions.push('Network-related issues in logs. Check if the test requires network connectivity.'); } if (logText.includes('permission')) { suggestions.push('Permission issue detected. Ensure the app has required permissions granted.'); } return suggestions; } /** * Create failure summary for AI consumption */ export function createFailureSummary(bundle: FailureBundle): string { const lines: string[] = [ `Failure Bundle: ${bundle.id}`, `Flow: ${bundle.flowResult.flowName}`, `Platform: ${bundle.platform}`, `Device: ${bundle.deviceId}`, '', `Steps: ${bundle.flowResult.passedSteps}/${bundle.flowResult.totalSteps} passed`, ]; if (bundle.flowResult.failedAtStep >= 0) { const failedStep = bundle.flowResult.steps[bundle.flowResult.failedAtStep]; if (failedStep) { lines.push(`Failed at step ${failedStep.index + 1}: ${failedStep.command}`); if (failedStep.error) { lines.push(`Error: ${failedStep.error.slice(0, 200)}`); } } } if (bundle.suggestions.length > 0) { lines.push('', 'Suggestions:'); for (const suggestion of bundle.suggestions.slice(0, 3)) { lines.push(` - ${suggestion}`); } } const errorLogs = bundle.logs.filter((l) => l.level === 'error').slice(0, 3); if (errorLogs.length > 0) { lines.push('', 'Recent Errors:'); for (const log of errorLogs) { lines.push(` [${log.tag}] ${log.message.slice(0, 100)}`); } } return lines.join('\n'); } /** * Filter logs around failure time */ export function filterLogsAroundFailure( logs: CapturedLog[], failureTime: number, windowMs: number = 5000 ): CapturedLog[] { const startTime = failureTime - windowMs; const endTime = failureTime + windowMs; return logs.filter((log) => log.timestamp >= startTime && log.timestamp <= endTime); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/abd3lraouf/specter-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server