Frontend: - Refactored CSS architecture with new utility classes - Updated dashboard components styling - Improved responsive grid system - Enhanced typography and variables - Updated E2E and integration tests Added: - Claude Code slash commands for validation - SSH tunnel and start test scripts 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
493 lines
19 KiB
JavaScript
493 lines
19 KiB
JavaScript
/**
|
||
* Console Error Pattern Analysis Tests
|
||
* Analyzes frontend console errors, categorizes patterns, and monitors error frequencies
|
||
* Provides insights into application stability and potential issues
|
||
*/
|
||
|
||
import { test, expect } from '@playwright/test';
|
||
import {
|
||
authenticateWithRealCredentials,
|
||
selectCompany,
|
||
REAL_CREDENTIALS
|
||
} from '../../utils/real-auth.js';
|
||
import {
|
||
setupConsoleCapture,
|
||
ErrorClassifier,
|
||
generateErrorReport
|
||
} from '../../utils/console-monitor.js';
|
||
|
||
test.describe('Console Error Pattern Analysis', () => {
|
||
const commonErrorPatterns = [
|
||
{ pattern: /Failed to fetch/i, category: 'Network Error', severity: 'WARNING' },
|
||
{ pattern: /Network request failed/i, category: 'Network Error', severity: 'WARNING' },
|
||
{ pattern: /404.*not found/i, category: '404 Error', severity: 'WARNING' },
|
||
{ pattern: /Uncaught TypeError/i, category: 'JavaScript Error', severity: 'CRITICAL' },
|
||
{ pattern: /Uncaught ReferenceError/i, category: 'JavaScript Error', severity: 'CRITICAL' },
|
||
{ pattern: /Vue warn/i, category: 'Vue Warning', severity: 'WARNING' },
|
||
{ pattern: /Component.*not found/i, category: 'Component Error', severity: 'WARNING' },
|
||
{ pattern: /Oracle.*connection/i, category: 'Database Error', severity: 'CRITICAL' },
|
||
{ pattern: /Authentication.*failed/i, category: 'Auth Error', severity: 'CRITICAL' },
|
||
{ pattern: /Cannot read property/i, category: 'Property Error', severity: 'CRITICAL' },
|
||
{ pattern: /Cannot access before initialization/i, category: 'Initialization Error', severity: 'CRITICAL' }
|
||
];
|
||
|
||
test.beforeEach(async ({ page }) => {
|
||
setupConsoleCapture(page);
|
||
});
|
||
|
||
test.afterEach(async ({ page }) => {
|
||
const report = generateErrorReport(page, test.info().title);
|
||
|
||
if (report.summary.classifications.critical > 0) {
|
||
console.warn('🚨 Critical console errors detected:', report.details.criticalErrors);
|
||
}
|
||
|
||
// Log error pattern summary
|
||
if (Object.keys(report.details.errorPatterns).length > 0) {
|
||
console.log('📊 Error patterns detected:', report.details.errorPatterns);
|
||
}
|
||
});
|
||
|
||
test('should detect and categorize frontend errors during navigation', async ({ page }) => {
|
||
console.log('🔍 Analyzing console errors during complete navigation flow...');
|
||
|
||
// Navigate through all main application views
|
||
const navigationFlow = [
|
||
{ action: () => page.goto('/login'), name: 'Login Page' },
|
||
{ action: () => authenticateWithRealCredentials(page), name: 'Authentication' },
|
||
{ action: () => page.goto('/dashboard'), name: 'Dashboard' },
|
||
{ action: () => selectCompany(page, REAL_CREDENTIALS.company), name: 'Company Selection' },
|
||
{ action: () => page.goto('/invoices'), name: 'Invoices Page' },
|
||
{ action: () => page.goto('/payments'), name: 'Payments Page' },
|
||
{ action: () => page.goto('/dashboard'), name: 'Return to Dashboard' }
|
||
];
|
||
|
||
const errorsByStep = {};
|
||
|
||
for (const step of navigationFlow) {
|
||
console.log(`📍 Navigating to: ${step.name}`);
|
||
|
||
const initialErrorCount = (page.consoleMessages || []).length;
|
||
|
||
try {
|
||
await step.action();
|
||
await page.waitForLoadState('networkidle', { timeout: 10000 });
|
||
} catch (error) {
|
||
console.warn(`⚠️ Navigation warning for ${step.name}:`, error.message);
|
||
}
|
||
|
||
const newErrors = (page.consoleMessages || []).slice(initialErrorCount);
|
||
errorsByStep[step.name] = newErrors;
|
||
|
||
console.log(`📊 ${step.name}: ${newErrors.length} new console messages`);
|
||
}
|
||
|
||
// Analyze error patterns across all steps
|
||
const allErrors = Object.values(errorsByStep).flat();
|
||
const errorsByPattern = {};
|
||
const errorsBySeverity = { CRITICAL: 0, WARNING: 0, INFO: 0, UNKNOWN: 0 };
|
||
|
||
allErrors.forEach(error => {
|
||
// Classify by severity
|
||
const severity = ErrorClassifier.classify(error);
|
||
errorsBySeverity[severity]++;
|
||
|
||
// Classify by pattern
|
||
const pattern = commonErrorPatterns.find(p => p.pattern.test(error.text || error.error || ''));
|
||
if (pattern) {
|
||
errorsByPattern[pattern.category] = (errorsByPattern[pattern.category] || 0) + 1;
|
||
} else if (error.type === 'error') {
|
||
errorsByPattern['Unclassified Error'] = (errorsByPattern['Unclassified Error'] || 0) + 1;
|
||
}
|
||
});
|
||
|
||
console.log('📈 Error Analysis Summary:');
|
||
console.log(` Total Console Messages: ${allErrors.length}`);
|
||
console.log(` By Severity:`, errorsBySeverity);
|
||
console.log(` By Pattern:`, errorsByPattern);
|
||
|
||
// Validate error thresholds
|
||
expect(errorsBySeverity.CRITICAL, 'Critical errors detected during navigation').toBe(0);
|
||
expect(errorsBySeverity.WARNING, 'Excessive warnings during navigation').toBeLessThan(10);
|
||
|
||
// Check for high-frequency patterns
|
||
Object.entries(errorsByPattern).forEach(([pattern, count]) => {
|
||
if (count > 3) {
|
||
console.warn(`⚠️ High frequency error pattern: ${pattern} (${count} occurrences)`);
|
||
}
|
||
});
|
||
|
||
console.log('✅ Console error pattern analysis completed');
|
||
});
|
||
|
||
test('should monitor error frequencies and identify recurring issues', async ({ page }) => {
|
||
console.log('📊 Monitoring error frequencies across multiple operations...');
|
||
|
||
// Authenticate first
|
||
await authenticateWithRealCredentials(page);
|
||
await selectCompany(page, REAL_CREDENTIALS.company);
|
||
|
||
const operations = [
|
||
{ name: 'Dashboard Refresh', action: () => page.reload() },
|
||
{ name: 'Invoices Navigation', action: () => page.goto('/invoices') },
|
||
{ name: 'Payments Navigation', action: () => page.goto('/payments') },
|
||
{ name: 'Dashboard Return', action: () => page.goto('/dashboard') },
|
||
{ name: 'Company Re-selection', action: () => selectCompany(page, REAL_CREDENTIALS.company) }
|
||
];
|
||
|
||
const errorFrequencies = {};
|
||
const operationErrors = {};
|
||
|
||
for (let cycle = 0; cycle < 2; cycle++) {
|
||
console.log(`🔄 Error monitoring cycle ${cycle + 1}/2`);
|
||
|
||
for (const operation of operations) {
|
||
const initialMessageCount = (page.consoleMessages || []).length;
|
||
|
||
try {
|
||
await operation.action();
|
||
await page.waitForLoadState('networkidle', { timeout: 8000 });
|
||
} catch (error) {
|
||
console.warn(`⚠️ Operation ${operation.name} encountered issue:`, error.message);
|
||
}
|
||
|
||
const newMessages = (page.consoleMessages || []).slice(initialMessageCount);
|
||
const errorMessages = newMessages.filter(msg => msg.type === 'error' || msg.type === 'pageerror');
|
||
|
||
operationErrors[`${operation.name}_Cycle${cycle + 1}`] = errorMessages;
|
||
|
||
// Track error frequencies
|
||
errorMessages.forEach(error => {
|
||
const errorText = error.text || error.error || '';
|
||
const pattern = commonErrorPatterns.find(p => p.pattern.test(errorText));
|
||
const key = pattern ? pattern.category : 'Unclassified';
|
||
|
||
errorFrequencies[key] = (errorFrequencies[key] || 0) + 1;
|
||
});
|
||
|
||
console.log(` ${operation.name}: ${errorMessages.length} errors`);
|
||
}
|
||
}
|
||
|
||
// Analyze recurring patterns
|
||
console.log('🔍 Error Frequency Analysis:');
|
||
const recurringIssues = Object.entries(errorFrequencies)
|
||
.filter(([_pattern, count]) => count > 2)
|
||
.sort((a, b) => b[1] - a[1]);
|
||
|
||
if (recurringIssues.length > 0) {
|
||
console.log('🚨 Recurring Error Patterns:');
|
||
recurringIssues.forEach(([pattern, count]) => {
|
||
console.log(` ${pattern}: ${count} occurrences`);
|
||
});
|
||
} else {
|
||
console.log('✅ No recurring error patterns detected');
|
||
}
|
||
|
||
// Validate error thresholds
|
||
const totalErrors = Object.values(errorFrequencies).reduce((sum, count) => sum + count, 0);
|
||
expect(totalErrors, 'Excessive total errors across operations').toBeLessThan(20);
|
||
|
||
// Critical patterns should not recur
|
||
const criticalRecurring = recurringIssues.filter(([pattern]) =>
|
||
commonErrorPatterns.find(p => p.category === pattern && p.severity === 'CRITICAL')
|
||
);
|
||
|
||
expect(criticalRecurring.length, `Critical recurring errors: ${JSON.stringify(criticalRecurring)}`).toBe(0);
|
||
|
||
console.log('✅ Error frequency monitoring completed');
|
||
});
|
||
|
||
test('should detect performance-related console warnings', async ({ page }) => {
|
||
console.log('⚡ Detecting performance-related console warnings...');
|
||
|
||
await authenticateWithRealCredentials(page);
|
||
|
||
const performanceKeywords = [
|
||
'slow', 'performance', 'memory', 'leak', 'timeout',
|
||
'blocking', 'lag', 'delay', 'optimization', 'cache'
|
||
];
|
||
|
||
// Perform operations that might trigger performance warnings
|
||
const heavyOperations = [
|
||
{ name: 'Large Data Load', action: () => selectCompany(page, REAL_CREDENTIALS.company) },
|
||
{ name: 'Invoices with Filtering', action: async () => {
|
||
await page.goto('/invoices');
|
||
await page.waitForSelector('[data-testid="invoices-table"]', { timeout: 15000 });
|
||
|
||
// Trigger filtering operations
|
||
if (await page.locator('[data-testid="search-input"]').isVisible()) {
|
||
await page.fill('[data-testid="search-input"]', 'test');
|
||
await page.keyboard.press('Enter');
|
||
await page.waitForTimeout(2000);
|
||
}
|
||
}},
|
||
{ name: 'Multiple Page Navigation', action: async () => {
|
||
const pages = ['/dashboard', '/invoices', '/payments', '/dashboard'];
|
||
for (const pagePath of pages) {
|
||
await page.goto(pagePath);
|
||
await page.waitForLoadState('networkidle', { timeout: 5000 });
|
||
}
|
||
}}
|
||
];
|
||
|
||
const performanceWarnings = [];
|
||
|
||
for (const operation of heavyOperations) {
|
||
console.log(`🔧 Executing: ${operation.name}`);
|
||
|
||
const initialMessageCount = (page.consoleMessages || []).length;
|
||
|
||
const startTime = Date.now();
|
||
await operation.action();
|
||
const operationTime = Date.now() - startTime;
|
||
|
||
const newMessages = (page.consoleMessages || []).slice(initialMessageCount);
|
||
const perfMessages = newMessages.filter(msg => {
|
||
const text = msg.text || msg.error || '';
|
||
return performanceKeywords.some(keyword =>
|
||
text.toLowerCase().includes(keyword.toLowerCase())
|
||
);
|
||
});
|
||
|
||
if (perfMessages.length > 0) {
|
||
performanceWarnings.push({
|
||
operation: operation.name,
|
||
operationTime,
|
||
warnings: perfMessages,
|
||
count: perfMessages.length
|
||
});
|
||
|
||
console.log(`⚠️ ${operation.name}: ${perfMessages.length} performance warnings (${operationTime}ms)`);
|
||
} else {
|
||
console.log(`✅ ${operation.name}: No performance warnings (${operationTime}ms)`);
|
||
}
|
||
}
|
||
|
||
// Analyze performance warnings
|
||
if (performanceWarnings.length > 0) {
|
||
console.log('📊 Performance Warning Analysis:');
|
||
performanceWarnings.forEach(warning => {
|
||
console.log(` ${warning.operation}: ${warning.count} warnings, ${warning.operationTime}ms`);
|
||
warning.warnings.forEach(w => {
|
||
console.log(` - ${w.text || w.error}`);
|
||
});
|
||
});
|
||
|
||
// Performance warnings should be investigated but not fail tests
|
||
const totalPerfWarnings = performanceWarnings.reduce((sum, w) => sum + w.count, 0);
|
||
if (totalPerfWarnings > 5) {
|
||
console.warn(`⚠️ High number of performance warnings: ${totalPerfWarnings}`);
|
||
}
|
||
} else {
|
||
console.log('✅ No performance-related console warnings detected');
|
||
}
|
||
|
||
// Critical performance issues should not be present
|
||
const criticalPerfIssues = (page.consoleMessages || []).filter(msg => {
|
||
const text = msg.text || msg.error || '';
|
||
return msg.type === 'error' && performanceKeywords.some(keyword =>
|
||
text.toLowerCase().includes(keyword.toLowerCase())
|
||
);
|
||
});
|
||
|
||
expect(criticalPerfIssues.length, `Critical performance errors: ${JSON.stringify(criticalPerfIssues)}`).toBe(0);
|
||
|
||
console.log('✅ Performance warning detection completed');
|
||
});
|
||
|
||
test('should analyze error context and provide debugging information', async ({ page }) => {
|
||
console.log('🔬 Analyzing error context for debugging insights...');
|
||
|
||
await authenticateWithRealCredentials(page);
|
||
|
||
// Collect errors with context
|
||
const contextualErrors = [];
|
||
|
||
// Navigate through application collecting error context
|
||
const testScenarios = [
|
||
{
|
||
name: 'Invalid Route Access',
|
||
action: () => page.goto('/nonexistent-route'),
|
||
expectErrors: true
|
||
},
|
||
{
|
||
name: 'Rapid Navigation',
|
||
action: async () => {
|
||
await page.goto('/dashboard');
|
||
await page.goto('/invoices');
|
||
await page.goto('/payments');
|
||
await page.goto('/dashboard');
|
||
},
|
||
expectErrors: false
|
||
},
|
||
{
|
||
name: 'Form Interaction',
|
||
action: async () => {
|
||
await page.goto('/invoices');
|
||
if (await page.locator('[data-testid="search-input"]').isVisible()) {
|
||
await page.fill('[data-testid="search-input"]', 'test search');
|
||
await page.keyboard.press('Enter');
|
||
}
|
||
},
|
||
expectErrors: false
|
||
}
|
||
];
|
||
|
||
for (const scenario of testScenarios) {
|
||
console.log(`🎭 Testing scenario: ${scenario.name}`);
|
||
|
||
const initialMessageCount = (page.consoleMessages || []).length;
|
||
|
||
try {
|
||
await scenario.action();
|
||
await page.waitForLoadState('networkidle', { timeout: 8000 });
|
||
} catch (error) {
|
||
console.log(`ℹ️ Expected error in ${scenario.name}:`, error.message);
|
||
}
|
||
|
||
const newMessages = (page.consoleMessages || []).slice(initialMessageCount);
|
||
const errors = newMessages.filter(msg => msg.type === 'error' || msg.type === 'pageerror');
|
||
|
||
if (errors.length > 0) {
|
||
errors.forEach(error => {
|
||
contextualErrors.push({
|
||
scenario: scenario.name,
|
||
error: error,
|
||
url: page.url(),
|
||
timestamp: error.timestamp,
|
||
expected: scenario.expectErrors
|
||
});
|
||
});
|
||
|
||
console.log(`📍 ${scenario.name}: ${errors.length} errors (expected: ${scenario.expectErrors})`);
|
||
} else {
|
||
console.log(`✅ ${scenario.name}: No errors detected`);
|
||
}
|
||
}
|
||
|
||
// Analyze contextual errors
|
||
if (contextualErrors.length > 0) {
|
||
console.log('🔍 Contextual Error Analysis:');
|
||
|
||
// Group errors by type and scenario
|
||
const errorsByScenario = {};
|
||
const errorsByType = {};
|
||
|
||
contextualErrors.forEach(error => {
|
||
// Group by scenario
|
||
if (!errorsByScenario[error.scenario]) {
|
||
errorsByScenario[error.scenario] = [];
|
||
}
|
||
errorsByScenario[error.scenario].push(error);
|
||
|
||
// Group by error type
|
||
const errorText = error.error.text || error.error.error || '';
|
||
const pattern = commonErrorPatterns.find(p => p.pattern.test(errorText));
|
||
const category = pattern ? pattern.category : 'Unclassified';
|
||
|
||
errorsByType[category] = (errorsByType[category] || 0) + 1;
|
||
});
|
||
|
||
console.log('📊 Errors by Scenario:');
|
||
Object.entries(errorsByScenario).forEach(([scenario, errors]) => {
|
||
console.log(` ${scenario}: ${errors.length} errors`);
|
||
});
|
||
|
||
console.log('📊 Errors by Type:');
|
||
Object.entries(errorsByType).forEach(([type, count]) => {
|
||
console.log(` ${type}: ${count} occurrences`);
|
||
});
|
||
|
||
// Identify unexpected errors (those in scenarios that shouldn't have errors)
|
||
const unexpectedErrors = contextualErrors.filter(error => !error.expected);
|
||
|
||
if (unexpectedErrors.length > 0) {
|
||
console.warn('🚨 Unexpected errors detected:');
|
||
unexpectedErrors.forEach(error => {
|
||
console.warn(` ${error.scenario}: ${error.error.text || error.error.error}`);
|
||
});
|
||
|
||
// Unexpected critical errors should fail the test
|
||
const criticalUnexpected = unexpectedErrors.filter(error =>
|
||
ErrorClassifier.classify(error.error) === 'CRITICAL'
|
||
);
|
||
|
||
expect(criticalUnexpected.length, `Unexpected critical errors: ${JSON.stringify(criticalUnexpected.map(e => e.error.text))}`).toBe(0);
|
||
}
|
||
} else {
|
||
console.log('✅ No contextual errors to analyze');
|
||
}
|
||
|
||
console.log('✅ Error context analysis completed');
|
||
});
|
||
|
||
test('should generate comprehensive error report for debugging', async ({ page }) => {
|
||
console.log('📋 Generating comprehensive error report...');
|
||
|
||
// Perform full application workflow
|
||
await authenticateWithRealCredentials(page);
|
||
await selectCompany(page, REAL_CREDENTIALS.company);
|
||
|
||
const workflow = [
|
||
() => page.goto('/dashboard'),
|
||
() => page.goto('/invoices'),
|
||
() => page.goto('/payments'),
|
||
() => page.goto('/dashboard')
|
||
];
|
||
|
||
for (const step of workflow) {
|
||
await step();
|
||
await page.waitForLoadState('networkidle', { timeout: 8000 });
|
||
}
|
||
|
||
// Generate final error report
|
||
const finalReport = generateErrorReport(page, 'Complete Application Workflow');
|
||
|
||
console.log('📊 Final Error Report:');
|
||
console.log(' Test:', finalReport.testName);
|
||
console.log(' Timestamp:', finalReport.timestamp);
|
||
console.log(' Summary:', finalReport.summary);
|
||
|
||
if (finalReport.details.criticalErrors.length > 0) {
|
||
console.log('🚨 Critical Errors:');
|
||
finalReport.details.criticalErrors.forEach(error => {
|
||
console.log(` - ${error.text || error.error} (${error.location?.url || 'unknown'})`);
|
||
});
|
||
}
|
||
|
||
if (finalReport.details.warnings.length > 0) {
|
||
console.log('⚠️ Warnings:');
|
||
finalReport.details.warnings.slice(0, 5).forEach(warning => {
|
||
console.log(` - ${warning.text || warning.error}`);
|
||
});
|
||
|
||
if (finalReport.details.warnings.length > 5) {
|
||
console.log(` ... and ${finalReport.details.warnings.length - 5} more warnings`);
|
||
}
|
||
}
|
||
|
||
if (Object.keys(finalReport.details.errorPatterns).length > 0) {
|
||
console.log('📈 Error Patterns:');
|
||
Object.entries(finalReport.details.errorPatterns).forEach(([pattern, count]) => {
|
||
console.log(` ${pattern}: ${count} occurrences`);
|
||
});
|
||
}
|
||
|
||
// Performance metrics
|
||
if (finalReport.performance && finalReport.performance.apiCalls) {
|
||
const slowApiCalls = finalReport.performance.apiCalls.filter(call => call.timing > 2000);
|
||
if (slowApiCalls.length > 0) {
|
||
console.log('⚡ Slow API Calls:');
|
||
slowApiCalls.forEach(call => {
|
||
console.log(` ${call.url}: ${call.timing}ms`);
|
||
});
|
||
}
|
||
}
|
||
|
||
// Final validation
|
||
expect(finalReport.summary.classifications.critical, 'Critical errors in comprehensive workflow').toBe(0);
|
||
|
||
console.log('✅ Comprehensive error report generated successfully');
|
||
});
|
||
}); |