Skip to main content
Glama
prediction-engine.ts13.8 kB
import { Prediction, FailureAnalysis, TestCaseComparison, FrameAnalysis } from './types.js'; /** * PredictionEngine class * Analyzes evidence and predicts if failure is bug or test issue */ export class PredictionEngine { constructor(private debug: boolean = false) {} /** * Predict issue type based on all available evidence */ predictIssueType( failureAnalysis: FailureAnalysis, testCaseComparison: TestCaseComparison | null, frames: FrameAnalysis[], logs: string ): Prediction { if (this.debug) { console.log('[PredictionEngine] Analyzing evidence to predict issue type'); } const evidenceForBug: string[] = []; const evidenceForTestUpdate: string[] = []; let bugScore = 0; let testScore = 0; // Analyze failure type const failureType = failureAnalysis.failureType.toLowerCase(); const errorMessage = failureAnalysis.errorMessage.toLowerCase(); // Bug indicators if (failureType.includes('crash') || failureType.includes('anr') || failureType.includes('freeze')) { evidenceForBug.push('Application crashed or became unresponsive'); bugScore += 30; } if (errorMessage.includes('nullpointer') || errorMessage.includes('null reference')) { evidenceForBug.push('NullPointerException indicates code defect'); bugScore += 25; } if (errorMessage.includes('index out of bounds') || errorMessage.includes('array')) { evidenceForBug.push('Array/Index error suggests code issue'); bugScore += 20; } if (errorMessage.includes('network') || errorMessage.includes('connection') || errorMessage.includes('timeout')) { evidenceForBug.push('Network/connection issue detected'); bugScore += 15; } if (errorMessage.includes('database') || errorMessage.includes('sql')) { evidenceForBug.push('Database error detected'); bugScore += 15; } // Test issue indicators if (failureType.includes('elementnotfound') || failureType.includes('nosuchelement')) { evidenceForTestUpdate.push('Element not found - UI may have changed'); testScore += 25; } if (failureType.includes('timeout') || failureType.includes('wait')) { evidenceForTestUpdate.push('Wait timeout - may need increased wait time'); testScore += 20; } if (failureType.includes('assertion') || failureType.includes('expected')) { evidenceForTestUpdate.push('Assertion failure - expected vs actual mismatch'); testScore += 15; } if (errorMessage.includes('stale element') || errorMessage.includes('detached')) { evidenceForTestUpdate.push('Stale element reference - test needs to re-locate element'); testScore += 20; } // Analyze test case comparison if (testCaseComparison) { const coverage = testCaseComparison.coverageAnalysis.coveragePercentage; if (coverage < 50) { evidenceForTestUpdate.push(`Low test case coverage (${coverage}%) - test may be outdated`); testScore += 20; } if (testCaseComparison.coverageAnalysis.skippedSteps.length > 0) { evidenceForTestUpdate.push(`${testCaseComparison.coverageAnalysis.skippedSteps.length} test case steps were skipped`); testScore += 10; } if (testCaseComparison.coverageAnalysis.extraSteps.length > 3) { evidenceForTestUpdate.push(`Test executed ${testCaseComparison.coverageAnalysis.extraSteps.length} extra steps not in test case`); testScore += 15; } // Check for deviations const deviations = testCaseComparison.stepByStepComparison.filter(s => !s.match); if (deviations.length > 0) { evidenceForTestUpdate.push(`${deviations.length} steps deviated from test case`); testScore += 10; } } // Analyze visual evidence from frames const failureFrames = frames.filter(f => f.anomaliesDetected && f.anomaliesDetected.length > 0 ); if (failureFrames.length > 0) { for (const frame of failureFrames) { for (const anomaly of frame.anomaliesDetected) { const anomalyLower = anomaly.toLowerCase(); if (anomalyLower.includes('error dialog') || anomalyLower.includes('crash screen')) { evidenceForBug.push(`Visual anomaly detected: ${anomaly}`); bugScore += 20; } if (anomalyLower.includes('wrong screen') || anomalyLower.includes('unexpected')) { evidenceForTestUpdate.push(`Unexpected UI state: ${anomaly}`); testScore += 15; } } } } // Analyze root cause from failure analysis if (failureAnalysis.rootCause.category === 'app_bug') { evidenceForBug.push(failureAnalysis.rootCause.reasoning); bugScore += failureAnalysis.rootCause.confidence * 0.3; } else if (failureAnalysis.rootCause.category === 'test_issue') { evidenceForTestUpdate.push(failureAnalysis.rootCause.reasoning); testScore += failureAnalysis.rootCause.confidence * 0.3; } // Make prediction const totalScore = bugScore + testScore; const verdict = this.determineVerdict(bugScore, testScore, failureAnalysis); const confidence = totalScore > 0 ? Math.min(100, Math.round((Math.max(bugScore, testScore) / totalScore) * 100)) : 50; // Generate reasoning const reasoning = this.generateReasoning(verdict, bugScore, testScore, failureAnalysis); // Generate recommendations const recommendations = this.generateRecommendations( verdict, failureAnalysis, testCaseComparison, evidenceForBug, evidenceForTestUpdate ); if (this.debug) { console.log(`[PredictionEngine] Prediction: ${verdict} (${confidence}% confidence)`); console.log(`[PredictionEngine] Bug score: ${bugScore}, Test score: ${testScore}`); } return { verdict, confidence, reasoning, evidenceForBug, evidenceForTestUpdate, recommendations }; } /** * Determine final verdict based on scores */ private determineVerdict( bugScore: number, testScore: number, failureAnalysis: FailureAnalysis ): 'bug' | 'test_needs_update' | 'infrastructure_issue' | 'data_issue' | 'unclear' { const errorMessage = failureAnalysis.errorMessage.toLowerCase(); const failureType = failureAnalysis.failureType.toLowerCase(); // Infrastructure indicators if (errorMessage.includes('connection refused') || errorMessage.includes('server error') || failureType.includes('infrastructure')) { return 'infrastructure_issue'; } // Data issue indicators if (errorMessage.includes('invalid data') || errorMessage.includes('data not found') || errorMessage.includes('constraint violation')) { return 'data_issue'; } // Compare scores const diff = Math.abs(bugScore - testScore); if (diff < 10) { return 'unclear'; // Scores are too close } if (bugScore > testScore) { return bugScore > 30 ? 'bug' : 'unclear'; } else { return testScore > 30 ? 'test_needs_update' : 'unclear'; } } /** * Generate human-readable reasoning */ private generateReasoning( verdict: string, bugScore: number, testScore: number, failureAnalysis: FailureAnalysis ): string { const parts: string[] = []; parts.push(`Based on analysis of the failure evidence (bug score: ${Math.round(bugScore)}, test score: ${Math.round(testScore)}), `); switch (verdict) { case 'bug': parts.push('this appears to be an **application bug**. '); parts.push('The failure characteristics, error messages, and visual evidence point to a code defect rather than test automation issues.'); break; case 'test_needs_update': parts.push('this appears to be a **test automation issue**. '); parts.push('The test likely needs updates to match current application behavior or improved element locators/waits.'); break; case 'infrastructure_issue': parts.push('this appears to be an **infrastructure/environment issue**. '); parts.push('The failure is likely due to environment instability, network issues, or resource constraints.'); break; case 'data_issue': parts.push('this appears to be a **data-related issue**. '); parts.push('The test may have encountered invalid, missing, or conflicting data.'); break; case 'unclear': parts.push('the root cause is **unclear**. '); parts.push('Further investigation is needed as evidence points to multiple possible causes.'); break; } if (failureAnalysis.rootCause.reasoning) { parts.push(`\n\nAdditional context: ${failureAnalysis.rootCause.reasoning}`); } return parts.join(''); } /** * Generate actionable recommendations */ private generateRecommendations( verdict: string, failureAnalysis: FailureAnalysis, testCaseComparison: TestCaseComparison | null, evidenceForBug: string[], evidenceForTestUpdate: string[] ): Array<{ type: 'bug_report' | 'test_update' | 'infrastructure_fix' | 'investigation'; priority: 'high' | 'medium' | 'low'; description: string; actionItems: string[]; }> { const recommendations: Array<any> = []; switch (verdict) { case 'bug': recommendations.push({ type: 'bug_report', priority: 'high', description: 'Create bug report with evidence', actionItems: [ `Report to development team: ${failureAnalysis.errorMessage}`, 'Attach video and failure screenshots', 'Include stack trace and error logs', 'Specify environment and device details', 'Document steps to reproduce from test case' ] }); if (testCaseComparison && testCaseComparison.coverageAnalysis.coveragePercentage < 100) { recommendations.push({ type: 'test_update', priority: 'low', description: 'Update test case documentation', actionItems: [ 'Verify test case steps match actual execution', 'Update any outdated step descriptions' ] }); } break; case 'test_needs_update': recommendations.push({ type: 'test_update', priority: 'high', description: 'Update test automation', actionItems: [ `Fix element locators for: ${failureAnalysis.failureType}`, 'Add explicit waits or increase timeout values', 'Update test case steps to match current UI flow', 'Consider using more robust locator strategies', 'Re-run test after fixes to verify' ] }); if (evidenceForBug.length > 0) { recommendations.push({ type: 'investigation', priority: 'medium', description: 'Investigate potential app issues', actionItems: [ 'Verify if application behavior has changed intentionally', 'Check with developers if UI changes were planned', 'Review recent application commits' ] }); } break; case 'infrastructure_issue': recommendations.push({ type: 'infrastructure_fix', priority: 'high', description: 'Fix environment/infrastructure', actionItems: [ 'Check network connectivity and stability', 'Verify server/API availability', 'Review resource usage (CPU, memory, disk)', 'Check for external service dependencies', 'Re-run test to confirm if issue is transient' ] }); break; case 'data_issue': recommendations.push({ type: 'test_update', priority: 'high', description: 'Fix test data issues', actionItems: [ 'Verify test data is valid and available', 'Check data dependencies and prerequisites', 'Update test data setup/cleanup procedures', 'Consider using data factories or fixtures' ] }); break; case 'unclear': recommendations.push({ type: 'investigation', priority: 'high', description: 'Investigate root cause', actionItems: [ 'Review full test execution video carefully', 'Analyze complete log file for additional clues', 'Compare with previous successful executions', 'Try to reproduce failure manually', 'Consult with development team if needed' ] }); if (evidenceForBug.length > 0) { recommendations.push({ type: 'bug_report', priority: 'medium', description: 'Consider filing bug report', actionItems: [ 'Document all observed symptoms', 'Gather additional evidence', 'Discuss with team before filing' ] }); } if (evidenceForTestUpdate.length > 0) { recommendations.push({ type: 'test_update', priority: 'medium', description: 'Consider test improvements', actionItems: [ 'Review test implementation for potential issues', 'Update element locators if needed', 'Add better error handling and logging' ] }); } break; } return recommendations; } }

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/maksimsarychau/mcp-zebrunner'

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