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,192 @@
import { test, expect } from '@playwright/test';
import { LoginPage } from '../page-objects/LoginPage.js';
test.describe('🔧 Button Fix Test - Identify Disabled State Issue', () => {
let loginPage;
test.beforeEach(async ({ page }) => {
loginPage = new LoginPage(page);
await loginPage.navigate();
});
test('🐛 Debug Button Disabled State Logic', async ({ page }) => {
console.log('\n=== DEBUGGING BUTTON DISABLED STATE ===');
// Helper function to get detailed button state
const getButtonState = async () => {
return await page.evaluate(() => {
const usernameInput = document.getElementById('username');
const passwordInput = document.querySelector('#password input');
const button = document.querySelector('button[type="submit"]');
// Get Vue component data if available
const vueApp = document.querySelector('#app').__vue__;
let vueData = null;
try {
// Try to access Vue component state
const loginComponent = document.querySelector('.login-container').__vueParentComponent;
if (loginComponent && loginComponent.setupState) {
vueData = {
credentials: loginComponent.setupState.credentials?.value,
formErrors: loginComponent.setupState.formErrors?.value,
isFormValid: loginComponent.setupState.isFormValid?.value
};
}
} catch (e) {
console.log('Could not access Vue state:', e.message);
}
return {
dom: {
usernameValue: usernameInput?.value || '',
passwordValue: passwordInput?.value || '',
buttonDisabled: button?.disabled,
buttonClasses: button?.className,
usernameRequired: usernameInput?.required,
passwordRequired: passwordInput?.required
},
vue: vueData
};
});
};
// Test 1: Initial state
console.log('\n--- Test 1: Initial State ---');
let state = await getButtonState();
console.log('Initial state:', JSON.stringify(state, null, 2));
// Test 2: Fill only username
console.log('\n--- Test 2: Username Only ---');
await page.fill('#username', 'test_user');
await page.waitForTimeout(500); // Wait for Vue reactivity
state = await getButtonState();
console.log('Username only state:', JSON.stringify(state, null, 2));
// Test 3: Fill both fields
console.log('\n--- Test 3: Both Fields ---');
await page.fill('#password input', 'test_password');
await page.waitForTimeout(500); // Wait for Vue reactivity
state = await getButtonState();
console.log('Both fields state:', JSON.stringify(state, null, 2));
// Test 4: Check if validation triggers
console.log('\n--- Test 4: Trigger Validation ---');
await page.click('.login-card'); // Click outside to blur
await page.waitForTimeout(500);
state = await getButtonState();
console.log('After blur state:', JSON.stringify(state, null, 2));
// Test 5: Manual button click attempt
console.log('\n--- Test 5: Button Click Attempt ---');
const isClickable = await page.evaluate(() => {
const button = document.querySelector('button[type="submit"]');
return !button.disabled;
});
console.log('Button is clickable:', isClickable);
if (isClickable) {
console.log('✅ Button should be clickable');
} else {
console.log('❌ Button is still disabled - investigating why...');
// Check validation logic
const validationState = await page.evaluate(() => {
const usernameInput = document.getElementById('username');
const passwordInput = document.querySelector('#password input');
return {
usernameEmpty: !usernameInput.value.trim(),
passwordEmpty: !passwordInput.value.trim(),
usernameLength: usernameInput.value.length,
passwordLength: passwordInput.value.length,
formValidity: usernameInput.form?.checkValidity()
};
});
console.log('Validation details:', JSON.stringify(validationState, null, 2));
}
// Take screenshot for analysis
await page.screenshot({ path: 'button-debug.png', fullPage: true });
});
test('🔄 Test Button Reactivity with Real Input', async ({ page }) => {
console.log('\n=== TESTING BUTTON REACTIVITY ===');
// Monitor button state changes
const buttonStates = [];
const checkButton = async (action) => {
const disabled = await page.locator('button[type="submit"]').isDisabled();
buttonStates.push({ action, disabled });
console.log(`After ${action}: disabled = ${disabled}`);
};
await checkButton('initial load');
// Type character by character to see when button enables
const username = 'test';
const password = 'pass';
for (let i = 0; i < username.length; i++) {
await page.fill('#username', username.substring(0, i + 1));
await page.waitForTimeout(100);
await checkButton(`username: "${username.substring(0, i + 1)}"`);
}
for (let i = 0; i < password.length; i++) {
await page.fill('#password input', password.substring(0, i + 1));
await page.waitForTimeout(100);
await checkButton(`password: "${password.substring(0, i + 1)}"`);
}
console.log('\nButton state progression:');
buttonStates.forEach((state, index) => {
console.log(`${index + 1}. ${state.action}: ${state.disabled ? 'DISABLED' : 'ENABLED'}`);
});
});
test('🎯 Force Button Enable Test', async ({ page }) => {
console.log('\n=== TESTING FORCED BUTTON ENABLE ===');
// Fill valid data
await page.fill('#username', 'valid_user');
await page.fill('#password input', 'valid_password');
// Wait for Vue reactivity
await page.waitForTimeout(1000);
// Force enable button via JavaScript if needed
const buttonEnabled = await page.evaluate(() => {
const button = document.querySelector('button[type="submit"]');
const wasDisabled = button.disabled;
// Try to force enable for testing
button.disabled = false;
button.classList.remove('p-disabled');
return { wasDisabled, nowDisabled: button.disabled };
});
console.log('Button enable attempt:', buttonEnabled);
if (!buttonEnabled.nowDisabled) {
console.log('✅ Button was successfully enabled');
// Try to click it now
await page.click('button[type="submit"]');
console.log('✅ Button click succeeded');
// Wait for potential API call
await page.waitForTimeout(2000);
// Check if login was attempted
const currentUrl = page.url();
console.log('Current URL after click:', currentUrl);
} else {
console.log('❌ Could not enable button');
}
});
});

View File

@@ -0,0 +1,324 @@
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');
}
});
});

View File

@@ -0,0 +1,228 @@
import { test, expect } from '@playwright/test';
import { LoginPage } from '../../page-objects/LoginPage.js';
import { DashboardPage } from '../../page-objects/DashboardPage.js';
import { testCredentials } from '../../fixtures/auth.js';
test.describe('Dashboard View', () => {
let loginPage;
let dashboardPage;
test.beforeEach(async ({ page }) => {
loginPage = new LoginPage(page);
dashboardPage = new DashboardPage(page);
// Mock successful authentication
await page.route('**/api/auth/login', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
access_token: 'mock_access_token',
refresh_token: 'mock_refresh_token',
user: {
id: 1,
username: 'testuser',
full_name: 'Test User'
}
}),
});
});
// Mock companies endpoint
await page.route('**/api/companies', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([
{ code: 'COMP1', name: 'Compania Test 1' },
{ code: 'COMP2', name: 'Compania Test 2' }
]),
});
});
// Mock invoices summary endpoint
await page.route('**/api/invoices/*/summary', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
total: 150,
paid: 120,
overdue: 30,
amount: 850000.50
}),
});
});
// Mock payments summary endpoint
await page.route('**/api/payments/*/summary', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
total: 125,
amount: 750000.25
}),
});
});
// Login first
await loginPage.navigate();
await loginPage.login(testCredentials.valid.username, testCredentials.valid.password);
await page.waitForURL('/dashboard');
});
test('should display dashboard page correctly', async ({ page: _page }) => {
// Check page elements
expect(await dashboardPage.isOnDashboardPage()).toBe(true);
// Check page title contains "Dashboard"
const title = await dashboardPage.getPageTitle();
expect(title).toContain('Dashboard');
// Check welcome message includes username
const welcomeMessage = await dashboardPage.getWelcomeMessage();
expect(welcomeMessage).toContain('testuser');
});
test('should show company selection when no company selected', async ({ page: _page }) => {
// Wait for dashboard to load
await dashboardPage.waitForDashboardLoad();
// Should show company selection card
expect(await dashboardPage.isCompanySelectionVisible()).toBe(true);
// Dashboard content should not be visible yet
expect(await dashboardPage.isDashboardContentVisible()).toBe(false);
});
test('should display dashboard content after company selection', async ({ page }) => {
// Wait for dashboard to load
await dashboardPage.waitForDashboardLoad();
// Select a company
await dashboardPage.selectCompany('Compania Test 1');
// Wait for dashboard content to appear
await page.waitForSelector(dashboardPage.dashboardContent, { timeout: 10000 });
// Dashboard content should now be visible
expect(await dashboardPage.isDashboardContentVisible()).toBe(true);
// Stats cards should be visible
expect(await dashboardPage.areStatsCardsVisible()).toBe(true);
});
test('should display correct statistics after company selection', async ({ page }) => {
// Wait for dashboard to load and select company
await dashboardPage.waitForDashboardLoad();
await dashboardPage.selectCompany('Compania Test 1');
// Wait for stats to load
await page.waitForSelector(dashboardPage.statsGrid, { timeout: 10000 });
// Check statistics values
const stats = await dashboardPage.getStatsData();
expect(stats.invoices).toBe('150');
expect(stats.payments).toBe('125');
expect(stats.company).toContain('Compania Test 1');
});
test('should navigate to invoices view when clicking invoices action', async ({ page }) => {
// Setup dashboard with company selected
await dashboardPage.waitForDashboardLoad();
await dashboardPage.selectCompany('Compania Test 1');
await page.waitForSelector(dashboardPage.dashboardContent);
// Click invoices action button
await dashboardPage.clickInvoicesAction();
// Should navigate to invoices page
await page.waitForURL('/invoices');
expect(page.url()).toContain('/invoices');
});
test('should navigate to payments view when clicking payments action', async ({ page }) => {
// Setup dashboard with company selected
await dashboardPage.waitForDashboardLoad();
await dashboardPage.selectCompany('Compania Test 1');
await page.waitForSelector(dashboardPage.dashboardContent);
// Click payments action button
await dashboardPage.clickPaymentsAction();
// Should navigate to payments page
await page.waitForURL('/payments');
expect(page.url()).toContain('/payments');
});
test('should handle API errors gracefully', async ({ page }) => {
// Mock companies API error
await page.route('**/api/companies', async route => {
await route.fulfill({
status: 500,
contentType: 'application/json',
body: JSON.stringify({
detail: 'Internal server error'
}),
});
});
// Navigate to dashboard
await dashboardPage.navigate();
// Should still show the page but might show error messages
expect(await dashboardPage.isOnDashboardPage()).toBe(true);
// Check for error toast messages
const errorToast = page.locator('.p-toast-message-error');
if (await errorToast.isVisible()) {
const errorText = await errorToast.textContent();
expect(errorText.toLowerCase()).toContain('eroare');
}
});
test('should update stats when switching between companies', async ({ page }) => {
// Mock different stats for second company
await page.route('**/api/invoices/COMP2/summary', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
total: 200,
paid: 180,
overdue: 20,
amount: 1200000.75
}),
});
});
await page.route('**/api/payments/COMP2/summary', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
total: 175,
amount: 950000.50
}),
});
});
// Select first company
await dashboardPage.waitForDashboardLoad();
await dashboardPage.selectCompany('Compania Test 1');
await page.waitForSelector(dashboardPage.statsGrid);
const stats1 = await dashboardPage.getStatsData();
expect(stats1.invoices).toBe('150');
// Switch to second company
await dashboardPage.selectCompany('Compania Test 2');
await dashboardPage.waitForLoadingToFinish();
const stats2 = await dashboardPage.getStatsData();
expect(stats2.invoices).toBe('200');
expect(stats2.company).toContain('Compania Test 2');
});
});

View File

@@ -0,0 +1,324 @@
//! 🔍 DEBUGGING COMPREHENSIVE TEST - ROA2WEB Real Issues Detection
//! Created: 2025-08-04
//! Purpose: Find and fix REAL problems, not just "passing tests"
import { test, expect } from '@playwright/test';
import { LoginPage } from '../page-objects/LoginPage.js';
test.describe('🔍 ROA2WEB Real Issues Debugging Suite', () => {
let loginPage;
let networkRequests = [];
let consoleErrors = [];
let apiResponses = [];
test.beforeEach(async ({ page }) => {
// Reset monitoring arrays
networkRequests = [];
consoleErrors = [];
apiResponses = [];
// Setup comprehensive monitoring
page.on('request', request => {
networkRequests.push({
url: request.url(),
method: request.method(),
headers: request.headers(),
postData: request.postData(),
timestamp: new Date().toISOString()
});
});
page.on('response', response => {
apiResponses.push({
url: response.url(),
status: response.status(),
statusText: response.statusText(),
headers: response.headers(),
timestamp: new Date().toISOString()
});
// Log failed requests immediately
if (response.status() >= 400) {
console.log(`❌ API Error: ${response.status()} ${response.url()}`);
}
});
page.on('console', msg => {
if (msg.type() === 'error') {
const error = {
type: msg.type(),
text: msg.text(),
location: msg.location(),
timestamp: new Date().toISOString()
};
consoleErrors.push(error);
console.log(`🔥 Console Error: ${msg.text()}`);
}
});
page.on('pageerror', err => {
consoleErrors.push({
type: 'pageerror',
text: err.message,
stack: err.stack,
timestamp: new Date().toISOString()
});
console.log(`💥 Page Error: ${err.message}`);
});
loginPage = new LoginPage(page);
await loginPage.navigate();
});
test('🧪 REAL AUTH FLOW - Find FormData vs JSON Issues', async ({ page }) => {
console.log('\n🔍 === TESTING REAL AUTHENTICATION FLOW ===');
// Fill real credentials (update these with actual test credentials)
const username = 'test_user';
const password = 'test_password';
await page.fill('#username', username);
await page.fill('#password input', password);
// Monitor the actual request being sent
const [response] = await Promise.all([
page.waitForResponse('**/auth/login'),
page.click('button[type="submit"]')
]);
// CRITICAL: Analyze the actual request format
const request = response.request();
const postData = request.postData();
const contentType = request.headers()['content-type'];
console.log('\n📊 === REQUEST ANALYSIS ===');
console.log('Content-Type:', contentType);
console.log('Request Method:', request.method());
console.log('Request Body:', postData);
console.log('Response Status:', response.status());
// Check if FormData or JSON is being sent
if (contentType && contentType.includes('application/json')) {
console.log('✅ Sending JSON (correct)');
try {
const jsonData = JSON.parse(postData);
expect(jsonData).toHaveProperty('username');
expect(jsonData).toHaveProperty('password');
} catch (e) {
console.log('❌ Invalid JSON format');
}
} else if (contentType && contentType.includes('multipart/form-data')) {
console.log('⚠️ Sending FormData (may cause issues)');
} else {
console.log('❓ Unknown content type:', contentType);
}
// Check response
if (response.status() === 422) {
const responseBody = await response.text();
console.log('🚨 422 Validation Error:', responseBody);
}
// Generate comprehensive monitoring report
console.log('\n📈 === MONITORING REPORT ===');
console.log(`Network Requests: ${networkRequests.length}`);
console.log(`API Responses: ${apiResponses.length}`);
console.log(`Console Errors: ${consoleErrors.length}`);
// Take screenshot for analysis
await page.screenshot({
path: 'debug-auth-flow.png',
fullPage: true
});
});
test('🔧 LOGIN BUTTON STATE - Debug Disabled Logic', async ({ page }) => {
console.log('\n🔍 === DEBUGGING LOGIN BUTTON STATE ===');
// Test initial state
const initialDisabled = await page.locator('button[type="submit"]').isDisabled();
console.log('Initial button disabled:', initialDisabled);
// Test empty fields
await page.fill('#username', '');
await page.fill('#password input', '');
const emptyFieldsDisabled = await page.locator('button[type="submit"]').isDisabled();
console.log('Empty fields - button disabled:', emptyFieldsDisabled);
// Test with only username
await page.fill('#username', 'test');
const usernameOnlyDisabled = await page.locator('button[type="submit"]').isDisabled();
console.log('Username only - button disabled:', usernameOnlyDisabled);
// Test with both fields
await page.fill('#password input', 'password');
const bothFieldsDisabled = await page.locator('button[type="submit"]').isDisabled();
console.log('Both fields - button disabled:', bothFieldsDisabled);
// Check form validation state
const formValidation = await page.evaluate(() => {
const usernameInput = document.getElementById('username');
const passwordInput = document.querySelector('#password input');
const button = document.querySelector('button[type="submit"]');
return {
usernameValue: usernameInput?.value,
passwordValue: passwordInput?.value,
buttonDisabled: button?.disabled,
buttonClasses: button?.className,
usernameValid: usernameInput?.checkValidity(),
passwordValid: passwordInput?.checkValidity()
};
});
console.log('Form validation state:', JSON.stringify(formValidation, null, 2));
// Take screenshot of current state
await page.screenshot({ path: 'debug-button-state.png' });
});
test('🚨 ERROR MESSAGE FORMAT - Debug Toast Issues', async ({ page }) => {
console.log('\n🔍 === DEBUGGING ERROR MESSAGE FORMAT ===');
// Force a network error by using wrong endpoint
await page.route('**/auth/login', async route => {
await route.fulfill({
status: 500,
contentType: 'application/json',
body: JSON.stringify({ detail: 'Server error for testing' })
});
});
await page.fill('#username', 'test');
await page.fill('#password input', 'test');
await page.click('button[type="submit"]');
// Wait for error message and analyze its content
await page.waitForTimeout(2000); // Wait for toast to appear
// Check various possible error message selectors
const errorSelectors = [
'.error-message',
'.p-toast-message-error',
'.p-toast-message-text',
'.p-toast-summary',
'.p-toast-detail'
];
for (const selector of errorSelectors) {
const elements = await page.locator(selector).all();
for (let i = 0; i < elements.length; i++) {
const text = await elements[i].textContent();
if (text && text.trim()) {
console.log(`Error message found in ${selector}[${i}]:`, text);
}
}
}
// Check all visible text content that might contain error messages
const allText = await page.evaluate(() => {
const textNodes = [];
const walker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_TEXT,
null,
false
);
let node;
while (node = walker.nextNode()) {
const text = node.textContent.trim();
if (text.toLowerCase().includes('eroare') ||
text.toLowerCase().includes('error') ||
text.toLowerCase().includes('conectare')) {
textNodes.push(text);
}
}
return textNodes;
});
console.log('All error-related text found:', allText);
// Take screenshot of error state
await page.screenshot({ path: 'debug-error-messages.png' });
});
test('🌐 NETWORK MONITORING - Real API Behavior', async ({ page }) => {
console.log('\n🔍 === COMPREHENSIVE NETWORK MONITORING ===');
// Test various scenarios
const testScenarios = [
{ name: 'Valid Login', username: 'valid_user', password: 'valid_pass' },
{ name: 'Invalid Credentials', username: 'invalid', password: 'invalid' },
{ name: 'Empty Credentials', username: '', password: '' }
];
for (const scenario of testScenarios) {
console.log(`\n--- Testing: ${scenario.name} ---`);
// Clear form
await page.fill('#username', '');
await page.fill('#password input', '');
// Fill credentials if provided
if (scenario.username) await page.fill('#username', scenario.username);
if (scenario.password) await page.fill('#password input', scenario.password);
// Reset monitoring arrays for this scenario
networkRequests.length = 0;
apiResponses.length = 0;
consoleErrors.length = 0;
// Try to submit (if button is enabled)
const isDisabled = await page.locator('button[type="submit"]').isDisabled();
if (!isDisabled) {
try {
const [response] = await Promise.all([
page.waitForResponse('**/auth/login', { timeout: 5000 }),
page.click('button[type="submit"]')
]);
console.log(`Response Status: ${response.status()}`);
const responseBody = await response.text();
console.log(`Response Body: ${responseBody.substring(0, 200)}...`);
} catch (error) {
console.log(`No API call made: ${error.message}`);
}
} else {
console.log('Button is disabled - no API call expected');
}
// Wait a bit for any async operations
await page.waitForTimeout(1000);
console.log(`Network requests: ${networkRequests.length}`);
console.log(`Console errors: ${consoleErrors.length}`);
}
});
test.afterEach(async ({ page }) => {
// Generate final report
console.log('\n📋 === FINAL TEST REPORT ===');
console.log(`Total Network Requests: ${networkRequests.length}`);
console.log(`Total API Responses: ${apiResponses.length}`);
console.log(`Total Console Errors: ${consoleErrors.length}`);
if (consoleErrors.length > 0) {
console.log('\n🚨 Console Errors Found:');
consoleErrors.forEach((error, index) => {
console.log(`${index + 1}. ${error.text}`);
});
}
if (apiResponses.some(r => r.status >= 400)) {
console.log('\n❌ Failed API Requests:');
apiResponses
.filter(r => r.status >= 400)
.forEach(response => {
console.log(`${response.status} ${response.url}`);
});
}
});
});

View File

@@ -0,0 +1,214 @@
import { test, expect } from '@playwright/test';
import { LoginPage } from '../../page-objects/LoginPage.js';
import { InvoicesPage } from '../../page-objects/InvoicesPage.js';
import { testCredentials } from '../../fixtures/auth.js';
import { mockInvoices } from '../../fixtures/invoices.js';
test.describe('Invoices View', () => {
let loginPage;
let invoicesPage;
test.beforeEach(async ({ page }) => {
loginPage = new LoginPage(page);
invoicesPage = new InvoicesPage(page);
// Mock authentication
await page.route('**/api/auth/login', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
access_token: 'mock_access_token',
refresh_token: 'mock_refresh_token',
user: { id: 1, username: 'testuser', full_name: 'Test User' }
}),
});
});
// Mock companies
await page.route('**/api/companies', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([
{ code: 'COMP1', name: 'Compania Test 1' }
]),
});
});
// Mock invoices endpoint
await page.route('**/api/invoices/COMP1', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(mockInvoices),
});
});
// Login and navigate to invoices
await loginPage.navigate();
await loginPage.login(testCredentials.valid.username, testCredentials.valid.password);
await page.waitForURL('/dashboard');
await invoicesPage.navigate();
});
test('should display invoices page correctly', async ({ page: _page }) => {
expect(await invoicesPage.isOnInvoicesPage()).toBe(true);
const title = await invoicesPage.getPageTitle();
expect(title).toContain('Facturi');
});
test('should show company selection when no company selected', async ({ page: _page }) => {
await invoicesPage.waitForPageLoad();
expect(await invoicesPage.isCompanySelectionVisible()).toBe(true);
expect(await invoicesPage.isInvoicesTableVisible()).toBe(false);
});
test('should display invoices table after company selection', async ({ page }) => {
await invoicesPage.waitForPageLoad();
await invoicesPage.selectCompany('Compania Test 1');
await page.waitForSelector(invoicesPage.invoicesTable, { timeout: 10000 });
expect(await invoicesPage.isInvoicesTableVisible()).toBe(true);
});
test('should filter invoices by search term', async ({ page }) => {
await invoicesPage.waitForPageLoad();
await invoicesPage.selectCompany('Compania Test 1');
await page.waitForSelector(invoicesPage.invoicesTable);
// Search for specific invoice
await invoicesPage.searchInvoices('INV001');
await invoicesPage.waitForLoadingToFinish();
const visibleRows = await invoicesPage.getVisibleInvoicesCount();
expect(visibleRows).toBeGreaterThan(0);
// Check that displayed invoices contain search term
const firstRowData = await invoicesPage.getFirstInvoiceData();
expect(firstRowData.number).toContain('INV001');
});
test('should filter invoices by status', async ({ page }) => {
await invoicesPage.waitForPageLoad();
await invoicesPage.selectCompany('Compania Test 1');
await page.waitForSelector(invoicesPage.invoicesTable);
// Filter by paid status
await invoicesPage.filterByStatus('paid');
await invoicesPage.waitForLoadingToFinish();
const visibleRows = await invoicesPage.getVisibleInvoicesCount();
expect(visibleRows).toBeGreaterThan(0);
});
test('should sort invoices by date', async ({ page }) => {
await invoicesPage.waitForPageLoad();
await invoicesPage.selectCompany('Compania Test 1');
await page.waitForSelector(invoicesPage.invoicesTable);
// Click date column header to sort
await invoicesPage.sortByColumn('date');
await invoicesPage.waitForLoadingToFinish();
// Verify sorting worked
const firstRowDate = await invoicesPage.getFirstInvoiceData();
expect(firstRowDate.date).toBeTruthy();
});
test('should display invoice details when clicking on row', async ({ page }) => {
await invoicesPage.waitForPageLoad();
await invoicesPage.selectCompany('Compania Test 1');
await page.waitForSelector(invoicesPage.invoicesTable);
// Click on first invoice row
await invoicesPage.clickFirstInvoiceRow();
// Check if details panel or modal appears
expect(await invoicesPage.isInvoiceDetailsVisible()).toBe(true);
});
test('should export invoices data', async ({ page }) => {
await invoicesPage.waitForPageLoad();
await invoicesPage.selectCompany('Compania Test 1');
await page.waitForSelector(invoicesPage.invoicesTable);
// Set up download handler
const downloadPromise = page.waitForEvent('download');
await invoicesPage.clickExportButton();
const download = await downloadPromise;
expect(download.suggestedFilename()).toContain('facturi');
});
test('should handle pagination correctly', async ({ page }) => {
// Mock large dataset
await page.route('**/api/invoices/COMP1*', async route => {
const url = route.request().url();
const urlParams = new URL(url).searchParams;
const page_num = parseInt(urlParams.get('page') || '1');
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
items: mockInvoices.slice((page_num - 1) * 10, page_num * 10),
total: 25,
page: page_num,
size: 10,
pages: 3
}),
});
});
await invoicesPage.waitForPageLoad();
await invoicesPage.selectCompany('Compania Test 1');
await page.waitForSelector(invoicesPage.invoicesTable);
// Check pagination controls appear
expect(await invoicesPage.isPaginationVisible()).toBe(true);
// Navigate to next page
await invoicesPage.goToNextPage();
await invoicesPage.waitForLoadingToFinish();
// Verify page changed
const currentPage = await invoicesPage.getCurrentPage();
expect(currentPage).toBe(2);
});
test('should handle API errors gracefully', async ({ page }) => {
// Mock API error
await page.route('**/api/invoices/COMP1', async route => {
await route.fulfill({
status: 500,
contentType: 'application/json',
body: JSON.stringify({ detail: 'Internal server error' }),
});
});
await invoicesPage.waitForPageLoad();
await invoicesPage.selectCompany('Compania Test 1');
// Should show error message
const errorToast = page.locator('.p-toast-message-error');
if (await errorToast.isVisible()) {
const errorText = await errorToast.textContent();
expect(errorText.toLowerCase()).toContain('eroare');
}
});
test('should refresh data when refresh button clicked', async ({ page }) => {
await invoicesPage.waitForPageLoad();
await invoicesPage.selectCompany('Compania Test 1');
await page.waitForSelector(invoicesPage.invoicesTable);
// Click refresh button
await invoicesPage.clickRefreshButton();
await invoicesPage.waitForLoadingToFinish();
// Table should still be visible after refresh
expect(await invoicesPage.isInvoicesTableVisible()).toBe(true);
});
});

View File

@@ -0,0 +1,254 @@
import { test, expect } from '@playwright/test';
import { LoginPage } from '../../page-objects/LoginPage.js';
import { PaymentsPage } from '../../page-objects/PaymentsPage.js';
import { testCredentials } from '../../fixtures/auth.js';
import { mockPayments } from '../../fixtures/payments.js';
test.describe('Payments View', () => {
let loginPage;
let paymentsPage;
test.beforeEach(async ({ page }) => {
loginPage = new LoginPage(page);
paymentsPage = new PaymentsPage(page);
// Mock authentication
await page.route('**/api/auth/login', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
access_token: 'mock_access_token',
refresh_token: 'mock_refresh_token',
user: { id: 1, username: 'testuser', full_name: 'Test User' }
}),
});
});
// Mock companies
await page.route('**/api/companies', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([
{ code: 'COMP1', name: 'Compania Test 1' }
]),
});
});
// Mock payments endpoint
await page.route('**/api/payments/COMP1', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(mockPayments),
});
});
// Login and navigate to payments
await loginPage.navigate();
await loginPage.login(testCredentials.valid.username, testCredentials.valid.password);
await page.waitForURL('/dashboard');
await paymentsPage.navigate();
});
test('should display payments page correctly', async ({ page: _page }) => {
expect(await paymentsPage.isOnPaymentsPage()).toBe(true);
const title = await paymentsPage.getPageTitle();
expect(title).toContain('Încasări');
});
test('should show company selection when no company selected', async ({ page: _page }) => {
await paymentsPage.waitForPageLoad();
expect(await paymentsPage.isCompanySelectionVisible()).toBe(true);
expect(await paymentsPage.isPaymentsTableVisible()).toBe(false);
});
test('should display payments table after company selection', async ({ page }) => {
await paymentsPage.waitForPageLoad();
await paymentsPage.selectCompany('Compania Test 1');
await page.waitForSelector(paymentsPage.paymentsTable, { timeout: 10000 });
expect(await paymentsPage.isPaymentsTableVisible()).toBe(true);
});
test('should filter payments by search term', async ({ page }) => {
await paymentsPage.waitForPageLoad();
await paymentsPage.selectCompany('Compania Test 1');
await page.waitForSelector(paymentsPage.paymentsTable);
// Search for specific payment
await paymentsPage.searchPayments('PAY001');
await paymentsPage.waitForLoadingToFinish();
const visibleRows = await paymentsPage.getVisiblePaymentsCount();
expect(visibleRows).toBeGreaterThan(0);
// Check that displayed payments contain search term
const firstRowData = await paymentsPage.getFirstPaymentData();
expect(firstRowData.reference).toContain('PAY001');
});
test('should filter payments by method', async ({ page }) => {
await paymentsPage.waitForPageLoad();
await paymentsPage.selectCompany('Compania Test 1');
await page.waitForSelector(paymentsPage.paymentsTable);
// Filter by bank transfer
await paymentsPage.filterByMethod('bank_transfer');
await paymentsPage.waitForLoadingToFinish();
const visibleRows = await paymentsPage.getVisiblePaymentsCount();
expect(visibleRows).toBeGreaterThan(0);
});
test('should sort payments by date', async ({ page }) => {
await paymentsPage.waitForPageLoad();
await paymentsPage.selectCompany('Compania Test 1');
await page.waitForSelector(paymentsPage.paymentsTable);
// Click date column header to sort
await paymentsPage.sortByColumn('date');
await paymentsPage.waitForLoadingToFinish();
// Verify sorting worked
const firstRowDate = await paymentsPage.getFirstPaymentData();
expect(firstRowDate.date).toBeTruthy();
});
test('should display payment details when clicking on row', async ({ page }) => {
await paymentsPage.waitForPageLoad();
await paymentsPage.selectCompany('Compania Test 1');
await page.waitForSelector(paymentsPage.paymentsTable);
// Click on first payment row
await paymentsPage.clickFirstPaymentRow();
// Check if details panel or modal appears
expect(await paymentsPage.isPaymentDetailsVisible()).toBe(true);
});
test('should export payments data', async ({ page }) => {
await paymentsPage.waitForPageLoad();
await paymentsPage.selectCompany('Compania Test 1');
await page.waitForSelector(paymentsPage.paymentsTable);
// Set up download handler
const downloadPromise = page.waitForEvent('download');
await paymentsPage.clickExportButton();
const download = await downloadPromise;
expect(download.suggestedFilename()).toContain('incasari');
});
test('should display correct payment totals', async ({ page }) => {
await paymentsPage.waitForPageLoad();
await paymentsPage.selectCompany('Compania Test 1');
await page.waitForSelector(paymentsPage.paymentsTable);
// Check if totals card is visible and contains data
if (await paymentsPage.isTotalsCardVisible()) {
const totals = await paymentsPage.getTotalsData();
expect(parseFloat(totals.totalAmount)).toBeGreaterThan(0);
expect(parseInt(totals.totalCount)).toBeGreaterThan(0);
}
});
test('should filter payments by date range', async ({ page }) => {
await paymentsPage.waitForPageLoad();
await paymentsPage.selectCompany('Compania Test 1');
await page.waitForSelector(paymentsPage.paymentsTable);
// Apply this month filter
await paymentsPage.filterByDateRange('thisMonth');
await paymentsPage.waitForLoadingToFinish();
const visibleRows = await paymentsPage.getVisiblePaymentsCount();
expect(visibleRows).toBeGreaterThanOrEqual(0);
});
test('should handle pagination correctly', async ({ page }) => {
// Mock large dataset
await page.route('**/api/payments/COMP1*', async route => {
const url = route.request().url();
const urlParams = new URL(url).searchParams;
const page_num = parseInt(urlParams.get('page') || '1');
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
items: mockPayments.slice((page_num - 1) * 10, page_num * 10),
total: 25,
page: page_num,
size: 10,
pages: 3
}),
});
});
await paymentsPage.waitForPageLoad();
await paymentsPage.selectCompany('Compania Test 1');
await page.waitForSelector(paymentsPage.paymentsTable);
// Check pagination controls appear
expect(await paymentsPage.isPaginationVisible()).toBe(true);
// Navigate to next page
await paymentsPage.goToNextPage();
await paymentsPage.waitForLoadingToFinish();
// Verify page changed
const currentPage = await paymentsPage.getCurrentPage();
expect(currentPage).toBe(2);
});
test('should handle API errors gracefully', async ({ page }) => {
// Mock API error
await page.route('**/api/payments/COMP1', async route => {
await route.fulfill({
status: 500,
contentType: 'application/json',
body: JSON.stringify({ detail: 'Internal server error' }),
});
});
await paymentsPage.waitForPageLoad();
await paymentsPage.selectCompany('Compania Test 1');
// Should show error message
const errorToast = page.locator('.p-toast-message-error');
if (await errorToast.isVisible()) {
const errorText = await errorToast.textContent();
expect(errorText.toLowerCase()).toContain('eroare');
}
});
test('should refresh data when refresh button clicked', async ({ page }) => {
await paymentsPage.waitForPageLoad();
await paymentsPage.selectCompany('Compania Test 1');
await page.waitForSelector(paymentsPage.paymentsTable);
// Click refresh button
await paymentsPage.clickRefreshButton();
await paymentsPage.waitForLoadingToFinish();
// Table should still be visible after refresh
expect(await paymentsPage.isPaymentsTableVisible()).toBe(true);
});
test('should group payments by method in summary view', async ({ page }) => {
await paymentsPage.waitForPageLoad();
await paymentsPage.selectCompany('Compania Test 1');
await page.waitForSelector(paymentsPage.paymentsTable);
// Switch to summary view if available
if (await paymentsPage.isSummaryViewAvailable()) {
await paymentsPage.switchToSummaryView();
await paymentsPage.waitForLoadingToFinish();
expect(await paymentsPage.isSummaryViewVisible()).toBe(true);
}
});
});

View File

@@ -0,0 +1,364 @@
//! 🌍 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');
}
});
});

View File

@@ -0,0 +1,237 @@
import { test, expect } from '@playwright/test';
import { LoginPage } from '../../page-objects/LoginPage.js';
import { DashboardPage } from '../../page-objects/DashboardPage.js';
import { testCredentials } from '../../fixtures/auth.js';
test.describe('Responsive Design Tests', () => {
let loginPage;
let dashboardPage;
// Common setup for all responsive tests
const setupMockAuth = async (page) => {
await page.route('**/api/auth/login', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
access_token: 'mock_access_token',
refresh_token: 'mock_refresh_token',
user: { id: 1, username: 'testuser', full_name: 'Test User' }
}),
});
});
await page.route('**/api/companies', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([
{ code: 'COMP1', name: 'Test Company' }
]),
});
});
};
test.describe('Mobile Layout (320px)', () => {
test.beforeEach(async ({ page }) => {
await page.setViewportSize({ width: 320, height: 568 });
loginPage = new LoginPage(page);
dashboardPage = new DashboardPage(page);
await setupMockAuth(page);
});
test('should display login form correctly on mobile', async ({ page }) => {
await loginPage.navigate();
// Take screenshot for visual verification
await page.screenshot({ path: 'mobile-login.png', fullPage: true });
// Login form should be visible and properly sized
await expect(page.locator(loginPage.loginCard)).toBeVisible();
await expect(page.locator(loginPage.usernameInput)).toBeVisible();
await expect(page.locator(loginPage.passwordInput)).toBeVisible();
// Check that form takes appropriate width
const cardWidth = await page.locator(loginPage.loginCard).boundingBox();
expect(cardWidth.width).toBeLessThan(320); // Should fit in viewport
});
test('should adapt dashboard layout for mobile', async ({ page }) => {
// Login and navigate to dashboard
await loginPage.navigate();
await loginPage.login(testCredentials.valid.username, testCredentials.valid.password);
await page.waitForURL('/dashboard');
// Take screenshot
await page.screenshot({ path: 'mobile-dashboard.png', fullPage: true });
// Dashboard should be responsive
await expect(page.locator(dashboardPage.pageTitle)).toBeVisible();
// Stats grid should stack vertically on mobile
const statsGrid = page.locator(dashboardPage.statsGrid);
if (await statsGrid.isVisible()) {
const gridBox = await statsGrid.boundingBox();
expect(gridBox.width).toBeLessThan(320);
}
});
});
test.describe('Tablet Layout (768px)', () => {
test.beforeEach(async ({ page }) => {
await page.setViewportSize({ width: 768, height: 1024 });
loginPage = new LoginPage(page);
dashboardPage = new DashboardPage(page);
await setupMockAuth(page);
});
test('should display login form appropriately on tablet', async ({ page }) => {
await loginPage.navigate();
await page.screenshot({ path: 'tablet-login.png', fullPage: true });
// Login card should be centered and well-proportioned
const loginCard = page.locator(loginPage.loginCard);
await expect(loginCard).toBeVisible();
const cardBox = await loginCard.boundingBox();
expect(cardBox.width).toBeGreaterThan(300);
expect(cardBox.width).toBeLessThan(500);
});
test('should show proper tablet dashboard layout', async ({ page }) => {
await loginPage.navigate();
await loginPage.login(testCredentials.valid.username, testCredentials.valid.password);
await page.waitForURL('/dashboard');
await page.screenshot({ path: 'tablet-dashboard.png', fullPage: true });
// Dashboard elements should be properly spaced
await expect(page.locator(dashboardPage.pageTitle)).toBeVisible();
// Stats should be arranged in appropriate grid
const statsCards = page.locator('.stat-card');
const cardCount = await statsCards.count();
if (cardCount > 0) {
// Cards should be visible and properly sized
for (let i = 0; i < cardCount; i++) {
await expect(statsCards.nth(i)).toBeVisible();
}
}
});
});
test.describe('Desktop Layout (1024px+)', () => {
test.beforeEach(async ({ page }) => {
await page.setViewportSize({ width: 1024, height: 768 });
loginPage = new LoginPage(page);
dashboardPage = new DashboardPage(page);
await setupMockAuth(page);
});
test('should display full desktop login layout', async ({ page }) => {
await loginPage.navigate();
await page.screenshot({ path: 'desktop-login.png', fullPage: true });
// Login should be centered with appropriate sizing
const loginCard = page.locator(loginPage.loginCard);
await expect(loginCard).toBeVisible();
// Card should not take full width on desktop
const cardBox = await loginCard.boundingBox();
expect(cardBox.width).toBeLessThan(500);
});
test('should show complete desktop dashboard layout', async ({ page }) => {
await loginPage.navigate();
await loginPage.login(testCredentials.valid.username, testCredentials.valid.password);
await page.waitForURL('/dashboard');
await page.screenshot({ path: 'desktop-dashboard.png', fullPage: true });
// All dashboard elements should be visible
await expect(page.locator(dashboardPage.pageTitle)).toBeVisible();
await expect(page.locator(dashboardPage.pageSubtitle)).toBeVisible();
// Stats grid should use horizontal layout
const statsGrid = page.locator(dashboardPage.statsGrid);
if (await statsGrid.isVisible()) {
const gridBox = await statsGrid.boundingBox();
expect(gridBox.width).toBeGreaterThan(600);
}
});
});
test.describe('Wide Screen Layout (1920px)', () => {
test.beforeEach(async ({ page }) => {
await page.setViewportSize({ width: 1920, height: 1080 });
loginPage = new LoginPage(page);
dashboardPage = new DashboardPage(page);
await setupMockAuth(page);
});
test('should handle wide screen layouts appropriately', async ({ page }) => {
await loginPage.navigate();
await loginPage.login(testCredentials.valid.username, testCredentials.valid.password);
await page.waitForURL('/dashboard');
await page.screenshot({ path: 'widescreen-dashboard.png', fullPage: true });
// Content should not stretch too wide
const mainContent = page.locator('.dashboard-content');
if (await mainContent.isVisible()) {
const contentBox = await mainContent.boundingBox();
// Content should have reasonable max-width
expect(contentBox.width).toBeLessThan(1600);
}
});
});
test.describe('Orientation Changes', () => {
test('should handle portrait to landscape orientation', async ({ page }) => {
// Start in mobile portrait
await page.setViewportSize({ width: 375, height: 667 });
loginPage = new LoginPage(page);
await setupMockAuth(page);
await loginPage.navigate();
await page.screenshot({ path: 'mobile-portrait.png' });
// Rotate to landscape
await page.setViewportSize({ width: 667, height: 375 });
await page.waitForTimeout(500); // Allow for reflow
await page.screenshot({ path: 'mobile-landscape.png' });
// Login form should still be usable
await expect(page.locator(loginPage.loginCard)).toBeVisible();
await expect(page.locator(loginPage.usernameInput)).toBeVisible();
});
});
test.describe('Touch Interactions', () => {
test('should handle touch interactions on mobile', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 });
loginPage = new LoginPage(page);
await setupMockAuth(page);
await loginPage.navigate();
// Test touch interactions
await page.tap(loginPage.usernameInput);
await page.fill(loginPage.usernameInput, 'testuser');
await page.tap(loginPage.passwordInput);
await page.fill(loginPage.passwordInput, 'testpass');
// Login button should be tappable
const loginButton = page.locator(loginPage.loginButton);
await expect(loginButton).toBeEnabled();
// Button should have appropriate touch target size (minimum 44px)
const buttonBox = await loginButton.boundingBox();
expect(buttonBox.height).toBeGreaterThanOrEqual(44);
});
});
});