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
364 lines
12 KiB
JavaScript
364 lines
12 KiB
JavaScript
//! 🌍 COMPREHENSIVE REAL-WORLD TESTING SUITE
|
|
//! Created: 2025-08-04
|
|
//! Purpose: Test complete application flows with real data and interactions
|
|
|
|
import { test, expect } from '@playwright/test';
|
|
import { LoginPage } from '../page-objects/LoginPage.js';
|
|
import { DashboardPage } from '../page-objects/DashboardPage.js';
|
|
|
|
test.describe('🌍 ROA2WEB Real-World Comprehensive Testing', () => {
|
|
let loginPage;
|
|
let dashboardPage;
|
|
let performanceMetrics = [];
|
|
let networkErrors = [];
|
|
let consoleErrors = [];
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
// Reset metrics
|
|
performanceMetrics = [];
|
|
networkErrors = [];
|
|
consoleErrors = [];
|
|
|
|
// Setup comprehensive monitoring
|
|
page.on('response', response => {
|
|
const timing = {
|
|
url: response.url(),
|
|
status: response.status(),
|
|
timing: response.request().timing(),
|
|
size: response.headers()['content-length'] || 0,
|
|
timestamp: new Date().toISOString()
|
|
};
|
|
performanceMetrics.push(timing);
|
|
|
|
if (response.status() >= 400) {
|
|
networkErrors.push({
|
|
url: response.url(),
|
|
status: response.status(),
|
|
statusText: response.statusText()
|
|
});
|
|
}
|
|
});
|
|
|
|
page.on('console', msg => {
|
|
if (msg.type() === 'error') {
|
|
consoleErrors.push({
|
|
text: msg.text(),
|
|
location: msg.location(),
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
}
|
|
});
|
|
|
|
loginPage = new LoginPage(page);
|
|
dashboardPage = new DashboardPage(page);
|
|
});
|
|
|
|
test('🎯 COMPLETE USER JOURNEY - Login to Dashboard to Reports', async ({ page }) => {
|
|
console.log('\n🌍 === COMPLETE USER JOURNEY TEST ===');
|
|
|
|
const startTime = Date.now();
|
|
|
|
// Phase 1: Navigate to Login
|
|
console.log('\n📍 Phase 1: Navigate to Login');
|
|
await loginPage.navigate();
|
|
await page.screenshot({ path: 'journey-01-login-page.png' });
|
|
|
|
// Verify login page loads correctly
|
|
await expect(page).toHaveTitle(/ROA Reports/);
|
|
console.log('✅ Login page loaded');
|
|
|
|
// Phase 2: Attempt Authentication with Real Credentials
|
|
console.log('\n📍 Phase 2: Authentication Flow');
|
|
|
|
// Test with test credentials first
|
|
await page.fill('#username', 'MARIUS M');
|
|
await page.fill('#password input', 'PAROLA9911');
|
|
|
|
// Verify button becomes enabled
|
|
await page.waitForTimeout(200);
|
|
const buttonEnabled = !(await page.locator('button[type="submit"]').isDisabled());
|
|
expect(buttonEnabled).toBe(true);
|
|
console.log('✅ Login button enabled with credentials');
|
|
|
|
// Monitor the authentication request
|
|
const authPromise = page.waitForResponse('**/auth/login').catch(() => null);
|
|
await page.click('button[type="submit"]');
|
|
|
|
const authResponse = await authPromise;
|
|
if (authResponse) {
|
|
console.log(`📊 Auth Response: ${authResponse.status()}`);
|
|
|
|
if (authResponse.status() === 200) {
|
|
console.log('✅ Authentication successful');
|
|
|
|
// Wait for redirect to dashboard
|
|
await page.waitForURL('**/dashboard', { timeout: 10000 }).catch(() => {
|
|
console.log('⚠️ No redirect to dashboard - checking current state');
|
|
});
|
|
|
|
} else if (authResponse.status() === 422) {
|
|
console.log('❌ Validation error - checking response');
|
|
const responseBody = await authResponse.text();
|
|
console.log('Response body:', responseBody);
|
|
|
|
} else if (authResponse.status() === 401) {
|
|
console.log('❌ Authentication failed - invalid credentials');
|
|
|
|
} else {
|
|
console.log(`❌ Unexpected response: ${authResponse.status()}`);
|
|
}
|
|
} else {
|
|
console.log('⚠️ No authentication response received');
|
|
}
|
|
|
|
await page.screenshot({ path: 'journey-02-after-auth.png' });
|
|
|
|
// Phase 3: Dashboard Interaction (if successful)
|
|
const currentUrl = page.url();
|
|
console.log(`📍 Current URL: ${currentUrl}`);
|
|
|
|
if (currentUrl.includes('/dashboard')) {
|
|
console.log('\n📍 Phase 3: Dashboard Interaction');
|
|
|
|
// Wait for dashboard to load
|
|
await page.waitForSelector('.dashboard-container', { timeout: 5000 }).catch(() => {
|
|
console.log('⚠️ Dashboard container not found');
|
|
});
|
|
|
|
// Test dashboard functionality
|
|
const companySelector = page.locator('select, .p-dropdown');
|
|
if (await companySelector.first().isVisible()) {
|
|
console.log('✅ Company selector visible');
|
|
|
|
// Try to select a company
|
|
await companySelector.first().click();
|
|
await page.waitForTimeout(500);
|
|
|
|
const options = page.locator('.p-dropdown-item');
|
|
const optionCount = await options.count();
|
|
|
|
if (optionCount > 0) {
|
|
await options.first().click();
|
|
console.log('✅ Company selected');
|
|
|
|
// Wait for data to load
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Check if statistics are displayed
|
|
const statsCards = page.locator('.stat-card, .dashboard-stat, .metric-card');
|
|
const statsCount = await statsCards.count();
|
|
console.log(`📊 Statistics cards found: ${statsCount}`);
|
|
}
|
|
}
|
|
|
|
await page.screenshot({ path: 'journey-03-dashboard.png' });
|
|
|
|
// Phase 4: Navigation Test
|
|
console.log('\n📍 Phase 4: Navigation Test');
|
|
|
|
const navLinks = page.locator('nav a, .nav-link, .menu-item');
|
|
const navCount = await navLinks.count();
|
|
console.log(`🧭 Navigation links found: ${navCount}`);
|
|
|
|
if (navCount > 0) {
|
|
// Try to navigate to invoices
|
|
const invoicesLink = page.locator('text=/facturi|invoice/i').first();
|
|
if (await invoicesLink.isVisible()) {
|
|
await invoicesLink.click();
|
|
await page.waitForTimeout(1000);
|
|
console.log('✅ Navigated to invoices');
|
|
await page.screenshot({ path: 'journey-04-invoices.png' });
|
|
}
|
|
}
|
|
}
|
|
|
|
const totalTime = Date.now() - startTime;
|
|
console.log(`\n⏱️ Total journey time: ${totalTime}ms`);
|
|
});
|
|
|
|
test('🔍 NETWORK PERFORMANCE ANALYSIS', async ({ page }) => {
|
|
console.log('\n🔍 === NETWORK PERFORMANCE ANALYSIS ===');
|
|
|
|
// Navigate and monitor performance
|
|
await loginPage.navigate();
|
|
|
|
// Wait for all initial requests to complete
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Analyze performance metrics
|
|
const slowRequests = performanceMetrics.filter(metric => {
|
|
const timing = metric.timing;
|
|
return timing && (timing.responseEnd - timing.requestStart) > 2000;
|
|
});
|
|
|
|
const failedRequests = performanceMetrics.filter(metric => metric.status >= 400);
|
|
|
|
console.log(`📊 Total requests: ${performanceMetrics.length}`);
|
|
console.log(`🐌 Slow requests (>2s): ${slowRequests.length}`);
|
|
console.log(`❌ Failed requests: ${failedRequests.length}`);
|
|
|
|
if (slowRequests.length > 0) {
|
|
console.log('\n🐌 Slow requests:');
|
|
slowRequests.forEach(request => {
|
|
const duration = request.timing ?
|
|
(request.timing.responseEnd - request.timing.requestStart) : 'unknown';
|
|
console.log(` - ${request.url}: ${duration}ms`);
|
|
});
|
|
}
|
|
|
|
if (failedRequests.length > 0) {
|
|
console.log('\n❌ Failed requests:');
|
|
failedRequests.forEach(request => {
|
|
console.log(` - ${request.status} ${request.url}`);
|
|
});
|
|
}
|
|
|
|
// Performance assertions
|
|
expect(slowRequests.length).toBeLessThan(5); // Max 5 slow requests
|
|
expect(failedRequests.length).toBeLessThan(3); // Max 3 failed requests
|
|
});
|
|
|
|
test('🧪 ERROR HANDLING STRESS TEST', async ({ page }) => {
|
|
console.log('\n🧪 === ERROR HANDLING STRESS TEST ===');
|
|
|
|
await loginPage.navigate();
|
|
|
|
// Test various error scenarios
|
|
const errorScenarios = [
|
|
{
|
|
name: 'Server Error 500',
|
|
setup: () => page.route('**/auth/login', route =>
|
|
route.fulfill({ status: 500, body: '{"detail": "Internal server error"}' })
|
|
)
|
|
},
|
|
{
|
|
name: 'Network Timeout',
|
|
setup: () => page.route('**/auth/login', route => route.abort('timeout'))
|
|
},
|
|
{
|
|
name: 'Invalid JSON Response',
|
|
setup: () => page.route('**/auth/login', route =>
|
|
route.fulfill({ status: 200, body: 'invalid json' })
|
|
)
|
|
},
|
|
{
|
|
name: 'Rate Limiting 429',
|
|
setup: () => page.route('**/auth/login', route =>
|
|
route.fulfill({ status: 429, body: '{"detail": "Too many requests"}' })
|
|
)
|
|
}
|
|
];
|
|
|
|
for (const scenario of errorScenarios) {
|
|
console.log(`\n--- Testing: ${scenario.name} ---`);
|
|
|
|
// Setup error scenario
|
|
await scenario.setup();
|
|
|
|
// Fill credentials and submit
|
|
await page.fill('#username', 'test');
|
|
await page.fill('#password input', 'test');
|
|
await page.click('button[type="submit"]');
|
|
|
|
// Wait for error handling
|
|
await page.waitForTimeout(3000);
|
|
|
|
// Check if error is handled gracefully
|
|
const errorMessage = await page.locator('.error-message, .p-toast-error').isVisible();
|
|
console.log(`Error message shown: ${errorMessage}`);
|
|
|
|
// Verify user is still on login page
|
|
const isOnLogin = await loginPage.isOnLoginPage();
|
|
console.log(`Still on login page: ${isOnLogin}`);
|
|
|
|
expect(isOnLogin).toBe(true);
|
|
|
|
// Clear the route override
|
|
await page.unroute('**/auth/login');
|
|
|
|
// Clear form for next test
|
|
await page.fill('#username', '');
|
|
await page.fill('#password input', '');
|
|
await page.waitForTimeout(500);
|
|
}
|
|
});
|
|
|
|
test('📱 RESPONSIVE DESIGN VALIDATION', async ({ page }) => {
|
|
console.log('\n📱 === RESPONSIVE DESIGN VALIDATION ===');
|
|
|
|
const viewports = [
|
|
{ name: 'Mobile Portrait', width: 375, height: 667 },
|
|
{ name: 'Mobile Landscape', width: 667, height: 375 },
|
|
{ name: 'Tablet', width: 768, height: 1024 },
|
|
{ name: 'Desktop', width: 1920, height: 1080 }
|
|
];
|
|
|
|
for (const viewport of viewports) {
|
|
console.log(`\n--- Testing: ${viewport.name} (${viewport.width}x${viewport.height}) ---`);
|
|
|
|
await page.setViewportSize({ width: viewport.width, height: viewport.height });
|
|
await loginPage.navigate();
|
|
|
|
// Wait for layout to adjust
|
|
await page.waitForTimeout(500);
|
|
|
|
// Check if login form is visible and accessible
|
|
const formVisible = await page.locator('.login-form').isVisible();
|
|
const buttonVisible = await page.locator('button[type="submit"]').isVisible();
|
|
|
|
console.log(`Form visible: ${formVisible}`);
|
|
console.log(`Button visible: ${buttonVisible}`);
|
|
|
|
expect(formVisible).toBe(true);
|
|
expect(buttonVisible).toBe(true);
|
|
|
|
// Test form interaction
|
|
await page.fill('#username', 'test');
|
|
await page.fill('#password input', 'test');
|
|
|
|
const buttonEnabled = !(await page.locator('button[type="submit"]').isDisabled());
|
|
expect(buttonEnabled).toBe(true);
|
|
|
|
// Take screenshot for visual verification
|
|
await page.screenshot({
|
|
path: `responsive-${viewport.name.toLowerCase().replace(' ', '-')}.png`,
|
|
fullPage: true
|
|
});
|
|
}
|
|
});
|
|
|
|
test.afterEach(async ({ page }) => {
|
|
// Generate comprehensive test report
|
|
console.log('\n📋 === COMPREHENSIVE TEST REPORT ===');
|
|
console.log(`🌐 Total Network Requests: ${performanceMetrics.length}`);
|
|
console.log(`❌ Network Errors: ${networkErrors.length}`);
|
|
console.log(`🔥 Console Errors: ${consoleErrors.length}`);
|
|
|
|
if (networkErrors.length > 0) {
|
|
console.log('\n❌ Network Errors:');
|
|
networkErrors.forEach(error => {
|
|
console.log(` - ${error.status} ${error.url}`);
|
|
});
|
|
}
|
|
|
|
if (consoleErrors.length > 0) {
|
|
console.log('\n🔥 Console Errors:');
|
|
consoleErrors.forEach(error => {
|
|
console.log(` - ${error.text}`);
|
|
});
|
|
}
|
|
|
|
// Performance summary
|
|
const avgResponseTime = performanceMetrics.length > 0 ?
|
|
performanceMetrics.reduce((sum, metric) => {
|
|
const timing = metric.timing;
|
|
return sum + (timing ? (timing.responseEnd - timing.requestStart) : 0);
|
|
}, 0) / performanceMetrics.length : 0;
|
|
|
|
console.log(`⚡ Average Response Time: ${Math.round(avgResponseTime)}ms`);
|
|
|
|
if (avgResponseTime > 1000) {
|
|
console.log('⚠️ Performance Warning: Average response time is high');
|
|
}
|
|
});
|
|
}); |