Initial commit: ROA2WEB - FastAPI + Vue.js + Telegram Bot

Modern ERP Reports Application with microservices architecture

Tech Stack:
- Backend: FastAPI + python-oracledb (Oracle DB integration)
- Frontend: Vue.js 3 + PrimeVue + Vite
- Telegram Bot: python-telegram-bot + SQLite
- Infrastructure: Shared database pool, JWT authentication, SSH tunnel

Features:
- FastAPI backend with async Oracle connection pool
- Vue.js 3 responsive frontend with PrimeVue components
- Telegram bot alternative interface
- Microservices architecture with shared components
- Complete deployment support (Linux Docker + Windows IIS)
- Comprehensive testing (Playwright E2E + pytest)

Repository Structure:
- reports-app/ - Main application (backend, frontend, telegram-bot)
- shared/ - Shared components (database pool, auth, utils)
- deployment/ - Deployment scripts (Linux & Windows)
- docs/ - Project documentation
- security/ - Security scanning and git hooks
This commit is contained in:
2025-10-25 14:55:08 +03:00
commit 6b13ffa183
237 changed files with 70035 additions and 0 deletions

View File

@@ -0,0 +1,494 @@
/**
* 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,
assertNoCriticalErrors
} 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');
});
});

View File

@@ -0,0 +1,511 @@
/**
* Performance Regression Testing Suite
* Monitors application performance baselines and detects regressions
* Tests real Oracle data loading performance with comprehensive metrics
*/
import { test, expect } from '@playwright/test';
import {
authenticateWithRealCredentials,
selectCompany,
REAL_CREDENTIALS
} from '../../utils/real-auth.js';
import {
setupConsoleCapture,
PerformanceMonitor,
PerformanceBaselines,
assertPerformanceBaseline,
generateErrorReport
} from '../../utils/console-monitor.js';
test.describe('Performance Regression Testing', () => {
test.beforeEach(async ({ page }) => {
setupConsoleCapture(page);
});
test.afterEach(async ({ page }) => {
const report = generateErrorReport(page, test.info().title);
// Log performance metrics from the test
if (page.performanceMetrics?.apiCalls?.length > 0) {
const avgApiTime = page.performanceMetrics.apiCalls
.reduce((sum, call) => sum + call.timing, 0) / page.performanceMetrics.apiCalls.length;
console.log(`📊 Average API response time: ${avgApiTime.toFixed(0)}ms`);
const slowCalls = page.performanceMetrics.apiCalls.filter(call => call.timing > 3000);
if (slowCalls.length > 0) {
console.warn('⚠️ Slow API calls detected:', slowCalls.map(c => `${c.url}: ${c.timing}ms`));
}
}
});
test('should meet performance baselines with real data', async ({ page }) => {
console.log('📈 Testing performance baselines with ROMFAST real data...');
// Measure login performance
console.log('🔐 Measuring login performance...');
const loginStart = Date.now();
const authResult = await authenticateWithRealCredentials(page);
const loginTime = Date.now() - loginStart;
expect(authResult.success, 'Authentication must succeed for performance test').toBe(true);
assertPerformanceBaseline(loginTime, PerformanceBaselines.loginTime, 'Login process', expect);
console.log(`✅ Login completed in ${loginTime}ms (baseline: ${PerformanceBaselines.loginTime}ms)`);
// Measure dashboard load with ROMFAST data
console.log('📊 Measuring dashboard load performance...');
const dashboardStart = Date.now();
const selectSuccess = await selectCompany(page, REAL_CREDENTIALS.company);
await page.waitForSelector('[data-testid="dashboard-stats"]', { timeout: 15000 });
const dashboardTime = Date.now() - dashboardStart;
expect(selectSuccess, 'Company selection must succeed').toBe(true);
assertPerformanceBaseline(dashboardTime, PerformanceBaselines.dashboardLoad, 'Dashboard load', expect);
console.log(`✅ Dashboard loaded in ${dashboardTime}ms (baseline: ${PerformanceBaselines.dashboardLoad}ms)`);
// Measure report generation performance
console.log('📋 Measuring report generation performance...');
const reportStart = Date.now();
await page.click('[data-testid="nav-invoices"]');
await page.waitForSelector('[data-testid="invoices-table"]', { timeout: 20000 });
const reportTime = Date.now() - reportStart;
assertPerformanceBaseline(reportTime, PerformanceBaselines.reportGeneration, 'Invoice report generation', expect);
console.log(`✅ Report generated in ${reportTime}ms (baseline: ${PerformanceBaselines.reportGeneration}ms)`);
// Check for performance-related console warnings
const performanceWarnings = (page.consoleMessages || [])
.filter(msg => msg.text && (
msg.text.includes('slow') ||
msg.text.includes('performance') ||
msg.text.includes('timeout')
));
if (performanceWarnings.length > 0) {
console.warn('⚠️ Performance warnings detected:', performanceWarnings.map(w => w.text));
}
// Overall workflow should be reasonably fast
const totalWorkflowTime = loginTime + dashboardTime + reportTime;
expect(totalWorkflowTime).toBeLessThan(12000); // Max 12s for complete workflow
console.log(`✅ Complete workflow: ${totalWorkflowTime}ms`);
});
test('should detect performance regressions across multiple runs', async ({ page }) => {
console.log('🔄 Testing performance consistency across multiple runs...');
const performanceRuns = [];
const numberOfRuns = 3;
for (let run = 1; run <= numberOfRuns; run++) {
console.log(`🏃 Performance run ${run}/${numberOfRuns}`);
// Clear state for clean run
await page.evaluate(() => {
localStorage.clear();
sessionStorage.clear();
});
const runMetrics = {
run: run,
login: 0,
dashboard: 0,
invoices: 0,
payments: 0,
navigation: 0
};
// Login timing
const loginStart = Date.now();
const authResult = await authenticateWithRealCredentials(page);
runMetrics.login = Date.now() - loginStart;
expect(authResult.success).toBe(true);
// Dashboard timing
const dashboardStart = Date.now();
await selectCompany(page, REAL_CREDENTIALS.company);
await page.waitForSelector('[data-testid="dashboard-stats"]', { timeout: 15000 });
runMetrics.dashboard = Date.now() - dashboardStart;
// Invoices timing
const invoicesStart = Date.now();
await page.click('[data-testid="nav-invoices"]');
await page.waitForSelector('[data-testid="invoices-table"]', { timeout: 15000 });
runMetrics.invoices = Date.now() - invoicesStart;
// Payments timing
const paymentsStart = Date.now();
await page.click('[data-testid="nav-payments"]');
await page.waitForSelector('[data-testid="payments-table"]', { timeout: 15000 });
runMetrics.payments = Date.now() - paymentsStart;
// Navigation timing (return to dashboard)
const navStart = Date.now();
await page.click('[data-testid="nav-dashboard"]');
await page.waitForSelector('[data-testid="dashboard-stats"]', { timeout: 10000 });
runMetrics.navigation = Date.now() - navStart;
performanceRuns.push(runMetrics);
console.log(`📊 Run ${run} metrics:`, {
login: `${runMetrics.login}ms`,
dashboard: `${runMetrics.dashboard}ms`,
invoices: `${runMetrics.invoices}ms`,
payments: `${runMetrics.payments}ms`,
navigation: `${runMetrics.navigation}ms`
});
// Short delay between runs
await page.waitForTimeout(1000);
}
// Analyze performance consistency
const metrics = ['login', 'dashboard', 'invoices', 'payments', 'navigation'];
const performanceAnalysis = {};
metrics.forEach(metric => {
const values = performanceRuns.map(run => run[metric]);
const avg = values.reduce((sum, val) => sum + val, 0) / values.length;
const min = Math.min(...values);
const max = Math.max(...values);
const variance = values.reduce((sum, val) => sum + Math.pow(val - avg, 2), 0) / values.length;
const stdDev = Math.sqrt(variance);
performanceAnalysis[metric] = {
average: avg,
min: min,
max: max,
standardDeviation: stdDev,
variationCoeff: (stdDev / avg) * 100 // Coefficient of variation as percentage
};
});
console.log('📈 Performance Consistency Analysis:');
Object.entries(performanceAnalysis).forEach(([metric, stats]) => {
console.log(` ${metric}:`);
console.log(` Average: ${stats.average.toFixed(0)}ms`);
console.log(` Range: ${stats.min}ms - ${stats.max}ms`);
console.log(` Variation: ${stats.variationCoeff.toFixed(1)}%`);
});
// Validate performance consistency
metrics.forEach(metric => {
const stats = performanceAnalysis[metric];
// Average should meet baseline
const baseline = PerformanceBaselines[metric] || PerformanceBaselines.apiResponse;
expect(stats.average, `${metric} average performance regression`).toBeLessThan(baseline);
// Variation should be reasonable (< 50% coefficient of variation)
expect(stats.variationCoeff, `${metric} performance too inconsistent`).toBeLessThan(50);
// Max time should not be extremely higher than average (< 2x)
const maxRatio = stats.max / stats.average;
expect(maxRatio, `${metric} has outlier performance`).toBeLessThan(2.5);
});
console.log('✅ Performance consistency validated across all runs');
});
test('should measure page load performance metrics', async ({ page }) => {
console.log('📄 Measuring comprehensive page load performance...');
await authenticateWithRealCredentials(page);
const pages = [
{ path: '/dashboard', name: 'Dashboard', selector: '[data-testid="dashboard-stats"]' },
{ path: '/invoices', name: 'Invoices', selector: '[data-testid="invoices-table"]' },
{ path: '/payments', name: 'Payments', selector: '[data-testid="payments-table"]' }
];
const pageMetrics = [];
for (const pageInfo of pages) {
console.log(`📊 Measuring ${pageInfo.name} page performance...`);
const navigationStart = Date.now();
await page.goto(pageInfo.path);
// Wait for page to be interactive
await page.waitForLoadState('domcontentloaded');
const domContentLoadTime = Date.now() - navigationStart;
// Wait for main content
await page.waitForSelector(pageInfo.selector, { timeout: 15000 });
const contentLoadTime = Date.now() - navigationStart;
// Get detailed performance metrics
const perfMetrics = await PerformanceMonitor.measurePageLoad(page);
const networkMetrics = await PerformanceMonitor.getNetworkMetrics(page);
const pageMetric = {
page: pageInfo.name,
path: pageInfo.path,
navigationTime: contentLoadTime,
domContentLoaded: domContentLoadTime,
performanceApi: perfMetrics,
network: networkMetrics
};
pageMetrics.push(pageMetric);
console.log(` ${pageInfo.name} Performance:`);
console.log(` Navigation: ${contentLoadTime}ms`);
console.log(` DOM Content Loaded: ${domContentLoadTime}ms`);
console.log(` First Paint: ${perfMetrics.firstPaint.toFixed(0)}ms`);
console.log(` Network Resources: ${networkMetrics.totalResources}`);
console.log(` Average Resource Time: ${networkMetrics.averageResponseTime.toFixed(0)}ms`);
if (networkMetrics.slowResources.length > 0) {
console.log(` Slow Resources: ${networkMetrics.slowResources.length}`);
networkMetrics.slowResources.slice(0, 3).forEach(resource => {
console.log(` ${resource.name}: ${resource.duration.toFixed(0)}ms`);
});
}
}
// Validate page performance
pageMetrics.forEach(metric => {
// Navigation time should meet baseline
assertPerformanceBaseline(
metric.navigationTime,
PerformanceBaselines.pageLoad,
`${metric.page} navigation`,
expect
);
// DOM content should load quickly
expect(metric.domContentLoaded, `${metric.page} DOM content load too slow`)
.toBeLessThan(3000);
// First paint should be reasonable
if (metric.performanceApi.firstPaint > 0) {
expect(metric.performanceApi.firstPaint, `${metric.page} first paint too slow`)
.toBeLessThan(2000);
}
// Should not have excessive slow resources
expect(metric.network.slowResources.length, `${metric.page} has too many slow resources`)
.toBeLessThan(5);
});
console.log('✅ Page load performance metrics validated');
});
test('should monitor API response times and detect slow endpoints', async ({ page }) => {
console.log('🌐 Monitoring API response times...');
await authenticateWithRealCredentials(page);
await selectCompany(page, REAL_CREDENTIALS.company);
const apiEndpoints = [
{ name: 'Companies', trigger: () => page.reload() },
{ name: 'Dashboard Stats', trigger: () => page.goto('/dashboard') },
{ name: 'Invoices', trigger: () => page.goto('/invoices') },
{ name: 'Payments', trigger: () => page.goto('/payments') }
];
const apiMetrics = [];
for (const endpoint of apiEndpoints) {
console.log(`📡 Testing ${endpoint.name} API performance...`);
// Clear previous metrics
if (page.performanceMetrics) {
page.performanceMetrics.apiCalls = [];
}
const startTime = Date.now();
await endpoint.trigger();
// Wait for API calls to complete
await page.waitForLoadState('networkidle', { timeout: 15000 });
const totalTime = Date.now() - startTime;
// Analyze API calls made during this operation
const apiCalls = page.performanceMetrics?.apiCalls || [];
if (apiCalls.length > 0) {
const avgResponseTime = apiCalls.reduce((sum, call) => sum + call.timing, 0) / apiCalls.length;
const maxResponseTime = Math.max(...apiCalls.map(call => call.timing));
const slowCalls = apiCalls.filter(call => call.timing > PerformanceBaselines.apiResponse);
const metric = {
endpoint: endpoint.name,
totalTime: totalTime,
apiCallCount: apiCalls.length,
averageApiTime: avgResponseTime,
maxApiTime: maxResponseTime,
slowCallCount: slowCalls.length,
slowCalls: slowCalls
};
apiMetrics.push(metric);
console.log(` ${endpoint.name} API Metrics:`);
console.log(` Total Operation: ${totalTime}ms`);
console.log(` API Calls: ${apiCalls.length}`);
console.log(` Average API Time: ${avgResponseTime.toFixed(0)}ms`);
console.log(` Max API Time: ${maxResponseTime}ms`);
if (slowCalls.length > 0) {
console.log(` Slow Calls: ${slowCalls.length}`);
slowCalls.forEach(call => {
console.log(` ${call.url}: ${call.timing}ms (${call.status})`);
});
}
} else {
console.log(` ${endpoint.name}: No API calls detected`);
}
}
// Validate API performance
apiMetrics.forEach(metric => {
// Average API response time should meet baseline
if (metric.averageApiTime > 0) {
assertPerformanceBaseline(
metric.averageApiTime,
PerformanceBaselines.apiResponse,
`${metric.endpoint} average API response`,
expect
);
}
// Should not have many slow calls
const slowCallRatio = metric.slowCallCount / metric.apiCallCount;
expect(slowCallRatio, `${metric.endpoint} has too many slow API calls`)
.toBeLessThan(0.3); // Max 30% slow calls
// No API call should be extremely slow
expect(metric.maxApiTime, `${metric.endpoint} has extremely slow API call`)
.toBeLessThan(10000); // Max 10s
});
// Overall API performance summary
const totalApiCalls = apiMetrics.reduce((sum, m) => sum + m.apiCallCount, 0);
const totalSlowCalls = apiMetrics.reduce((sum, m) => sum + m.slowCallCount, 0);
const overallSlowRatio = totalSlowCalls / totalApiCalls;
console.log('📊 Overall API Performance Summary:');
console.log(` Total API Calls: ${totalApiCalls}`);
console.log(` Slow Calls: ${totalSlowCalls}`);
console.log(` Slow Call Rate: ${(overallSlowRatio * 100).toFixed(1)}%`);
expect(overallSlowRatio, 'Overall API performance degraded').toBeLessThan(0.25);
console.log('✅ API response time monitoring completed');
});
test('should detect memory leaks and resource usage patterns', async ({ page }) => {
console.log('🧠 Monitoring memory usage and detecting potential leaks...');
await authenticateWithRealCredentials(page);
const memorySnapshots = [];
const operations = [
{ name: 'Initial State', action: () => Promise.resolve() },
{ name: 'Company Selection', action: () => selectCompany(page, REAL_CREDENTIALS.company) },
{ name: 'Invoices Load', action: () => {
return page.goto('/invoices').then(() =>
page.waitForSelector('[data-testid="invoices-table"]', { timeout: 15000 })
);
}},
{ name: 'Payments Load', action: () => {
return page.goto('/payments').then(() =>
page.waitForSelector('[data-testid="payments-table"]', { timeout: 15000 })
);
}},
{ name: 'Dashboard Return', action: () => page.goto('/dashboard') },
{ name: 'Multiple Navigation Cycles', action: async () => {
for (let i = 0; i < 3; i++) {
await page.goto('/invoices');
await page.goto('/payments');
await page.goto('/dashboard');
}
}}
];
for (const operation of operations) {
console.log(`📊 Memory snapshot: ${operation.name}`);
await operation.action();
await page.waitForLoadState('networkidle', { timeout: 10000 });
// Get memory metrics
const memoryMetrics = await page.evaluate(() => {
// Force garbage collection if available (in dev environments)
if (window.gc && typeof window.gc === 'function') {
window.gc();
}
const performance = window.performance;
const memory = performance.memory || {};
return {
timestamp: Date.now(),
usedJSHeapSize: memory.usedJSHeapSize || 0,
totalJSHeapSize: memory.totalJSHeapSize || 0,
jsHeapSizeLimit: memory.jsHeapSizeLimit || 0,
// Additional performance metrics
navigation: performance.getEntriesByType('navigation')[0] || {},
resources: performance.getEntriesByType('resource').length
};
});
memorySnapshots.push({
operation: operation.name,
...memoryMetrics
});
if (memoryMetrics.usedJSHeapSize > 0) {
const memoryMB = (memoryMetrics.usedJSHeapSize / 1024 / 1024).toFixed(1);
console.log(` Used Memory: ${memoryMB}MB`);
console.log(` Resources: ${memoryMetrics.resources}`);
}
}
// Analyze memory usage patterns
if (memorySnapshots.length > 1 && memorySnapshots[0].usedJSHeapSize > 0) {
console.log('🔍 Memory Usage Analysis:');
// Check for significant memory increases
const initialMemory = memorySnapshots[0].usedJSHeapSize;
const finalMemory = memorySnapshots[memorySnapshots.length - 1].usedJSHeapSize;
const memoryIncrease = finalMemory - initialMemory;
const increaseRatio = memoryIncrease / initialMemory;
console.log(` Initial Memory: ${(initialMemory / 1024 / 1024).toFixed(1)}MB`);
console.log(` Final Memory: ${(finalMemory / 1024 / 1024).toFixed(1)}MB`);
console.log(` Memory Increase: ${(memoryIncrease / 1024 / 1024).toFixed(1)}MB`);
console.log(` Increase Ratio: ${(increaseRatio * 100).toFixed(1)}%`);
// Memory increase should be reasonable (< 100% growth)
expect(increaseRatio, 'Potential memory leak detected').toBeLessThan(1.0);
// Final memory should not be excessive (< 100MB for typical usage)
const finalMemoryMB = finalMemory / 1024 / 1024;
expect(finalMemoryMB, 'Excessive memory usage').toBeLessThan(100);
// Check for memory leaks by comparing before/after cycles
if (memorySnapshots.length >= 4) {
const beforeCycles = memorySnapshots[memorySnapshots.length - 3].usedJSHeapSize;
const afterCycles = memorySnapshots[memorySnapshots.length - 1].usedJSHeapSize;
const cycleIncrease = (afterCycles - beforeCycles) / beforeCycles;
if (cycleIncrease > 0.5) { // > 50% increase from cycles
console.warn(`⚠️ Potential memory leak from repeated operations: ${(cycleIncrease * 100).toFixed(1)}% increase`);
}
}
} else {
console.log(' Memory metrics not available in this environment');
}
console.log('✅ Memory usage monitoring completed');
});
});