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:
364
reports-app/frontend/tests/integration/README.md
Normal file
364
reports-app/frontend/tests/integration/README.md
Normal file
@@ -0,0 +1,364 @@
|
||||
# ROA2WEB Integration Tests with Console Error Monitoring
|
||||
|
||||
This directory contains comprehensive integration tests for the ROA2WEB application, implementing the full Playwright testing plan with console error monitoring and real Oracle data validation.
|
||||
|
||||
## 🎯 Overview
|
||||
|
||||
The integration test suite provides:
|
||||
|
||||
- **Console Error Monitoring** - Comprehensive tracking and classification of frontend errors
|
||||
- **Real Oracle Data Testing** - Integration tests using actual CONTAFIN_ORACLE credentials
|
||||
- **Performance Regression Testing** - Automated baseline validation and monitoring
|
||||
- **Cross-Schema Validation** - Testing data flow between Oracle schemas
|
||||
- **Health Monitoring** - Backend service and database connectivity validation
|
||||
|
||||
## 🏗️ Test Architecture
|
||||
|
||||
```
|
||||
tests/integration/
|
||||
├── real-auth/ # Real authentication tests
|
||||
│ └── oracle-login.spec.js # CONTAFIN_ORACLE credential testing
|
||||
├── real-data/ # Real data integration tests
|
||||
│ └── romfast-reports.spec.js # ROMFAST company data validation
|
||||
├── api-endpoints/ # Backend API validation
|
||||
│ ├── health-check.spec.js # Service health monitoring
|
||||
│ └── data-consistency.spec.js # Cross-schema data validation
|
||||
├── console-monitoring/ # Console error analysis
|
||||
│ ├── error-tracking.spec.js # Error pattern detection
|
||||
│ └── performance-monitoring.spec.js # Performance regression testing
|
||||
├── global-setup.js # Test environment setup
|
||||
├── global-teardown.js # Test cleanup
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## 🔧 Setup and Configuration
|
||||
|
||||
### 1. Environment Configuration
|
||||
|
||||
Copy the example environment file:
|
||||
```bash
|
||||
cp .env.test.example .env.test
|
||||
```
|
||||
|
||||
Edit `.env.test` with your database configuration:
|
||||
```bash
|
||||
# Oracle Database (through SSH tunnel)
|
||||
ORACLE_USER=CONTAFIN_ORACLE
|
||||
ORACLE_PASSWORD=your_password_here
|
||||
ORACLE_HOST=localhost
|
||||
ORACLE_PORT=1521
|
||||
ORACLE_SID=ROA
|
||||
|
||||
# Test Credentials
|
||||
TEST_USERNAME=MARIUS M
|
||||
TEST_PASSWORD=PAROLA9911
|
||||
TEST_COMPANY=ROMFAST
|
||||
```
|
||||
|
||||
### 2. Prerequisites
|
||||
|
||||
- **SSH Tunnel Active**: Oracle database accessible via SSH tunnel
|
||||
- **Backend Running**: FastAPI backend on port 8000
|
||||
- **Frontend Running**: Vue.js frontend on port 3001
|
||||
- **Node.js**: v16+ with npm
|
||||
- **Python**: v3.8+ with backend dependencies
|
||||
|
||||
### 3. Service Dependencies
|
||||
|
||||
Ensure all services are running:
|
||||
```bash
|
||||
# SSH Tunnel
|
||||
cd /path/to/roa2web
|
||||
./ssh_tunnel.sh start
|
||||
|
||||
# Backend
|
||||
cd reports-app/backend
|
||||
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
|
||||
|
||||
# Frontend
|
||||
cd reports-app/frontend
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## 🚀 Running Tests
|
||||
|
||||
### Quick Start - Comprehensive Test Suite
|
||||
|
||||
Run all tests with service management:
|
||||
```bash
|
||||
./run-comprehensive-tests.sh
|
||||
```
|
||||
|
||||
### Individual Test Categories
|
||||
|
||||
**Real Authentication Tests:**
|
||||
```bash
|
||||
npx playwright test tests/integration/real-auth/ --config=playwright.real-api.config.js
|
||||
```
|
||||
|
||||
**ROMFAST Data Integration:**
|
||||
```bash
|
||||
npx playwright test tests/integration/real-data/ --config=playwright.real-api.config.js
|
||||
```
|
||||
|
||||
**Console Error Monitoring:**
|
||||
```bash
|
||||
npx playwright test tests/integration/console-monitoring/ --config=playwright.real-api.config.js
|
||||
```
|
||||
|
||||
**Backend Health Monitoring:**
|
||||
```bash
|
||||
npx playwright test tests/integration/api-endpoints/ --config=playwright.real-api.config.js
|
||||
```
|
||||
|
||||
### Test Runner Options
|
||||
|
||||
```bash
|
||||
# Skip mock tests, run integration only
|
||||
./run-comprehensive-tests.sh --no-mock
|
||||
|
||||
# Skip integration tests, run mock only
|
||||
./run-comprehensive-tests.sh --no-integration
|
||||
|
||||
# Don't cleanup services (for debugging)
|
||||
./run-comprehensive-tests.sh --no-cleanup
|
||||
|
||||
# Skip report generation
|
||||
./run-comprehensive-tests.sh --no-reports
|
||||
```
|
||||
|
||||
## 📊 Console Error Monitoring
|
||||
|
||||
### Error Classification System
|
||||
|
||||
The test suite automatically classifies console messages:
|
||||
|
||||
- **CRITICAL**: Authentication failures, database errors, uncaught exceptions
|
||||
- **WARNING**: Network failures, 404 errors, component warnings
|
||||
- **INFO**: Development messages, HMR notifications, DevTools
|
||||
- **UNKNOWN**: Unclassified error patterns
|
||||
|
||||
### Performance Baselines
|
||||
|
||||
Automated validation against performance baselines:
|
||||
|
||||
```javascript
|
||||
PerformanceBaselines = {
|
||||
loginTime: 2000, // Max 2s for login
|
||||
dashboardLoad: 3000, // Max 3s for dashboard
|
||||
reportGeneration: 5000, // Max 5s for reports
|
||||
apiResponse: 1500, // Max 1.5s for API calls
|
||||
pageLoad: 4000 // Max 4s for page loads
|
||||
}
|
||||
```
|
||||
|
||||
### Error Pattern Detection
|
||||
|
||||
Automatic detection of recurring error patterns:
|
||||
- Failed fetch requests
|
||||
- 404 Not Found errors
|
||||
- JavaScript TypeErrors
|
||||
- Vue component warnings
|
||||
- Oracle connection issues
|
||||
|
||||
## 🔍 Test Categories
|
||||
|
||||
### Real Authentication Tests (`real-auth/`)
|
||||
|
||||
- **Oracle Login Testing**: Validates CONTAFIN_ORACLE authentication
|
||||
- **JWT Token Management**: Tests token storage and expiration
|
||||
- **Session Persistence**: Validates session across page reloads
|
||||
- **Invalid Credentials**: Tests error handling for bad credentials
|
||||
- **Performance Monitoring**: Measures auth response times
|
||||
|
||||
### Real Data Integration (`real-data/`)
|
||||
|
||||
- **ROMFAST Data Loading**: Tests real company data integration
|
||||
- **Invoice Schema Validation**: Validates Oracle invoice data fields
|
||||
- **Payment Data Testing**: Tests payment data structure and loading
|
||||
- **Data Filtering**: Tests search and filter functionality
|
||||
- **Dashboard Metrics**: Validates dashboard accuracy with real data
|
||||
- **Performance Under Load**: Tests data loading performance
|
||||
|
||||
### API Endpoint Validation (`api-endpoints/`)
|
||||
|
||||
- **Health Check Monitoring**: Validates backend and database health
|
||||
- **SSH Tunnel Dependency**: Tests tunnel connectivity requirements
|
||||
- **Error Rate Monitoring**: Tracks API error patterns
|
||||
- **Concurrent Load Testing**: Tests backend under concurrent requests
|
||||
- **Resource Usage Monitoring**: Detects memory leaks and resource issues
|
||||
- **Cross-Schema Data Validation**: Tests Oracle schema relationships
|
||||
|
||||
### Console Error Analysis (`console-monitoring/`)
|
||||
|
||||
- **Error Pattern Detection**: Identifies recurring error patterns
|
||||
- **Performance Warning Detection**: Monitors performance-related warnings
|
||||
- **Error Context Analysis**: Provides debugging context for errors
|
||||
- **Comprehensive Error Reporting**: Generates detailed error reports
|
||||
- **Memory Leak Detection**: Monitors JavaScript memory usage
|
||||
- **Performance Regression Testing**: Validates performance consistency
|
||||
|
||||
## 📈 Performance Monitoring
|
||||
|
||||
### Metrics Collected
|
||||
|
||||
- **Page Load Times**: DOM content loaded, first paint, interactive
|
||||
- **API Response Times**: Individual and average response times
|
||||
- **Network Resource Timing**: Resource loading performance
|
||||
- **Memory Usage**: JavaScript heap size monitoring
|
||||
- **Error Frequencies**: Console error occurrence rates
|
||||
|
||||
### Regression Detection
|
||||
|
||||
- **Baseline Validation**: Automatic comparison against performance baselines
|
||||
- **Consistency Analysis**: Validates performance across multiple runs
|
||||
- **Outlier Detection**: Identifies abnormal performance spikes
|
||||
- **Trend Analysis**: Monitors performance degradation over time
|
||||
|
||||
## 🏥 Health Monitoring
|
||||
|
||||
### Service Health Checks
|
||||
|
||||
- **Backend Health**: `/health` endpoint validation
|
||||
- **Database Connectivity**: Oracle connection through SSH tunnel
|
||||
- **Frontend Availability**: Vue.js application responsiveness
|
||||
- **SSH Tunnel Status**: Tunnel connectivity validation
|
||||
|
||||
### Error Handling Validation
|
||||
|
||||
- **Graceful Degradation**: Tests behavior during service failures
|
||||
- **Error Message Clarity**: Validates user-facing error messages
|
||||
- **Recovery Mechanisms**: Tests automatic recovery from failures
|
||||
- **Fallback Behavior**: Validates fallback when services unavailable
|
||||
|
||||
## 📋 Test Reports
|
||||
|
||||
### Report Types Generated
|
||||
|
||||
- **HTML Reports**: Interactive test results with screenshots
|
||||
- **JSON Reports**: Machine-readable test data
|
||||
- **JUnit Reports**: CI/CD integration format
|
||||
- **Console Error Reports**: Detailed error analysis
|
||||
- **Performance Reports**: Performance metrics and trends
|
||||
|
||||
### Report Locations
|
||||
|
||||
```
|
||||
test-results/
|
||||
├── playwright-report-integration/ # HTML reports
|
||||
├── integration-results.json # JSON results
|
||||
├── integration-junit.xml # JUnit format
|
||||
├── integration-summary.json # Combined summary
|
||||
└── reports/ # Additional reports
|
||||
└── comprehensive-test-report-*.json
|
||||
```
|
||||
|
||||
## 🔧 Debugging and Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**SSH Tunnel Not Running:**
|
||||
```bash
|
||||
cd /path/to/roa2web
|
||||
./ssh_tunnel.sh status
|
||||
./ssh_tunnel.sh start
|
||||
```
|
||||
|
||||
**Backend Not Accessible:**
|
||||
```bash
|
||||
curl http://localhost:8000/health
|
||||
# Should return: {"database":"connected","api":"healthy"}
|
||||
```
|
||||
|
||||
**Frontend Not Running:**
|
||||
```bash
|
||||
curl http://localhost:3001
|
||||
# Should return HTML content
|
||||
```
|
||||
|
||||
**Oracle Connection Issues:**
|
||||
- Verify SSH tunnel is active
|
||||
- Check Oracle credentials in environment
|
||||
- Validate database accessibility through tunnel
|
||||
|
||||
### Debug Mode
|
||||
|
||||
Run tests with detailed logging:
|
||||
```bash
|
||||
DEBUG=1 npx playwright test --config=playwright.real-api.config.js
|
||||
```
|
||||
|
||||
View console messages during test execution:
|
||||
```bash
|
||||
npx playwright test --headed --config=playwright.real-api.config.js
|
||||
```
|
||||
|
||||
### Log Files
|
||||
|
||||
Test execution logs are available in:
|
||||
- `frontend.log` - Frontend service logs
|
||||
- `backend.log` - Backend service logs
|
||||
- `test-results/*.log` - Individual test logs
|
||||
|
||||
## 🎯 Success Criteria
|
||||
|
||||
### Passing Integration Tests
|
||||
|
||||
- ✅ Zero critical console errors with real data
|
||||
- ✅ Response times under baseline thresholds
|
||||
- ✅ 100% coverage for ROMFAST company flows
|
||||
- ✅ Automatic error pattern detection working
|
||||
- ✅ Performance baselines consistently met
|
||||
- ✅ Cross-schema data validation passing
|
||||
- ✅ Health monitoring detecting issues correctly
|
||||
|
||||
### Performance Requirements
|
||||
|
||||
- Login: < 2 seconds
|
||||
- Dashboard load: < 3 seconds
|
||||
- Report generation: < 5 seconds
|
||||
- API responses: < 1.5 seconds
|
||||
- Page loads: < 4 seconds
|
||||
|
||||
### Error Tolerance
|
||||
|
||||
- Critical errors: 0 allowed
|
||||
- Warning errors: < 10 per test run
|
||||
- Performance degradation: < 50% variance
|
||||
- Memory leaks: < 100% memory growth
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
### Adding New Integration Tests
|
||||
|
||||
1. Create test file in appropriate category directory
|
||||
2. Import required utilities from `../../utils/`
|
||||
3. Use `setupConsoleCapture(page)` in `beforeEach`
|
||||
4. Use `assertNoCriticalErrors(page, expect)` for validation
|
||||
5. Generate error reports in `afterEach`
|
||||
|
||||
### Test Utilities Available
|
||||
|
||||
- `setupConsoleCapture()` - Console monitoring setup
|
||||
- `authenticateWithRealCredentials()` - Real Oracle authentication
|
||||
- `selectCompany()` - Company selection helper
|
||||
- `assertNoCriticalErrors()` - Error validation
|
||||
- `generateErrorReport()` - Comprehensive error reporting
|
||||
- `PerformanceMonitor` - Performance measurement utilities
|
||||
|
||||
### Best Practices
|
||||
|
||||
- Always use real credentials for integration tests
|
||||
- Monitor console errors in all tests
|
||||
- Validate performance against baselines
|
||||
- Generate comprehensive error reports
|
||||
- Test with actual Oracle data when possible
|
||||
- Clean up test state between runs
|
||||
|
||||
---
|
||||
|
||||
**Integration Test Status**: ✅ Fully Implemented
|
||||
**Console Monitoring**: ✅ Active
|
||||
**Real Data Testing**: ✅ ROMFAST Validated
|
||||
**Performance Monitoring**: ✅ Baseline Tracking
|
||||
**Error Analysis**: ✅ Pattern Detection Active
|
||||
@@ -0,0 +1,391 @@
|
||||
/**
|
||||
* Cross-Schema Data Validation Tests
|
||||
* Validates data consistency between CONTAFIN_ORACLE authentication schema
|
||||
* and ROMFAST company data, ensuring proper Oracle data flow
|
||||
*/
|
||||
|
||||
import { test, expect } from '@playwright/test';
|
||||
import {
|
||||
authenticateWithRealCredentials,
|
||||
getRealCompanies,
|
||||
selectCompany,
|
||||
REAL_CREDENTIALS,
|
||||
API_ENDPOINTS
|
||||
} from '../../utils/real-auth.js';
|
||||
import {
|
||||
setupConsoleCapture,
|
||||
assertNoCriticalErrors,
|
||||
generateErrorReport
|
||||
} from '../../utils/console-monitor.js';
|
||||
|
||||
test.describe('Oracle Cross-Schema Data Consistency', () => {
|
||||
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 errors in data consistency test:', report.details.criticalErrors);
|
||||
}
|
||||
});
|
||||
|
||||
test('should validate CONTAFIN_ORACLE → ROMFAST data flow', async ({ page }) => {
|
||||
console.log('🔄 Testing Oracle cross-schema data flow...');
|
||||
|
||||
// Authenticate with CONTAFIN_ORACLE schema credentials
|
||||
const authResult = await authenticateWithRealCredentials(page);
|
||||
expect(authResult.success, 'CONTAFIN_ORACLE authentication failed').toBe(true);
|
||||
|
||||
console.log('✅ CONTAFIN_ORACLE authentication successful');
|
||||
|
||||
// Test companies endpoint returns ROMFAST from NOM_FIRME table
|
||||
console.log('🏢 Validating companies data from NOM_FIRME...');
|
||||
const companiesResponse = await page.request.get(`${API_ENDPOINTS.backend}${API_ENDPOINTS.companies}`);
|
||||
expect(companiesResponse.status()).toBe(200);
|
||||
|
||||
const companies = await companiesResponse.json();
|
||||
expect(companies).toBeInstanceOf(Array);
|
||||
expect(companies.length).toBeGreaterThan(0);
|
||||
|
||||
console.log(`📊 Found ${companies.length} companies in NOM_FIRME table`);
|
||||
|
||||
// Validate ROMFAST company exists
|
||||
const romfast = companies.find(c => c.id_firma === 'ROMFAST');
|
||||
expect(romfast, 'ROMFAST company not found in NOM_FIRME table').toBeDefined();
|
||||
expect(romfast.name).toContain('ROMFAST');
|
||||
|
||||
console.log('✅ ROMFAST company validated in NOM_FIRME table:', romfast);
|
||||
|
||||
// Validate company data structure matches Oracle schema
|
||||
expect(romfast).toHaveProperty('id_firma');
|
||||
expect(romfast).toHaveProperty('name');
|
||||
|
||||
// Additional Oracle-specific fields that might be present
|
||||
const oracleFields = ['cui', 'reg_com', 'adresa', 'telefon', 'email'];
|
||||
oracleFields.forEach(field => {
|
||||
if (romfast.hasOwnProperty(field)) {
|
||||
console.log(`ℹ️ Oracle field '${field}' present:`, romfast[field]);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('✅ Cross-schema authentication and company data flow validated');
|
||||
});
|
||||
|
||||
test('should validate invoice schema consistency', async ({ page }) => {
|
||||
console.log('📋 Validating invoice data schema consistency...');
|
||||
|
||||
await authenticateWithRealCredentials(page);
|
||||
await selectCompany(page, REAL_CREDENTIALS.company);
|
||||
|
||||
// Get invoices data for ROMFAST
|
||||
console.log('📥 Fetching ROMFAST invoice data...');
|
||||
const invoicesResponse = await page.request.get(`${API_ENDPOINTS.backend}/api/invoices/ROMFAST`);
|
||||
expect(invoicesResponse.status()).toBe(200);
|
||||
|
||||
const invoicesData = await invoicesResponse.json();
|
||||
expect(invoicesData).toHaveProperty('data');
|
||||
expect(invoicesData.data).toBeInstanceOf(Array);
|
||||
|
||||
if (invoicesData.data.length > 0) {
|
||||
const sampleInvoice = invoicesData.data[0];
|
||||
console.log('📋 Sample invoice structure:', Object.keys(sampleInvoice));
|
||||
|
||||
// Validate Oracle-specific invoice fields are present
|
||||
const requiredOracleFields = [
|
||||
'numar_factura', // Invoice number
|
||||
'data_scadenta', // Due date
|
||||
'suma_totala' // Total amount
|
||||
];
|
||||
|
||||
requiredOracleFields.forEach(field => {
|
||||
expect(sampleInvoice, `Missing Oracle field: ${field}`).toHaveProperty(field);
|
||||
console.log(`✅ Oracle field '${field}':`, sampleInvoice[field]);
|
||||
});
|
||||
|
||||
// Validate data types
|
||||
expect(typeof sampleInvoice.numar_factura).toBe('string');
|
||||
expect(sampleInvoice.suma_totala).toBeGreaterThanOrEqual(0);
|
||||
|
||||
// Validate date format (should be ISO string or valid date)
|
||||
if (sampleInvoice.data_scadenta) {
|
||||
const date = new Date(sampleInvoice.data_scadenta);
|
||||
expect(date.toString()).not.toBe('Invalid Date');
|
||||
console.log(`✅ Date validation passed: ${sampleInvoice.data_scadenta}`);
|
||||
}
|
||||
|
||||
console.log(`✅ Invoice schema validation passed (${invoicesData.data.length} invoices)`);
|
||||
} else {
|
||||
console.log('ℹ️ No invoice data found for ROMFAST - schema validation skipped');
|
||||
}
|
||||
|
||||
// Check for console errors during data retrieval
|
||||
assertNoCriticalErrors(page, expect);
|
||||
});
|
||||
|
||||
test('should validate payment schema consistency', async ({ page }) => {
|
||||
console.log('💰 Validating payment data schema consistency...');
|
||||
|
||||
await authenticateWithRealCredentials(page);
|
||||
await selectCompany(page, REAL_CREDENTIALS.company);
|
||||
|
||||
// Get payments data for ROMFAST
|
||||
console.log('💳 Fetching ROMFAST payment data...');
|
||||
const paymentsResponse = await page.request.get(`${API_ENDPOINTS.backend}/api/payments/ROMFAST`);
|
||||
expect(paymentsResponse.status()).toBe(200);
|
||||
|
||||
const paymentsData = await paymentsResponse.json();
|
||||
expect(paymentsData).toHaveProperty('data');
|
||||
expect(paymentsData.data).toBeInstanceOf(Array);
|
||||
|
||||
if (paymentsData.data.length > 0) {
|
||||
const samplePayment = paymentsData.data[0];
|
||||
console.log('💳 Sample payment structure:', Object.keys(samplePayment));
|
||||
|
||||
// Validate Oracle-specific payment fields
|
||||
const requiredOracleFields = [
|
||||
'numar_plata', // Payment number
|
||||
'data_plata', // Payment date
|
||||
'suma_plata' // Payment amount
|
||||
];
|
||||
|
||||
requiredOracleFields.forEach(field => {
|
||||
expect(samplePayment, `Missing Oracle payment field: ${field}`).toHaveProperty(field);
|
||||
console.log(`✅ Oracle payment field '${field}':`, samplePayment[field]);
|
||||
});
|
||||
|
||||
// Validate payment data types
|
||||
expect(typeof samplePayment.numar_plata).toBe('string');
|
||||
expect(samplePayment.suma_plata).toBeGreaterThanOrEqual(0);
|
||||
|
||||
// Validate payment date
|
||||
if (samplePayment.data_plata) {
|
||||
const date = new Date(samplePayment.data_plata);
|
||||
expect(date.toString()).not.toBe('Invalid Date');
|
||||
console.log(`✅ Payment date validation passed: ${samplePayment.data_plata}`);
|
||||
}
|
||||
|
||||
console.log(`✅ Payment schema validation passed (${paymentsData.data.length} payments)`);
|
||||
} else {
|
||||
console.log('ℹ️ No payment data found for ROMFAST - schema validation skipped');
|
||||
}
|
||||
|
||||
// Check for console errors during data retrieval
|
||||
assertNoCriticalErrors(page, expect);
|
||||
});
|
||||
|
||||
test('should validate user permissions across schemas', async ({ page }) => {
|
||||
console.log('🔐 Validating user permissions across Oracle schemas...');
|
||||
|
||||
const authResult = await authenticateWithRealCredentials(page);
|
||||
expect(authResult.success).toBe(true);
|
||||
|
||||
// Test access to different endpoints with authenticated user
|
||||
const endpointsToTest = [
|
||||
{ url: '/api/companies', name: 'Companies List', expectAccess: true },
|
||||
{ url: '/api/invoices/ROMFAST', name: 'ROMFAST Invoices', expectAccess: true },
|
||||
{ url: '/api/payments/ROMFAST', name: 'ROMFAST Payments', expectAccess: true },
|
||||
{ url: '/api/user/profile', name: 'User Profile', expectAccess: true },
|
||||
{ url: '/api/admin/users', name: 'Admin Users', expectAccess: false } // Should fail
|
||||
];
|
||||
|
||||
const accessResults = [];
|
||||
|
||||
for (const endpoint of endpointsToTest) {
|
||||
console.log(`🔍 Testing access to ${endpoint.name}...`);
|
||||
|
||||
try {
|
||||
const response = await page.request.get(`${API_ENDPOINTS.backend}${endpoint.url}`);
|
||||
const hasAccess = response.status() < 400;
|
||||
|
||||
accessResults.push({
|
||||
endpoint: endpoint.name,
|
||||
url: endpoint.url,
|
||||
status: response.status(),
|
||||
hasAccess: hasAccess,
|
||||
expectAccess: endpoint.expectAccess
|
||||
});
|
||||
|
||||
if (endpoint.expectAccess) {
|
||||
expect(hasAccess, `Expected access to ${endpoint.name} but got ${response.status()}`).toBe(true);
|
||||
console.log(`✅ ${endpoint.name}: Access granted (${response.status()})`);
|
||||
} else {
|
||||
expect(hasAccess, `Expected no access to ${endpoint.name} but got ${response.status()}`).toBe(false);
|
||||
console.log(`✅ ${endpoint.name}: Access denied as expected (${response.status()})`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.log(`⚠️ ${endpoint.name}: Request failed - ${error.message}`);
|
||||
accessResults.push({
|
||||
endpoint: endpoint.name,
|
||||
url: endpoint.url,
|
||||
error: error.message,
|
||||
hasAccess: false,
|
||||
expectAccess: endpoint.expectAccess
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Summary of access results
|
||||
console.log('📊 Permission Validation Summary:');
|
||||
accessResults.forEach(result => {
|
||||
const status = result.hasAccess === result.expectAccess ? '✅' : '❌';
|
||||
console.log(` ${status} ${result.endpoint}: ${result.hasAccess ? 'Access' : 'No Access'}`);
|
||||
});
|
||||
|
||||
console.log('✅ User permission validation completed');
|
||||
});
|
||||
|
||||
test('should validate data relationships between tables', async ({ page }) => {
|
||||
console.log('🔗 Validating data relationships between Oracle tables...');
|
||||
|
||||
await authenticateWithRealCredentials(page);
|
||||
|
||||
// Get company data
|
||||
const companiesResponse = await page.request.get(`${API_ENDPOINTS.backend}${API_ENDPOINTS.companies}`);
|
||||
const companies = await companiesResponse.json();
|
||||
const romfast = companies.find(c => c.id_firma === 'ROMFAST');
|
||||
|
||||
expect(romfast).toBeDefined();
|
||||
|
||||
// Get invoices for ROMFAST
|
||||
const invoicesResponse = await page.request.get(`${API_ENDPOINTS.backend}/api/invoices/ROMFAST`);
|
||||
const invoicesData = await invoicesResponse.json();
|
||||
|
||||
// Get payments for ROMFAST
|
||||
const paymentsResponse = await page.request.get(`${API_ENDPOINTS.backend}/api/payments/ROMFAST`);
|
||||
const paymentsData = await paymentsResponse.json();
|
||||
|
||||
console.log('📊 Data relationship summary:');
|
||||
console.log(` Company: ${romfast.name} (${romfast.id_firma})`);
|
||||
console.log(` Invoices: ${invoicesData.data?.length || 0}`);
|
||||
console.log(` Payments: ${paymentsData.data?.length || 0}`);
|
||||
|
||||
// Validate referential integrity
|
||||
if (invoicesData.data && invoicesData.data.length > 0) {
|
||||
const sampleInvoice = invoicesData.data[0];
|
||||
|
||||
// Invoice should reference the company
|
||||
if (sampleInvoice.cod_firma) {
|
||||
expect(sampleInvoice.cod_firma).toBe(romfast.id_firma);
|
||||
console.log('✅ Invoice-Company relationship validated');
|
||||
}
|
||||
|
||||
// Check if there are related payments
|
||||
if (paymentsData.data && paymentsData.data.length > 0) {
|
||||
console.log('✅ Payment data exists for company with invoices');
|
||||
|
||||
// Look for potential invoice-payment relationships
|
||||
const samplePayment = paymentsData.data[0];
|
||||
if (samplePayment.cod_firma) {
|
||||
expect(samplePayment.cod_firma).toBe(romfast.id_firma);
|
||||
console.log('✅ Payment-Company relationship validated');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate data consistency
|
||||
const totalInvoicesFromApi = invoicesData.data?.length || 0;
|
||||
const totalPaymentsFromApi = paymentsData.data?.length || 0;
|
||||
|
||||
// These should be reasonable numbers for a real company
|
||||
if (totalInvoicesFromApi > 0) {
|
||||
console.log(`✅ Company has ${totalInvoicesFromApi} invoices`);
|
||||
}
|
||||
|
||||
if (totalPaymentsFromApi > 0) {
|
||||
console.log(`✅ Company has ${totalPaymentsFromApi} payments`);
|
||||
}
|
||||
|
||||
// Check for console errors during relationship validation
|
||||
assertNoCriticalErrors(page, expect);
|
||||
|
||||
console.log('✅ Data relationship validation completed');
|
||||
});
|
||||
|
||||
test('should validate Oracle connection persistence during operations', async ({ page }) => {
|
||||
console.log('🔄 Testing Oracle connection persistence...');
|
||||
|
||||
await authenticateWithRealCredentials(page);
|
||||
|
||||
// Perform multiple operations to test connection persistence
|
||||
const operations = [
|
||||
{ name: 'Companies Load', action: () => page.request.get(`${API_ENDPOINTS.backend}${API_ENDPOINTS.companies}`) },
|
||||
{ name: 'Invoice Load', action: () => page.request.get(`${API_ENDPOINTS.backend}/api/invoices/ROMFAST`) },
|
||||
{ name: 'Payment Load', action: () => page.request.get(`${API_ENDPOINTS.backend}/api/payments/ROMFAST`) },
|
||||
{ name: 'Health Check', action: () => page.request.get(`${API_ENDPOINTS.backend}${API_ENDPOINTS.health}`) }
|
||||
];
|
||||
|
||||
const connectionResults = [];
|
||||
|
||||
for (let cycle = 1; cycle <= 3; cycle++) {
|
||||
console.log(`🔄 Connection persistence test cycle ${cycle}/3`);
|
||||
|
||||
for (const operation of operations) {
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
const response = await operation.action();
|
||||
const responseTime = Date.now() - startTime;
|
||||
const success = response.status() < 400;
|
||||
|
||||
connectionResults.push({
|
||||
cycle,
|
||||
operation: operation.name,
|
||||
success,
|
||||
status: response.status(),
|
||||
responseTime
|
||||
});
|
||||
|
||||
if (success) {
|
||||
console.log(` ✅ ${operation.name}: ${response.status()} (${responseTime}ms)`);
|
||||
} else {
|
||||
console.log(` ❌ ${operation.name}: ${response.status()} (${responseTime}ms)`);
|
||||
}
|
||||
|
||||
// Brief delay between operations
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
} catch (error) {
|
||||
console.log(` ❌ ${operation.name}: ${error.message}`);
|
||||
connectionResults.push({
|
||||
cycle,
|
||||
operation: operation.name,
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Delay between cycles
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
}
|
||||
|
||||
// Analyze connection persistence
|
||||
const totalOperations = connectionResults.length;
|
||||
const successfulOperations = connectionResults.filter(r => r.success).length;
|
||||
const successRate = (successfulOperations / totalOperations) * 100;
|
||||
|
||||
console.log('📊 Connection Persistence Analysis:');
|
||||
console.log(` Total Operations: ${totalOperations}`);
|
||||
console.log(` Successful: ${successfulOperations}`);
|
||||
console.log(` Success Rate: ${successRate.toFixed(1)}%`);
|
||||
|
||||
// Connection should be persistent (>90% success rate)
|
||||
expect(successRate, 'Oracle connection not persistent enough').toBeGreaterThan(90);
|
||||
|
||||
// No connection should fail in the same cycle
|
||||
const cycleFailures = {};
|
||||
connectionResults.filter(r => !r.success).forEach(r => {
|
||||
cycleFailures[r.cycle] = (cycleFailures[r.cycle] || 0) + 1;
|
||||
});
|
||||
|
||||
Object.entries(cycleFailures).forEach(([cycle, failures]) => {
|
||||
if (failures === operations.length) {
|
||||
throw new Error(`Complete connection failure in cycle ${cycle}`);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('✅ Oracle connection persistence validated');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,409 @@
|
||||
/**
|
||||
* Backend Health Monitoring Integration Tests
|
||||
* Validates backend services, database connectivity, and error handling
|
||||
* Monitors system health through comprehensive endpoint testing
|
||||
*/
|
||||
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { API_ENDPOINTS } from '../../utils/real-auth.js';
|
||||
import {
|
||||
setupConsoleCapture,
|
||||
assertNoCriticalErrors,
|
||||
generateErrorReport,
|
||||
PerformanceBaselines,
|
||||
assertPerformanceBaseline
|
||||
} from '../../utils/console-monitor.js';
|
||||
|
||||
test.describe('Backend Health Monitoring', () => {
|
||||
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 errors in health monitoring:', report.details.criticalErrors);
|
||||
}
|
||||
});
|
||||
|
||||
test('should validate database connectivity through health endpoint', async ({ page }) => {
|
||||
console.log('🏥 Testing backend health endpoint...');
|
||||
|
||||
const healthStartTime = Date.now();
|
||||
const response = await page.request.get(`${API_ENDPOINTS.backend}${API_ENDPOINTS.health}`);
|
||||
const healthResponseTime = Date.now() - healthStartTime;
|
||||
|
||||
// Validate response
|
||||
expect(response.status()).toBe(200);
|
||||
|
||||
const health = await response.json();
|
||||
console.log('📊 Health response:', health);
|
||||
|
||||
// Validate health data structure
|
||||
expect(health).toHaveProperty('database');
|
||||
expect(health).toHaveProperty('api');
|
||||
expect(health).toHaveProperty('timestamp');
|
||||
|
||||
// Database should be connected (Oracle via SSH tunnel)
|
||||
expect(health.database).toBe('connected');
|
||||
expect(health.api).toBe('healthy');
|
||||
|
||||
// Validate response time
|
||||
assertPerformanceBaseline(
|
||||
healthResponseTime,
|
||||
1000, // Max 1s for health check
|
||||
'Health endpoint response',
|
||||
expect
|
||||
);
|
||||
|
||||
console.log(`✅ Backend health validated in ${healthResponseTime}ms`);
|
||||
});
|
||||
|
||||
test('should validate SSH tunnel dependency in health check', async ({ page }) => {
|
||||
console.log('🔐 Testing SSH tunnel dependency validation...');
|
||||
|
||||
const response = await page.request.get(`${API_ENDPOINTS.backend}${API_ENDPOINTS.health}`);
|
||||
expect(response.status()).toBe(200);
|
||||
|
||||
const health = await response.json();
|
||||
|
||||
// Should indicate SSH tunnel status
|
||||
if (health.ssh_tunnel !== undefined) {
|
||||
expect(health.ssh_tunnel).toBe('active');
|
||||
console.log('✅ SSH tunnel status confirmed in health check');
|
||||
}
|
||||
|
||||
// Database connection implies SSH tunnel is working
|
||||
expect(health.database).toBe('connected');
|
||||
|
||||
console.log('✅ SSH tunnel dependency validated through database connectivity');
|
||||
});
|
||||
|
||||
test('should handle Oracle connection failures gracefully', async ({ page }) => {
|
||||
console.log('💥 Testing Oracle connection failure handling...');
|
||||
|
||||
// First verify normal operation
|
||||
const normalResponse = await page.request.get(`${API_ENDPOINTS.backend}${API_ENDPOINTS.health}`);
|
||||
expect(normalResponse.status()).toBe(200);
|
||||
|
||||
// Navigate to app to test error handling in UI
|
||||
await page.goto('/dashboard');
|
||||
|
||||
// Mock database connection failure
|
||||
await page.route('**/api/**', async (route) => {
|
||||
if (route.request().url().includes('/health')) {
|
||||
await route.fulfill({
|
||||
status: 503,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
database: 'disconnected',
|
||||
api: 'degraded',
|
||||
error: 'Oracle connection failed'
|
||||
})
|
||||
});
|
||||
} else {
|
||||
await route.continue();
|
||||
}
|
||||
});
|
||||
|
||||
// Trigger health check from frontend
|
||||
await page.reload();
|
||||
|
||||
// Should show appropriate error handling
|
||||
const errorElements = await page.locator('[data-testid*="error"], [data-testid*="warning"]').count();
|
||||
|
||||
if (errorElements > 0) {
|
||||
console.log(`🚨 Found ${errorElements} error indicators in UI`);
|
||||
}
|
||||
|
||||
// Check console for appropriate error messages (not critical failures)
|
||||
const consoleMessages = page.consoleMessages || [];
|
||||
const dbErrors = consoleMessages.filter(msg =>
|
||||
msg.text.includes('database') || msg.text.includes('Oracle') || msg.text.includes('503')
|
||||
);
|
||||
|
||||
expect(dbErrors.length).toBeGreaterThan(0);
|
||||
console.log(`📝 Found ${dbErrors.length} database-related console messages`);
|
||||
|
||||
// Should not crash the application
|
||||
const currentUrl = page.url();
|
||||
expect(currentUrl).not.toContain('/error');
|
||||
|
||||
console.log('✅ Oracle connection failure handled gracefully');
|
||||
});
|
||||
|
||||
test('should validate API endpoint availability and response times', async ({ page }) => {
|
||||
console.log('📡 Testing API endpoint availability...');
|
||||
|
||||
const endpoints = [
|
||||
{ path: API_ENDPOINTS.health, name: 'Health Check', maxTime: 1000 },
|
||||
{ path: API_ENDPOINTS.companies, name: 'Companies', maxTime: 2000 },
|
||||
{ path: `${API_ENDPOINTS.invoices}/ROMFAST`, name: 'ROMFAST Invoices', maxTime: 3000 },
|
||||
{ path: `${API_ENDPOINTS.payments}/ROMFAST`, name: 'ROMFAST Payments', maxTime: 3000 }
|
||||
];
|
||||
|
||||
const results = [];
|
||||
|
||||
for (const endpoint of endpoints) {
|
||||
console.log(`🔍 Testing ${endpoint.name} endpoint...`);
|
||||
|
||||
const startTime = Date.now();
|
||||
try {
|
||||
const response = await page.request.get(`${API_ENDPOINTS.backend}${endpoint.path}`);
|
||||
const responseTime = Date.now() - startTime;
|
||||
|
||||
const result = {
|
||||
name: endpoint.name,
|
||||
path: endpoint.path,
|
||||
status: response.status(),
|
||||
responseTime,
|
||||
success: response.ok(),
|
||||
maxTime: endpoint.maxTime
|
||||
};
|
||||
|
||||
results.push(result);
|
||||
|
||||
if (response.ok()) {
|
||||
console.log(`✅ ${endpoint.name}: ${result.status} (${responseTime}ms)`);
|
||||
|
||||
// Validate response time
|
||||
assertPerformanceBaseline(
|
||||
responseTime,
|
||||
endpoint.maxTime,
|
||||
`${endpoint.name} response time`,
|
||||
expect
|
||||
);
|
||||
} else {
|
||||
console.warn(`⚠️ ${endpoint.name}: ${result.status} (${responseTime}ms)`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ ${endpoint.name} failed:`, error.message);
|
||||
results.push({
|
||||
name: endpoint.name,
|
||||
path: endpoint.path,
|
||||
error: error.message,
|
||||
success: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Summary
|
||||
const successCount = results.filter(r => r.success).length;
|
||||
const totalEndpoints = endpoints.length;
|
||||
|
||||
console.log(`📊 API Endpoint Summary: ${successCount}/${totalEndpoints} successful`);
|
||||
|
||||
// At least health and companies endpoints should work
|
||||
const criticalEndpoints = results.filter(r =>
|
||||
r.name === 'Health Check' || r.name === 'Companies'
|
||||
);
|
||||
const criticalSuccess = criticalEndpoints.filter(r => r.success).length;
|
||||
|
||||
expect(criticalSuccess).toBe(2); // Both critical endpoints must work
|
||||
|
||||
console.log('✅ Critical API endpoints validated');
|
||||
});
|
||||
|
||||
test('should monitor backend error rates and patterns', async ({ page }) => {
|
||||
console.log('📈 Monitoring backend error rates...');
|
||||
|
||||
const testRequests = [
|
||||
{ url: `${API_ENDPOINTS.backend}${API_ENDPOINTS.health}`, expected: 200 },
|
||||
{ url: `${API_ENDPOINTS.backend}${API_ENDPOINTS.companies}`, expected: 200 },
|
||||
{ url: `${API_ENDPOINTS.backend}/api/nonexistent`, expected: 404 },
|
||||
{ url: `${API_ENDPOINTS.backend}/api/invoices/INVALID_COMPANY`, expected: 404 }
|
||||
];
|
||||
|
||||
const errorPatterns = {};
|
||||
let totalRequests = 0;
|
||||
let errorCount = 0;
|
||||
|
||||
for (const request of testRequests) {
|
||||
totalRequests++;
|
||||
console.log(`📤 Testing: ${request.url}`);
|
||||
|
||||
try {
|
||||
const response = await page.request.get(request.url);
|
||||
const status = response.status();
|
||||
|
||||
if (status !== request.expected) {
|
||||
errorCount++;
|
||||
const pattern = `${Math.floor(status / 100)}xx`;
|
||||
errorPatterns[pattern] = (errorPatterns[pattern] || 0) + 1;
|
||||
|
||||
console.warn(`⚠️ Unexpected status: ${status} (expected ${request.expected})`);
|
||||
} else {
|
||||
console.log(`✅ Expected status: ${status}`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
errorCount++;
|
||||
errorPatterns['network'] = (errorPatterns['network'] || 0) + 1;
|
||||
console.error(`❌ Network error:`, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
const errorRate = (errorCount / totalRequests) * 100;
|
||||
|
||||
console.log(`📊 Error Analysis:`);
|
||||
console.log(` Total Requests: ${totalRequests}`);
|
||||
console.log(` Errors: ${errorCount}`);
|
||||
console.log(` Error Rate: ${errorRate.toFixed(1)}%`);
|
||||
console.log(` Error Patterns:`, errorPatterns);
|
||||
|
||||
// Error rate should be reasonable (some 404s expected)
|
||||
expect(errorRate).toBeLessThan(75); // Allow some expected errors
|
||||
|
||||
// Should not have network errors
|
||||
expect(errorPatterns.network || 0).toBe(0);
|
||||
|
||||
console.log('✅ Backend error monitoring completed');
|
||||
});
|
||||
|
||||
test('should validate backend performance under concurrent requests', async ({ page }) => {
|
||||
console.log('⚡ Testing backend performance under load...');
|
||||
|
||||
const concurrentRequests = 5;
|
||||
const requestsPerBatch = 3;
|
||||
const totalBatches = concurrentRequests;
|
||||
|
||||
const performanceResults = [];
|
||||
|
||||
for (let batch = 0; batch < totalBatches; batch++) {
|
||||
console.log(`🔄 Batch ${batch + 1}/${totalBatches}`);
|
||||
|
||||
const batchStartTime = Date.now();
|
||||
const batchPromises = [];
|
||||
|
||||
// Create concurrent requests
|
||||
for (let req = 0; req < requestsPerBatch; req++) {
|
||||
const requestPromise = page.request.get(`${API_ENDPOINTS.backend}${API_ENDPOINTS.health}`)
|
||||
.then(response => ({
|
||||
status: response.status(),
|
||||
timing: Date.now() - batchStartTime
|
||||
}))
|
||||
.catch(error => ({
|
||||
error: error.message,
|
||||
timing: Date.now() - batchStartTime
|
||||
}));
|
||||
|
||||
batchPromises.push(requestPromise);
|
||||
}
|
||||
|
||||
// Wait for all requests in batch
|
||||
const batchResults = await Promise.all(batchPromises);
|
||||
const batchTime = Date.now() - batchStartTime;
|
||||
|
||||
performanceResults.push({
|
||||
batch: batch + 1,
|
||||
results: batchResults,
|
||||
totalTime: batchTime,
|
||||
successful: batchResults.filter(r => r.status === 200).length
|
||||
});
|
||||
|
||||
console.log(`⏱️ Batch ${batch + 1}: ${batchTime}ms (${batchResults.filter(r => r.status === 200).length}/${requestsPerBatch} successful)`);
|
||||
|
||||
// Small delay between batches
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
}
|
||||
|
||||
// Analyze results
|
||||
const totalRequests = totalBatches * requestsPerBatch;
|
||||
const successfulRequests = performanceResults.reduce((sum, batch) => sum + batch.successful, 0);
|
||||
const averageBatchTime = performanceResults.reduce((sum, batch) => sum + batch.totalTime, 0) / totalBatches;
|
||||
const successRate = (successfulRequests / totalRequests) * 100;
|
||||
|
||||
console.log(`📊 Concurrent Load Test Results:`);
|
||||
console.log(` Total Requests: ${totalRequests}`);
|
||||
console.log(` Successful: ${successfulRequests}`);
|
||||
console.log(` Success Rate: ${successRate.toFixed(1)}%`);
|
||||
console.log(` Average Batch Time: ${averageBatchTime.toFixed(0)}ms`);
|
||||
|
||||
// Validate performance under load
|
||||
expect(successRate).toBeGreaterThan(90); // 90%+ success rate
|
||||
expect(averageBatchTime).toBeLessThan(5000); // Max 5s per batch
|
||||
|
||||
// Individual requests should not be extremely slow
|
||||
const slowRequests = performanceResults
|
||||
.flatMap(batch => batch.results)
|
||||
.filter(result => result.timing > 10000); // > 10s
|
||||
|
||||
expect(slowRequests.length).toBe(0);
|
||||
|
||||
console.log('✅ Backend performance under concurrent load validated');
|
||||
});
|
||||
|
||||
test('should validate backend memory and resource monitoring', async ({ page }) => {
|
||||
console.log('💾 Testing backend resource monitoring...');
|
||||
|
||||
// Test multiple heavy operations to check for memory leaks
|
||||
const operations = [
|
||||
() => page.request.get(`${API_ENDPOINTS.backend}${API_ENDPOINTS.companies}`),
|
||||
() => page.request.get(`${API_ENDPOINTS.backend}/api/invoices/ROMFAST`),
|
||||
() => page.request.get(`${API_ENDPOINTS.backend}/api/payments/ROMFAST`),
|
||||
() => page.request.get(`${API_ENDPOINTS.backend}${API_ENDPOINTS.health}`)
|
||||
];
|
||||
|
||||
const resourceMetrics = [];
|
||||
|
||||
// Perform operations multiple times
|
||||
for (let cycle = 0; cycle < 3; cycle++) {
|
||||
console.log(`🔄 Resource test cycle ${cycle + 1}/3`);
|
||||
|
||||
const cycleStartTime = Date.now();
|
||||
const cycleResults = [];
|
||||
|
||||
for (const operation of operations) {
|
||||
const opStartTime = Date.now();
|
||||
|
||||
try {
|
||||
const response = await operation();
|
||||
const responseTime = Date.now() - opStartTime;
|
||||
|
||||
cycleResults.push({
|
||||
success: response.ok(),
|
||||
status: response.status(),
|
||||
responseTime
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
cycleResults.push({
|
||||
success: false,
|
||||
error: error.message,
|
||||
responseTime: Date.now() - opStartTime
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const cycleTime = Date.now() - cycleStartTime;
|
||||
const avgResponseTime = cycleResults.reduce((sum, r) => sum + r.responseTime, 0) / cycleResults.length;
|
||||
|
||||
resourceMetrics.push({
|
||||
cycle: cycle + 1,
|
||||
totalTime: cycleTime,
|
||||
averageResponseTime: avgResponseTime,
|
||||
successCount: cycleResults.filter(r => r.success).length
|
||||
});
|
||||
|
||||
console.log(`📊 Cycle ${cycle + 1}: ${cycleTime}ms avg, ${avgResponseTime.toFixed(0)}ms response`);
|
||||
}
|
||||
|
||||
// Check for performance degradation over cycles (indicating resource leaks)
|
||||
const firstCycleAvg = resourceMetrics[0].averageResponseTime;
|
||||
const lastCycleAvg = resourceMetrics[resourceMetrics.length - 1].averageResponseTime;
|
||||
const degradationRatio = lastCycleAvg / firstCycleAvg;
|
||||
|
||||
console.log(`📈 Performance degradation ratio: ${degradationRatio.toFixed(2)}`);
|
||||
|
||||
// Should not degrade significantly (< 50% increase)
|
||||
expect(degradationRatio).toBeLessThan(1.5);
|
||||
|
||||
// All cycles should maintain good success rates
|
||||
resourceMetrics.forEach((metric, index) => {
|
||||
expect(metric.successCount).toBeGreaterThan(2); // At least 3/4 operations successful
|
||||
});
|
||||
|
||||
console.log('✅ Backend resource monitoring validated - no significant degradation detected');
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
138
reports-app/frontend/tests/integration/global-setup.js
Normal file
138
reports-app/frontend/tests/integration/global-setup.js
Normal file
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* Global setup for real API integration tests
|
||||
* Ensures SSH tunnel and backend services are running
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
export default async function globalSetup() {
|
||||
console.log('🔧 Setting up real API integration test environment...');
|
||||
|
||||
// Root directory for reference if needed later
|
||||
// const rootDir = path.resolve(__dirname, '../../../../../..');
|
||||
|
||||
try {
|
||||
// Check if SSH tunnel is running by testing Oracle port
|
||||
console.log('📡 Checking SSH tunnel status...');
|
||||
try {
|
||||
const response = await fetch('http://localhost:8000/health', { timeout: 5000 });
|
||||
const health = await response.json();
|
||||
if (health.database === 'connected') {
|
||||
console.log('✅ SSH tunnel appears to be working (database connected)');
|
||||
} else {
|
||||
console.log('⚠️ Database not connected - SSH tunnel may need to be started manually');
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('⚠️ Could not check tunnel status - continuing anyway');
|
||||
}
|
||||
|
||||
// Check backend health
|
||||
console.log('🏥 Checking backend health...');
|
||||
try {
|
||||
const healthResponse = await fetch('http://localhost:8000/health', {
|
||||
timeout: 10000
|
||||
});
|
||||
|
||||
if (!healthResponse.ok) {
|
||||
throw new Error(`Backend health check failed: ${healthResponse.status}`);
|
||||
}
|
||||
|
||||
const healthData = await healthResponse.json();
|
||||
console.log('✅ Backend health check passed:', healthData);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Backend health check failed:', error.message);
|
||||
throw new Error('Backend is not available for integration tests');
|
||||
}
|
||||
|
||||
// Check frontend availability
|
||||
console.log('🌐 Checking frontend availability...');
|
||||
try {
|
||||
const frontendResponse = await fetch('http://localhost:3001', {
|
||||
timeout: 10000
|
||||
});
|
||||
|
||||
if (!frontendResponse.ok) {
|
||||
throw new Error(`Frontend not available: ${frontendResponse.status}`);
|
||||
}
|
||||
|
||||
console.log('✅ Frontend is available');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Frontend availability check failed:', error.message);
|
||||
throw new Error('Frontend is not available for integration tests');
|
||||
}
|
||||
|
||||
// Validate environment variables
|
||||
console.log('🔐 Validating environment configuration...');
|
||||
const requiredEnvVars = [
|
||||
'ORACLE_USER',
|
||||
'ORACLE_PASSWORD',
|
||||
'ORACLE_HOST',
|
||||
'ORACLE_PORT',
|
||||
'ORACLE_SID'
|
||||
];
|
||||
|
||||
const missingVars = requiredEnvVars.filter(varName => !process.env[varName]);
|
||||
if (missingVars.length > 0) {
|
||||
console.warn('⚠️ Missing environment variables:', missingVars.join(', '));
|
||||
console.log('ℹ️ Some tests may use default values');
|
||||
} else {
|
||||
console.log('✅ All required environment variables are set');
|
||||
}
|
||||
|
||||
// Test database connectivity through backend
|
||||
console.log('🗄️ Testing database connectivity...');
|
||||
try {
|
||||
const dbTestResponse = await fetch('http://localhost:8000/api/companies', {
|
||||
timeout: 15000
|
||||
});
|
||||
|
||||
if (dbTestResponse.ok) {
|
||||
const companies = await dbTestResponse.json();
|
||||
console.log(`✅ Database connectivity verified (${companies.length} companies found)`);
|
||||
|
||||
// Check if ROMFAST is available
|
||||
const romfast = companies.find(c => c.id_firma === 'ROMFAST');
|
||||
if (romfast) {
|
||||
console.log('✅ ROMFAST company data available for testing');
|
||||
} else {
|
||||
console.warn('⚠️ ROMFAST company not found in test data');
|
||||
}
|
||||
} else {
|
||||
console.warn('⚠️ Database connectivity test returned:', dbTestResponse.status);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('⚠️ Database connectivity test failed:', error.message);
|
||||
console.log('ℹ️ Tests will proceed but may fail if database is not accessible');
|
||||
}
|
||||
|
||||
console.log('🎯 Global setup completed successfully');
|
||||
|
||||
// Store setup metadata for tests
|
||||
global.__INTEGRATION_SETUP__ = {
|
||||
timestamp: new Date().toISOString(),
|
||||
backend: 'http://localhost:8000',
|
||||
frontend: 'http://localhost:3001',
|
||||
sshTunnelActive: true,
|
||||
environmentValidated: true
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Global setup encountered error:', error.message);
|
||||
console.log('ℹ️ Continuing with tests - they may fail if services are not available');
|
||||
|
||||
// Don't fail setup - let individual tests handle service unavailability
|
||||
global.__INTEGRATION_SETUP__ = {
|
||||
timestamp: new Date().toISOString(),
|
||||
backend: 'http://localhost:8000',
|
||||
frontend: 'http://localhost:3001',
|
||||
sshTunnelActive: false,
|
||||
environmentValidated: false,
|
||||
setupError: error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
51
reports-app/frontend/tests/integration/global-teardown.js
Normal file
51
reports-app/frontend/tests/integration/global-teardown.js
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Global teardown for real API integration tests
|
||||
* Cleanup resources and generate final reports
|
||||
*/
|
||||
|
||||
import { writeFileSync } from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
export default async function globalTeardown() {
|
||||
console.log('🧹 Starting global teardown for integration tests...');
|
||||
|
||||
try {
|
||||
// Generate final test report
|
||||
const testReport = {
|
||||
testRun: {
|
||||
timestamp: new Date().toISOString(),
|
||||
type: 'integration',
|
||||
environment: 'development'
|
||||
},
|
||||
setup: global.__INTEGRATION_SETUP__ || {},
|
||||
summary: {
|
||||
message: 'Integration test run completed',
|
||||
backend: 'http://localhost:8000',
|
||||
frontend: 'http://localhost:3001',
|
||||
sshTunnel: 'managed externally'
|
||||
}
|
||||
};
|
||||
|
||||
// Write final report
|
||||
const reportPath = path.join(__dirname, '../../test-results/integration-summary.json');
|
||||
try {
|
||||
writeFileSync(reportPath, JSON.stringify(testReport, null, 2));
|
||||
console.log(`📊 Integration test summary written to: ${reportPath}`);
|
||||
} catch (error) {
|
||||
console.warn('⚠️ Could not write integration test summary:', error.message);
|
||||
}
|
||||
|
||||
// Log cleanup completion
|
||||
console.log('✅ Global teardown completed');
|
||||
console.log('ℹ️ SSH tunnel and services left running for continued development');
|
||||
console.log('ℹ️ Use ./ssh_tunnel.sh stop to manually stop the SSH tunnel if needed');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Global teardown encountered errors:', error.message);
|
||||
// Don't fail teardown on non-critical errors
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,366 @@
|
||||
/**
|
||||
* ROMFAST Company Data Integration Tests
|
||||
* Tests real Oracle data loading and validation for ROMFAST company
|
||||
* Monitors console errors during data operations
|
||||
*/
|
||||
|
||||
import { test, expect } from '@playwright/test';
|
||||
import {
|
||||
authenticateWithRealCredentials,
|
||||
selectCompany,
|
||||
getRealCompanies,
|
||||
REAL_CREDENTIALS,
|
||||
API_ENDPOINTS
|
||||
} from '../../utils/real-auth.js';
|
||||
import {
|
||||
setupConsoleCapture,
|
||||
assertNoCriticalErrors,
|
||||
generateErrorReport,
|
||||
PerformanceMonitor,
|
||||
PerformanceBaselines,
|
||||
assertPerformanceBaseline
|
||||
} from '../../utils/console-monitor.js';
|
||||
|
||||
test.describe('ROMFAST Company Data Integration', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Setup console monitoring
|
||||
setupConsoleCapture(page);
|
||||
|
||||
// Authenticate with real credentials
|
||||
const authResult = await authenticateWithRealCredentials(page);
|
||||
expect(authResult.success, `Authentication failed: ${authResult.error}`).toBe(true);
|
||||
|
||||
console.log('🔐 Authenticated successfully for ROMFAST data tests');
|
||||
});
|
||||
|
||||
test.afterEach(async ({ page }) => {
|
||||
// Generate comprehensive error report
|
||||
const report = generateErrorReport(page, test.info().title);
|
||||
|
||||
if (report.summary.classifications.critical > 0) {
|
||||
console.warn('❌ Critical errors in ROMFAST data test:', report.details.criticalErrors);
|
||||
}
|
||||
|
||||
if (report.summary.classifications.warning > 3) {
|
||||
console.warn('⚠️ High number of warnings:', report.summary.classifications.warning);
|
||||
}
|
||||
});
|
||||
|
||||
test('should load ROMFAST company data correctly', async ({ page }) => {
|
||||
console.log('🏢 Testing ROMFAST company data loading...');
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
// Select ROMFAST from real companies list
|
||||
const selectSuccess = await selectCompany(page, REAL_CREDENTIALS.company);
|
||||
expect(selectSuccess, 'Failed to select ROMFAST company').toBe(true);
|
||||
|
||||
const selectionTime = Date.now() - startTime;
|
||||
|
||||
// Verify company stats loaded
|
||||
await page.waitForSelector('[data-testid="company-stats"]', { timeout: 15000 });
|
||||
|
||||
// Verify company name display
|
||||
const companyName = await page.locator('[data-testid="company-name"]').textContent();
|
||||
expect(companyName).toContain('ROMFAST');
|
||||
|
||||
// Check for console errors during data load
|
||||
const criticalErrors = (page.consoleMessages || [])
|
||||
.filter(msg => msg.type === 'error' && !msg.text.includes('404'));
|
||||
|
||||
expect(criticalErrors, `Critical errors during ROMFAST data load: ${JSON.stringify(criticalErrors)}`).toHaveLength(0);
|
||||
|
||||
// Validate performance
|
||||
assertPerformanceBaseline(
|
||||
selectionTime,
|
||||
PerformanceBaselines.dashboardLoad,
|
||||
'ROMFAST company selection',
|
||||
expect
|
||||
);
|
||||
|
||||
console.log(`✅ ROMFAST company data loaded successfully in ${selectionTime}ms`);
|
||||
});
|
||||
|
||||
test('should validate ROMFAST invoice data structure', async ({ page }) => {
|
||||
console.log('📋 Testing ROMFAST invoice data structure...');
|
||||
|
||||
// Select ROMFAST company
|
||||
await selectCompany(page, REAL_CREDENTIALS.company);
|
||||
|
||||
// Navigate to invoices
|
||||
await page.click('[data-testid="nav-invoices"]');
|
||||
await page.waitForURL('/invoices');
|
||||
|
||||
// Measure API response time
|
||||
const apiStartTime = Date.now();
|
||||
await page.waitForResponse(response =>
|
||||
response.url().includes('/api/invoices') && response.status() === 200,
|
||||
{ timeout: 10000 }
|
||||
);
|
||||
const apiResponseTime = Date.now() - apiStartTime;
|
||||
|
||||
// Wait for invoice data to load
|
||||
await page.waitForSelector('[data-testid="invoices-table"]', { timeout: 15000 });
|
||||
|
||||
// Verify Oracle-specific data fields are present
|
||||
const invoiceRows = await page.locator('[data-testid="invoice-row"]').count();
|
||||
expect(invoiceRows).toBeGreaterThan(0);
|
||||
|
||||
if (invoiceRows > 0) {
|
||||
// Check first invoice for Oracle schema fields
|
||||
const firstInvoice = page.locator('[data-testid="invoice-row"]').first();
|
||||
|
||||
// These should match Oracle CONTAFIN schema
|
||||
await expect(firstInvoice.locator('[data-testid="numar-factura"]')).toBeVisible();
|
||||
await expect(firstInvoice.locator('[data-testid="data-scadenta"]')).toBeVisible();
|
||||
await expect(firstInvoice.locator('[data-testid="suma-totala"]')).toBeVisible();
|
||||
|
||||
console.log(`📊 Found ${invoiceRows} ROMFAST invoices with Oracle schema fields`);
|
||||
}
|
||||
|
||||
// Validate API performance
|
||||
assertPerformanceBaseline(
|
||||
apiResponseTime,
|
||||
PerformanceBaselines.apiResponse,
|
||||
'ROMFAST invoices API',
|
||||
expect
|
||||
);
|
||||
|
||||
// Check for no critical console errors
|
||||
assertNoCriticalErrors(page, expect);
|
||||
|
||||
console.log(`✅ ROMFAST invoice data structure validated (API: ${apiResponseTime}ms)`);
|
||||
});
|
||||
|
||||
test('should validate ROMFAST payment data integration', async ({ page }) => {
|
||||
console.log('💰 Testing ROMFAST payment data integration...');
|
||||
|
||||
// Select ROMFAST company
|
||||
await selectCompany(page, REAL_CREDENTIALS.company);
|
||||
|
||||
// Navigate to payments
|
||||
await page.click('[data-testid="nav-payments"]');
|
||||
await page.waitForURL('/payments');
|
||||
|
||||
// Measure payment data loading
|
||||
const loadStartTime = Date.now();
|
||||
await page.waitForSelector('[data-testid="payments-table"]', { timeout: 15000 });
|
||||
const loadTime = Date.now() - loadStartTime;
|
||||
|
||||
// Verify payment data structure
|
||||
const paymentRows = await page.locator('[data-testid="payment-row"]').count();
|
||||
console.log(`💳 Found ${paymentRows} ROMFAST payments`);
|
||||
|
||||
if (paymentRows > 0) {
|
||||
// Verify Oracle payment schema fields
|
||||
const firstPayment = page.locator('[data-testid="payment-row"]').first();
|
||||
|
||||
await expect(firstPayment.locator('[data-testid="numar-plata"]')).toBeVisible();
|
||||
await expect(firstPayment.locator('[data-testid="data-plata"]')).toBeVisible();
|
||||
await expect(firstPayment.locator('[data-testid="suma-plata"]')).toBeVisible();
|
||||
}
|
||||
|
||||
// Validate performance
|
||||
assertPerformanceBaseline(
|
||||
loadTime,
|
||||
PerformanceBaselines.reportGeneration,
|
||||
'ROMFAST payments loading',
|
||||
expect
|
||||
);
|
||||
|
||||
// Check console for errors
|
||||
assertNoCriticalErrors(page, expect);
|
||||
|
||||
console.log(`✅ ROMFAST payment data validated (Load: ${loadTime}ms)`);
|
||||
});
|
||||
|
||||
test('should handle ROMFAST data filtering and search', async ({ page }) => {
|
||||
console.log('🔍 Testing ROMFAST data filtering capabilities...');
|
||||
|
||||
// Select ROMFAST company
|
||||
await selectCompany(page, REAL_CREDENTIALS.company);
|
||||
|
||||
// Go to invoices for filtering test
|
||||
await page.click('[data-testid="nav-invoices"]');
|
||||
await page.waitForSelector('[data-testid="invoices-table"]', { timeout: 15000 });
|
||||
|
||||
// Get initial row count
|
||||
const initialCount = await page.locator('[data-testid="invoice-row"]').count();
|
||||
console.log(`📊 Initial ROMFAST invoices: ${initialCount}`);
|
||||
|
||||
if (initialCount > 0) {
|
||||
// Test date range filtering
|
||||
if (await page.locator('[data-testid="date-filter-from"]').isVisible()) {
|
||||
const filterStartTime = Date.now();
|
||||
|
||||
// Set date filter for last 30 days
|
||||
const thirtyDaysAgo = new Date();
|
||||
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
||||
|
||||
await page.fill('[data-testid="date-filter-from"]', thirtyDaysAgo.toISOString().split('T')[0]);
|
||||
await page.click('[data-testid="apply-filter"]');
|
||||
|
||||
// Wait for filtered results
|
||||
await page.waitForTimeout(2000); // Allow for filtering
|
||||
|
||||
const filteredCount = await page.locator('[data-testid="invoice-row"]').count();
|
||||
const filterTime = Date.now() - filterStartTime;
|
||||
|
||||
console.log(`🗓️ Filtered to ${filteredCount} invoices in ${filterTime}ms`);
|
||||
|
||||
// Performance check for filtering
|
||||
expect(filterTime).toBeLessThan(3000); // Max 3s for filtering
|
||||
}
|
||||
|
||||
// Test search functionality
|
||||
if (await page.locator('[data-testid="search-input"]').isVisible()) {
|
||||
const searchStartTime = Date.now();
|
||||
|
||||
// Search for specific criteria
|
||||
await page.fill('[data-testid="search-input"]', 'ROMFAST');
|
||||
await page.keyboard.press('Enter');
|
||||
|
||||
await page.waitForTimeout(1500); // Allow for search
|
||||
|
||||
const searchResults = await page.locator('[data-testid="invoice-row"]').count();
|
||||
const searchTime = Date.now() - searchStartTime;
|
||||
|
||||
console.log(`🔎 Search returned ${searchResults} results in ${searchTime}ms`);
|
||||
|
||||
// Performance check for search
|
||||
expect(searchTime).toBeLessThan(2000); // Max 2s for search
|
||||
}
|
||||
}
|
||||
|
||||
// Verify no critical errors during filtering operations
|
||||
assertNoCriticalErrors(page, expect);
|
||||
|
||||
console.log('✅ ROMFAST data filtering and search validated');
|
||||
});
|
||||
|
||||
test('should validate ROMFAST dashboard metrics accuracy', async ({ page }) => {
|
||||
console.log('📈 Testing ROMFAST dashboard metrics accuracy...');
|
||||
|
||||
// Select ROMFAST company
|
||||
await selectCompany(page, REAL_CREDENTIALS.company);
|
||||
|
||||
// Wait for dashboard stats to load
|
||||
await page.waitForSelector('[data-testid="company-stats"]', { timeout: 15000 });
|
||||
|
||||
// Capture dashboard metrics
|
||||
const dashboardMetrics = await page.evaluate(() => {
|
||||
const getMetric = (selector) => {
|
||||
const element = document.querySelector(selector);
|
||||
return element ? element.textContent.trim() : null;
|
||||
};
|
||||
|
||||
return {
|
||||
totalInvoices: getMetric('[data-testid="total-invoices"]'),
|
||||
totalPayments: getMetric('[data-testid="total-payments"]'),
|
||||
pendingAmount: getMetric('[data-testid="pending-amount"]'),
|
||||
overdueCount: getMetric('[data-testid="overdue-count"]')
|
||||
};
|
||||
});
|
||||
|
||||
console.log('📊 ROMFAST Dashboard Metrics:', dashboardMetrics);
|
||||
|
||||
// Validate metrics are present and reasonable
|
||||
if (dashboardMetrics.totalInvoices) {
|
||||
const invoiceCount = parseInt(dashboardMetrics.totalInvoices.replace(/\D/g, ''));
|
||||
expect(invoiceCount).toBeGreaterThanOrEqual(0);
|
||||
}
|
||||
|
||||
if (dashboardMetrics.totalPayments) {
|
||||
const paymentCount = parseInt(dashboardMetrics.totalPayments.replace(/\D/g, ''));
|
||||
expect(paymentCount).toBeGreaterThanOrEqual(0);
|
||||
}
|
||||
|
||||
// Cross-validate with individual pages
|
||||
await page.click('[data-testid="nav-invoices"]');
|
||||
await page.waitForSelector('[data-testid="invoices-table"]', { timeout: 10000 });
|
||||
|
||||
const actualInvoiceCount = await page.locator('[data-testid="invoice-row"]').count();
|
||||
console.log(`🔄 Cross-validation: Dashboard vs Invoices page (${actualInvoiceCount})`);
|
||||
|
||||
// Return to dashboard
|
||||
await page.click('[data-testid="nav-dashboard"]');
|
||||
|
||||
// Check for console errors during metric calculations
|
||||
assertNoCriticalErrors(page, expect);
|
||||
|
||||
console.log('✅ ROMFAST dashboard metrics accuracy validated');
|
||||
});
|
||||
|
||||
test('should measure ROMFAST data loading performance under load', async ({ page }) => {
|
||||
console.log('⚡ Testing ROMFAST data performance under simulated load...');
|
||||
|
||||
const performanceMetrics = [];
|
||||
|
||||
// Perform multiple data loading operations
|
||||
for (let i = 0; i < 3; i++) {
|
||||
console.log(`🔄 Performance test iteration ${i + 1}/3`);
|
||||
|
||||
const iterationStart = Date.now();
|
||||
|
||||
// Select company
|
||||
await selectCompany(page, REAL_CREDENTIALS.company);
|
||||
const companySelectTime = Date.now() - iterationStart;
|
||||
|
||||
// Load invoices
|
||||
const invoicesStart = Date.now();
|
||||
await page.click('[data-testid="nav-invoices"]');
|
||||
await page.waitForSelector('[data-testid="invoices-table"]', { timeout: 15000 });
|
||||
const invoicesLoadTime = Date.now() - invoicesStart;
|
||||
|
||||
// Load payments
|
||||
const paymentsStart = Date.now();
|
||||
await page.click('[data-testid="nav-payments"]');
|
||||
await page.waitForSelector('[data-testid="payments-table"]', { timeout: 15000 });
|
||||
const paymentsLoadTime = Date.now() - paymentsStart;
|
||||
|
||||
// Return to dashboard
|
||||
const dashboardStart = Date.now();
|
||||
await page.click('[data-testid="nav-dashboard"]');
|
||||
await page.waitForSelector('[data-testid="company-stats"]', { timeout: 10000 });
|
||||
const dashboardLoadTime = Date.now() - dashboardStart;
|
||||
|
||||
const totalIterationTime = Date.now() - iterationStart;
|
||||
|
||||
performanceMetrics.push({
|
||||
iteration: i + 1,
|
||||
companySelect: companySelectTime,
|
||||
invoicesLoad: invoicesLoadTime,
|
||||
paymentsLoad: paymentsLoadTime,
|
||||
dashboardLoad: dashboardLoadTime,
|
||||
total: totalIterationTime
|
||||
});
|
||||
|
||||
console.log(`📊 Iteration ${i + 1} - Total: ${totalIterationTime}ms`);
|
||||
}
|
||||
|
||||
// Calculate averages
|
||||
const averages = {
|
||||
companySelect: performanceMetrics.reduce((sum, m) => sum + m.companySelect, 0) / performanceMetrics.length,
|
||||
invoicesLoad: performanceMetrics.reduce((sum, m) => sum + m.invoicesLoad, 0) / performanceMetrics.length,
|
||||
paymentsLoad: performanceMetrics.reduce((sum, m) => sum + m.paymentsLoad, 0) / performanceMetrics.length,
|
||||
dashboardLoad: performanceMetrics.reduce((sum, m) => sum + m.dashboardLoad, 0) / performanceMetrics.length,
|
||||
total: performanceMetrics.reduce((sum, m) => sum + m.total, 0) / performanceMetrics.length
|
||||
};
|
||||
|
||||
console.log('📈 Average Performance Metrics:', averages);
|
||||
|
||||
// Validate against baselines
|
||||
assertPerformanceBaseline(averages.companySelect, PerformanceBaselines.dashboardLoad, 'Company selection', expect);
|
||||
assertPerformanceBaseline(averages.invoicesLoad, PerformanceBaselines.reportGeneration, 'Invoices loading', expect);
|
||||
assertPerformanceBaseline(averages.paymentsLoad, PerformanceBaselines.reportGeneration, 'Payments loading', expect);
|
||||
assertPerformanceBaseline(averages.dashboardLoad, PerformanceBaselines.dashboardLoad, 'Dashboard loading', expect);
|
||||
|
||||
// Total workflow should complete reasonably quickly
|
||||
expect(averages.total).toBeLessThan(15000); // Max 15s for full workflow
|
||||
|
||||
// Check for no critical errors across all iterations
|
||||
assertNoCriticalErrors(page, expect);
|
||||
|
||||
console.log('✅ ROMFAST performance under load validated');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user