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
324 lines
10 KiB
JavaScript
324 lines
10 KiB
JavaScript
import { test, expect } from '@playwright/test';
|
|
import { LoginPage } from '../page-objects/LoginPage.js';
|
|
import { DashboardPage } from '../page-objects/DashboardPage.js';
|
|
|
|
test.describe('📊 Complete Reports Functionality Test', () => {
|
|
let loginPage;
|
|
let dashboardPage;
|
|
let networkRequests = [];
|
|
let apiErrors = [];
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
// Reset monitoring arrays
|
|
networkRequests = [];
|
|
apiErrors = [];
|
|
|
|
// Monitor network requests
|
|
page.on('request', request => {
|
|
networkRequests.push({
|
|
url: request.url(),
|
|
method: request.method(),
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
});
|
|
|
|
page.on('response', response => {
|
|
if (response.status() >= 400) {
|
|
apiErrors.push({
|
|
url: response.url(),
|
|
status: response.status(),
|
|
statusText: response.statusText()
|
|
});
|
|
console.log(`❌ API Error: ${response.status()} ${response.url()}`);
|
|
}
|
|
});
|
|
|
|
loginPage = new LoginPage(page);
|
|
dashboardPage = new DashboardPage(page);
|
|
});
|
|
|
|
test('🎯 Complete User Flow: Login → Dashboard → Reports', async ({ page }) => {
|
|
console.log('\n🎯 === TESTING COMPLETE REPORTS FUNCTIONALITY ===');
|
|
|
|
// Phase 1: Authentication
|
|
console.log('\n📍 Phase 1: Login with real credentials');
|
|
await loginPage.navigate();
|
|
|
|
// Use real test credentials
|
|
await page.fill('#username', 'MARIUS M');
|
|
await page.fill('#password input', 'PAROLA9911');
|
|
|
|
// Submit login and wait for response
|
|
const [authResponse] = await Promise.all([
|
|
page.waitForResponse('**/auth/login'),
|
|
page.click('button[type="submit"]')
|
|
]);
|
|
|
|
console.log(`📊 Auth Response: ${authResponse.status()}`);
|
|
expect(authResponse.status()).toBe(200);
|
|
|
|
// Wait for redirect to dashboard
|
|
await expect(page).toHaveURL(/.*dashboard/);
|
|
console.log('✅ Successfully redirected to dashboard');
|
|
|
|
// Phase 2: Test Dashboard Loading
|
|
console.log('\n📍 Phase 2: Dashboard Data Loading');
|
|
|
|
// Wait for dashboard to load
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Check for companies API call
|
|
const companiesRequests = networkRequests.filter(req =>
|
|
req.url.includes('/companies') || req.url.includes('/api/companies')
|
|
);
|
|
|
|
console.log(`📊 Companies API requests: ${companiesRequests.length}`);
|
|
|
|
if (companiesRequests.length > 0) {
|
|
console.log('✅ Companies API was called');
|
|
|
|
// Check if there were CORS errors
|
|
const corsErrors = apiErrors.filter(err =>
|
|
err.url.includes('/companies')
|
|
);
|
|
|
|
if (corsErrors.length > 0) {
|
|
console.log('❌ CORS errors detected:');
|
|
corsErrors.forEach(err => {
|
|
console.log(` - ${err.status} ${err.url}`);
|
|
});
|
|
} else {
|
|
console.log('✅ No CORS errors for companies API');
|
|
}
|
|
} else {
|
|
console.log('⚠️ Companies API was not called - checking why...');
|
|
}
|
|
|
|
// Look for company selector dropdown
|
|
const companySelectors = [
|
|
'.p-dropdown',
|
|
'select',
|
|
'[data-testid="company-select"]',
|
|
'.company-selector'
|
|
];
|
|
|
|
let companySelectorFound = false;
|
|
for (const selector of companySelectors) {
|
|
const element = page.locator(selector).first();
|
|
if (await element.isVisible()) {
|
|
console.log(`✅ Company selector found: ${selector}`);
|
|
companySelectorFound = true;
|
|
|
|
// Try to interact with it
|
|
await element.click();
|
|
await page.waitForTimeout(500);
|
|
|
|
// Look for dropdown options
|
|
const options = page.locator('.p-dropdown-item, option');
|
|
const optionCount = await options.count();
|
|
console.log(`📊 Company options available: ${optionCount}`);
|
|
|
|
if (optionCount > 0) {
|
|
// Select first company
|
|
await options.first().click();
|
|
console.log('✅ Company selected');
|
|
|
|
// Wait for data to load after company selection
|
|
await page.waitForTimeout(3000);
|
|
|
|
// Check for additional API calls after company selection
|
|
const invoicesRequests = networkRequests.filter(req =>
|
|
req.url.includes('/invoices')
|
|
);
|
|
const paymentsRequests = networkRequests.filter(req =>
|
|
req.url.includes('/payments')
|
|
);
|
|
|
|
console.log(`📊 Invoices API requests: ${invoicesRequests.length}`);
|
|
console.log(`📊 Payments API requests: ${paymentsRequests.length}`);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!companySelectorFound) {
|
|
console.log('⚠️ Company selector not found - taking screenshot for analysis');
|
|
await page.screenshot({ path: 'dashboard-no-company-selector.png', fullPage: true });
|
|
}
|
|
|
|
// Phase 3: Test Navigation to Reports
|
|
console.log('\n📍 Phase 3: Navigation to Reports');
|
|
|
|
// Look for navigation links
|
|
const navLinks = [
|
|
'text=/facturi/i',
|
|
'text=/invoices/i',
|
|
'text=/încasări/i',
|
|
'text=/payments/i',
|
|
'[href*="/invoices"]',
|
|
'[href*="/payments"]'
|
|
];
|
|
|
|
for (const linkSelector of navLinks) {
|
|
const link = page.locator(linkSelector).first();
|
|
if (await link.isVisible()) {
|
|
const linkText = await link.textContent();
|
|
console.log(`✅ Found navigation link: "${linkText}"`);
|
|
|
|
// Click the link
|
|
await link.click();
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Check if we navigated to the correct page
|
|
const currentUrl = page.url();
|
|
console.log(`📍 Navigated to: ${currentUrl}`);
|
|
|
|
// Wait for page content to load
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Take screenshot of the reports page
|
|
await page.screenshot({
|
|
path: `reports-${linkText.toLowerCase().replace(/[^a-z0-9]/g, '-')}.png`,
|
|
fullPage: true
|
|
});
|
|
|
|
// Look for data tables or report content
|
|
const dataElements = [
|
|
'table',
|
|
'.p-datatable',
|
|
'.data-table',
|
|
'.report-table',
|
|
'.invoice-list',
|
|
'.payment-list'
|
|
];
|
|
|
|
let dataFound = false;
|
|
for (const selector of dataElements) {
|
|
const element = page.locator(selector);
|
|
if (await element.isVisible()) {
|
|
const rowCount = await element.locator('tr, .row').count();
|
|
console.log(`✅ Data table found with ${rowCount} rows`);
|
|
dataFound = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!dataFound) {
|
|
console.log('⚠️ No data tables found - checking for loading states or errors');
|
|
|
|
// Check for loading indicators
|
|
const loadingElements = [
|
|
'.loading',
|
|
'.p-progress-spinner',
|
|
'.spinner',
|
|
'[data-testid="loading"]'
|
|
];
|
|
|
|
for (const loadingSelector of loadingElements) {
|
|
if (await page.locator(loadingSelector).isVisible()) {
|
|
console.log('⏳ Loading indicator found - data may still be loading');
|
|
await page.waitForTimeout(5000); // Wait longer for data
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Go back to dashboard for next test
|
|
await page.goto('/dashboard');
|
|
await page.waitForTimeout(1000);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Take final dashboard screenshot
|
|
await page.screenshot({ path: 'final-dashboard-state.png', fullPage: true });
|
|
});
|
|
|
|
test('🔍 API Endpoints Health Check', async ({ page }) => {
|
|
console.log('\n🔍 === API ENDPOINTS HEALTH CHECK ===');
|
|
|
|
// First authenticate to get access token
|
|
await loginPage.navigate();
|
|
await page.fill('#username', 'MARIUS M');
|
|
await page.fill('#password input', 'PAROLA9911');
|
|
|
|
const [authResponse] = await Promise.all([
|
|
page.waitForResponse('**/auth/login'),
|
|
page.click('button[type="submit"]')
|
|
]);
|
|
|
|
expect(authResponse.status()).toBe(200);
|
|
await page.waitForURL(/.*dashboard/);
|
|
|
|
// Test individual API endpoints
|
|
const endpoints = [
|
|
'/api/companies',
|
|
'/api/invoices',
|
|
'/api/payments'
|
|
];
|
|
|
|
for (const endpoint of endpoints) {
|
|
console.log(`\n--- Testing endpoint: ${endpoint} ---`);
|
|
|
|
// Navigate to trigger API call
|
|
if (endpoint.includes('invoices')) {
|
|
await page.goto('/invoices');
|
|
} else if (endpoint.includes('payments')) {
|
|
await page.goto('/payments');
|
|
} else {
|
|
await page.goto('/dashboard');
|
|
}
|
|
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Check if API was called
|
|
const apiCalls = networkRequests.filter(req => req.url.includes(endpoint));
|
|
|
|
if (apiCalls.length > 0) {
|
|
console.log(`✅ ${endpoint} was called (${apiCalls.length} requests)`);
|
|
|
|
// Check for errors
|
|
const errors = apiErrors.filter(err => err.url.includes(endpoint));
|
|
if (errors.length > 0) {
|
|
console.log(`❌ Errors found for ${endpoint}:`);
|
|
errors.forEach(err => {
|
|
console.log(` - ${err.status} ${err.statusText}`);
|
|
});
|
|
} else {
|
|
console.log(`✅ ${endpoint} returned successful responses`);
|
|
}
|
|
} else {
|
|
console.log(`⚠️ ${endpoint} was not called`);
|
|
}
|
|
}
|
|
});
|
|
|
|
test.afterEach(async ({ page }) => {
|
|
// Generate test report
|
|
console.log('\n📋 === TEST REPORT ===');
|
|
console.log(`🌐 Total Network Requests: ${networkRequests.length}`);
|
|
console.log(`❌ API Errors: ${apiErrors.length}`);
|
|
|
|
if (apiErrors.length > 0) {
|
|
console.log('\n❌ API Errors Details:');
|
|
apiErrors.forEach(error => {
|
|
console.log(` - ${error.status} ${error.url} (${error.statusText})`);
|
|
});
|
|
}
|
|
|
|
// Check for specific CORS errors
|
|
const corsErrors = apiErrors.filter(err =>
|
|
err.statusText.includes('CORS') || err.status === 0
|
|
);
|
|
|
|
if (corsErrors.length > 0) {
|
|
console.log('\n🚨 CORS Issues Detected:');
|
|
corsErrors.forEach(error => {
|
|
console.log(` - ${error.url}`);
|
|
});
|
|
console.log('\n💡 Recommendation: Check CORS configuration in backend');
|
|
} else {
|
|
console.log('\n✅ No CORS issues detected');
|
|
}
|
|
});
|
|
}); |