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

Modern ERP Reports Application with microservices architecture

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

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

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

View File

@@ -0,0 +1,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

View File

@@ -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');
});
});

View File

@@ -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');
});
});

View File

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

View File

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

View 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
};
}
}

View 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
}
}

View File

@@ -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');
});
});