cqt-agent
Version:
613 lines (554 loc) ⢠19 kB
Markdown
# Test Failure Analyzer
This utility analyzes test failures and provides specific, actionable remediation steps to help users resolve issues quickly and effectively.
## Purpose
Automatically categorize test failures, identify root causes, and provide clear, step-by-step remediation instructions with code examples and scripts.
## Failure Analysis Framework
### 1. Failure Categorization System
```javascript
const FailureCategories = {
SYNTAX_ERROR: {
priority: 'HIGH',
category: 'syntax',
description: 'Code syntax or compilation errors',
fixable: true
},
ENVIRONMENT_ERROR: {
priority: 'HIGH',
category: 'environment',
description: 'Missing dependencies, configuration, or runtime issues',
fixable: true
},
LOGIC_ERROR: {
priority: 'MEDIUM',
category: 'logic',
description: 'Test logic errors or incorrect assertions',
fixable: true
},
DATA_ERROR: {
priority: 'MEDIUM',
category: 'data',
description: 'Test data issues or database problems',
fixable: true
},
TIMING_ERROR: {
priority: 'LOW',
category: 'timing',
description: 'Race conditions or timeout issues',
fixable: true
},
EXTERNAL_ERROR: {
priority: 'LOW',
category: 'external',
description: 'Third-party service or network issues',
fixable: false
}
};
```
### 2. Error Pattern Recognition
```javascript
const ErrorPatterns = {
// Syntax Errors
typescript: [
{
pattern: /Cannot find module ['"`](.+)['"`]/,
category: 'SYNTAX_ERROR',
message: 'Missing import or module not found',
solution: 'install_missing_dependency'
},
{
pattern: /Property ['"`](.+)['"`] does not exist on type/,
category: 'SYNTAX_ERROR',
message: 'TypeScript type error',
solution: 'fix_typescript_types'
},
{
pattern: /Expected (\d+) arguments, but got (\d+)/,
category: 'SYNTAX_ERROR',
message: 'Function call argument mismatch',
solution: 'fix_function_arguments'
}
],
// Environment Errors
environment: [
{
pattern: /ECONNREFUSED.*:(\d+)/,
category: 'ENVIRONMENT_ERROR',
message: 'Cannot connect to service on port',
solution: 'start_required_service'
},
{
pattern: /Command ['"`](.+)['"`] not found/,
category: 'ENVIRONMENT_ERROR',
message: 'Required command or tool not installed',
solution: 'install_missing_tool'
},
{
pattern: /Permission denied/,
category: 'ENVIRONMENT_ERROR',
message: 'Insufficient permissions',
solution: 'fix_permissions'
}
],
// Test Logic Errors
playwright: [
{
pattern: /locator\.click: Timeout (\d+)ms exceeded/,
category: 'LOGIC_ERROR',
message: 'Element not found or not clickable within timeout',
solution: 'fix_element_locator'
},
{
pattern: /expect.*\.toHaveText.*received/,
category: 'LOGIC_ERROR',
message: 'Text content assertion failed',
solution: 'update_text_assertion'
},
{
pattern: /Navigation timeout exceeded/,
category: 'TIMING_ERROR',
message: 'Page navigation too slow',
solution: 'increase_navigation_timeout'
}
],
// API Test Errors
karate: [
{
pattern: /status code was: (\d+), expected: (\d+)/,
category: 'LOGIC_ERROR',
message: 'API response status code mismatch',
solution: 'fix_api_expectation'
},
{
pattern: /path not found: (.+)/,
category: 'DATA_ERROR',
message: 'JSON path assertion failed',
solution: 'fix_json_path'
},
{
pattern: /connection refused/i,
category: 'ENVIRONMENT_ERROR',
message: 'Cannot connect to API server',
solution: 'start_api_server'
}
]
};
```
## Failure Analysis Engine
### 1. Error Log Parser
```javascript
function parseTestFailures(testResults) {
const failures = [];
// Parse different test result formats
if (testResults.type === 'playwright') {
testResults.suites.forEach(suite => {
suite.tests.forEach(test => {
if (test.outcome === 'failed') {
failures.push({
testName: test.title,
testFile: suite.file,
error: test.error,
framework: 'playwright',
rawLog: test.stderr
});
}
});
});
}
if (testResults.type === 'karate') {
testResults.scenarioResults.forEach(scenario => {
if (!scenario.passed) {
failures.push({
testName: scenario.scenario.name,
testFile: scenario.scenario.feature.resource.file,
error: scenario.error,
framework: 'karate',
rawLog: scenario.stepResults.map(s => s.error).filter(Boolean).join('\n')
});
}
});
}
if (testResults.type === 'jest') {
testResults.testResults.forEach(testFile => {
testFile.assertionResults.forEach(test => {
if (test.status === 'failed') {
failures.push({
testName: test.title,
testFile: testFile.testFilePath,
error: test.failureMessages.join('\n'),
framework: 'jest',
rawLog: test.failureMessages.join('\n')
});
}
});
});
}
return failures;
}
```
### 2. Root Cause Analysis
```javascript
function analyzeFailure(failure) {
const analysis = {
testName: failure.testName,
testFile: failure.testFile,
framework: failure.framework,
category: 'UNKNOWN',
rootCause: 'Unable to determine root cause',
confidence: 0,
remediation: []
};
// Match against known error patterns
for (const [framework, patterns] of Object.entries(ErrorPatterns)) {
if (framework === failure.framework || framework === 'environment') {
for (const pattern of patterns) {
const match = failure.error.match(pattern.pattern);
if (match) {
analysis.category = pattern.category;
analysis.rootCause = pattern.message;
analysis.confidence = 0.9;
analysis.patternMatch = match;
analysis.solutionKey = pattern.solution;
break;
}
}
}
}
// Additional heuristic analysis
if (analysis.confidence < 0.5) {
analysis = performHeuristicAnalysis(failure, analysis);
}
// Generate remediation steps
analysis.remediation = generateRemediation(analysis);
return analysis;
}
function performHeuristicAnalysis(failure, analysis) {
const error = failure.error.toLowerCase();
// Check for common indicators
if (error.includes('timeout') || error.includes('exceeded')) {
analysis.category = 'TIMING_ERROR';
analysis.rootCause = 'Operation timed out';
analysis.confidence = 0.7;
} else if (error.includes('not found') || error.includes('404')) {
analysis.category = 'LOGIC_ERROR';
analysis.rootCause = 'Resource or element not found';
analysis.confidence = 0.7;
} else if (error.includes('permission') || error.includes('403')) {
analysis.category = 'ENVIRONMENT_ERROR';
analysis.rootCause = 'Permission denied';
analysis.confidence = 0.7;
} else if (error.includes('network') || error.includes('econnreset')) {
analysis.category = 'EXTERNAL_ERROR';
analysis.rootCause = 'Network connectivity issue';
analysis.confidence = 0.6;
}
return analysis;
}
```
### 3. Remediation Generator
```javascript
const RemediationStrategies = {
install_missing_dependency: (analysis) => ({
priority: 'HIGH',
estimatedTime: '2-5 minutes',
steps: [
{
action: 'Install missing dependency',
command: `npm install ${analysis.patternMatch[1]}`,
description: `Install the missing module: ${analysis.patternMatch[1]}`
},
{
action: 'Verify installation',
command: `npm list ${analysis.patternMatch[1]}`,
description: 'Confirm the dependency was installed correctly'
},
{
action: 'Re-run tests',
command: 'npm test',
description: 'Execute tests again to verify fix'
}
],
preventionTips: [
'Run "npm ci" before test execution',
'Keep package.json dependencies up to date',
'Use package-lock.json for consistent dependencies'
]
}),
fix_typescript_types: (analysis) => ({
priority: 'HIGH',
estimatedTime: '5-10 minutes',
steps: [
{
action: 'Identify missing type',
description: `Property '${analysis.patternMatch[1]}' is not recognized`,
manual: true
},
{
action: 'Add type definition',
codeExample: `
interface ExpectedType {
${analysis.patternMatch[1]}: string; // Adjust type as needed
}`,
description: 'Add the missing property to your interface'
},
{
action: 'Update import statements',
description: 'Ensure all required types are imported'
}
],
automaticFix: {
available: true,
script: generateTypeFixScript(analysis)
}
}),
start_required_service: (analysis) => ({
priority: 'HIGH',
estimatedTime: '1-3 minutes',
steps: [
{
action: 'Check service status',
command: `netstat -tulpn | grep :${analysis.patternMatch[1]}`,
description: `Verify if service is running on port ${analysis.patternMatch[1]}`
},
{
action: 'Start database service',
command: 'docker-compose up -d db',
description: 'Start the required database service',
condition: 'if database port (5432, 3306, 27017)'
},
{
action: 'Start application server',
command: 'npm run start:dev',
description: 'Start the application server',
condition: 'if application port (3000, 8080, etc.)'
},
{
action: 'Wait for service ready',
command: `until nc -z localhost ${analysis.patternMatch[1]}; do sleep 1; done`,
description: 'Wait for service to be available'
}
],
healthCheck: {
command: `curl -f http://localhost:${analysis.patternMatch[1]}/health || echo "Service not ready"`,
description: 'Verify service health'
}
}),
fix_element_locator: (analysis) => ({
priority: 'MEDIUM',
estimatedTime: '5-15 minutes',
steps: [
{
action: 'Inspect element selectors',
description: 'Open browser dev tools and verify element exists',
manual: true
},
{
action: 'Try alternative selectors',
codeExample: `
// Instead of:
await page.locator('[data-testid=old-selector]').click();
// Try these alternatives:
await page.locator('text="Button Text"').click();
await page.locator('[aria-label="Button Label"]').click();
await page.locator('button:has-text("Submit")').click();`,
description: 'Use more robust selector strategies'
},
{
action: 'Add explicit wait',
codeExample: `
await page.waitForSelector('[data-testid=element]', { state: 'visible' });
await page.locator('[data-testid=element]').click();`,
description: 'Wait for element to be ready before interaction'
}
],
debuggingTips: [
'Use page.screenshot() to capture current page state',
'Add console.log to verify page navigation',
'Check if element is in viewport with scrollIntoView()'
]
})
};
function generateRemediation(analysis) {
const strategy = RemediationStrategies[analysis.solutionKey];
if (strategy) {
return strategy(analysis);
}
// Generic remediation for unknown issues
return {
priority: 'MEDIUM',
estimatedTime: '10-30 minutes',
steps: [
{
action: 'Review test logs',
description: 'Examine the full error message and stack trace'
},
{
action: 'Check test environment',
description: 'Verify all services and dependencies are running'
},
{
action: 'Run test in isolation',
description: 'Execute the failing test individually to isolate the issue'
},
{
action: 'Consult documentation',
description: 'Review framework documentation for similar issues'
}
]
};
}
```
## User-Friendly Report Generation
### 1. Failure Summary Report
```javascript
function generateFailureReport(failures) {
const analyses = failures.map(analyzeFailure);
const report = {
summary: {
totalFailures: failures.length,
categorized: groupBy(analyses, 'category'),
highPriority: analyses.filter(a => a.category === 'SYNTAX_ERROR' || a.category === 'ENVIRONMENT_ERROR').length,
autoFixable: analyses.filter(a => a.remediation?.automaticFix?.available).length
},
detailedAnalysis: analyses.map(formatAnalysisForUser),
quickActions: generateQuickActions(analyses),
executionScript: generateFixScript(analyses)
};
return report;
}
function formatAnalysisForUser(analysis) {
return {
testFile: analysis.testFile,
testName: analysis.testName,
issue: {
category: FailureCategories[analysis.category]?.description || 'Unknown issue',
rootCause: analysis.rootCause,
confidence: `${Math.round(analysis.confidence * 100)}% confident`
},
remedy: {
priority: analysis.remediation?.priority || 'MEDIUM',
estimatedTime: analysis.remediation?.estimatedTime || 'Unknown',
steps: analysis.remediation?.steps || [],
autoFixAvailable: !!analysis.remediation?.automaticFix
}
};
}
```
### 2. Interactive Fix Script Generator
```javascript
function generateFixScript(analyses) {
const script = ['#!/bin/bash', '# Automated Test Failure Fixes', ''];
// Group fixes by priority
const highPriorityFixes = analyses.filter(a => a.category === 'SYNTAX_ERROR' || a.category === 'ENVIRONMENT_ERROR');
const mediumPriorityFixes = analyses.filter(a => a.category === 'LOGIC_ERROR' || a.category === 'DATA_ERROR');
if (highPriorityFixes.length > 0) {
script.push('echo "š§ Applying high-priority fixes..."');
highPriorityFixes.forEach(analysis => {
if (analysis.remediation?.steps) {
analysis.remediation.steps.forEach(step => {
if (step.command) {
script.push(`echo "Executing: ${step.description}"`);
script.push(step.command);
script.push('');
}
});
}
});
}
script.push('echo "ā
High-priority fixes completed. Re-running tests..."');
script.push('npm test');
return script.join('\n');
}
```
### 3. HTML Report Generator
```javascript
function generateHTMLReport(failureReport) {
return `
<!DOCTYPE html>
<html>
<head>
<title>Test Failure Analysis Report</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; background: #f5f5f5; }
.container { max-width: 1200px; margin: 0 auto; background: white; padding: 20px; border-radius: 8px; }
.header { text-align: center; border-bottom: 2px solid #007bff; padding-bottom: 15px; }
.summary-card { background: #f8f9fa; padding: 15px; border-radius: 5px; margin: 15px 0; }
.failure-item { background: white; border-left: 4px solid #dc3545; padding: 15px; margin: 10px 0; }
.high-priority { border-left-color: #dc3545; }
.medium-priority { border-left-color: #ffc107; }
.low-priority { border-left-color: #28a745; }
.remediation-steps { background: #e9ecef; padding: 10px; border-radius: 3px; }
.command { background: #2d3748; color: #e2e8f0; padding: 8px; border-radius: 3px; font-family: monospace; }
.auto-fix-btn { background: #28a745; color: white; padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>š Test Failure Analysis Report</h1>
<p>Generated on ${new Date().toLocaleString()}</p>
</div>
<div class="summary-card">
<h2>š Summary</h2>
<p><strong>Total Failures:</strong> ${failureReport.summary.totalFailures}</p>
<p><strong>High Priority Issues:</strong> ${failureReport.summary.highPriority}</p>
<p><strong>Auto-fixable Issues:</strong> ${failureReport.summary.autoFixable}</p>
</div>
<div class="quick-actions">
<h2>ā” Quick Actions</h2>
<button class="auto-fix-btn" onclick="downloadFixScript()">Download Fix Script</button>
<button class="auto-fix-btn" onclick="runAutoFixes()">Run Auto-Fixes</button>
</div>
<div class="failures-list">
<h2>šØ Detailed Analysis</h2>
${failureReport.detailedAnalysis.map(renderFailureItem).join('')}
</div>
</div>
<script>
function downloadFixScript() {
const script = \`${failureReport.executionScript}\`;
const blob = new Blob([script], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'test-fixes.sh';
a.click();
}
function runAutoFixes() {
alert('Auto-fix functionality would be implemented here');
}
</script>
</body>
</html>`;
}
```
## Main Analysis Function
```javascript
function analyzeTestFailures(testResultsPath) {
console.log('š Analyzing test failures...');
// Load test results
const testResults = loadTestResults(testResultsPath);
// Parse failures from results
const failures = parseTestFailures(testResults);
if (failures.length === 0) {
console.log('ā
No test failures detected!');
return;
}
console.log(`Found ${failures.length} test failures. Analyzing...`);
// Generate comprehensive failure report
const failureReport = generateFailureReport(failures);
// Save reports
fs.writeFileSync('test-results/failure-analysis.json', JSON.stringify(failureReport, null, 2));
fs.writeFileSync('test-results/failure-report.html', generateHTMLReport(failureReport));
fs.writeFileSync('test-results/auto-fix.sh', failureReport.executionScript, { mode: 0o755 });
// Display summary
console.log('\nšÆ Failure Analysis Complete');
console.log('============================');
console.log(`š Total failures analyzed: ${failures.length}`);
console.log(`š„ High priority issues: ${failureReport.summary.highPriority}`);
console.log(`š§ Auto-fixable issues: ${failureReport.summary.autoFixable}`);
console.log(`š Detailed report: test-results/failure-report.html`);
console.log(`ā” Auto-fix script: test-results/auto-fix.sh`);
return failureReport;
}
```
This utility provides comprehensive failure analysis with specific, actionable remediation steps, automated fix scripts, and clear reporting to help users resolve test issues quickly and effectively.