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:
317
reports-app/frontend/tests/utils/console-monitor.js
Normal file
317
reports-app/frontend/tests/utils/console-monitor.js
Normal file
@@ -0,0 +1,317 @@
|
||||
/**
|
||||
* Console Error Monitoring Infrastructure for ROA2WEB Testing
|
||||
*
|
||||
* Provides comprehensive console error tracking, classification, and performance monitoring
|
||||
* for Playwright tests with real Oracle data integration.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Error classification system for console messages
|
||||
*/
|
||||
export const ErrorClassifier = {
|
||||
CRITICAL: [
|
||||
'Authentication failed',
|
||||
'Database connection',
|
||||
'Uncaught TypeError',
|
||||
'Uncaught ReferenceError',
|
||||
'Oracle connection error',
|
||||
'SSH tunnel failed',
|
||||
'Failed to authenticate',
|
||||
'Cannot read property',
|
||||
'Cannot access before initialization'
|
||||
],
|
||||
WARNING: [
|
||||
'404 Not Found',
|
||||
'Failed to fetch',
|
||||
'Network request failed',
|
||||
'Component warning',
|
||||
'Vue warn',
|
||||
'Resource loading error',
|
||||
'Timeout exceeded',
|
||||
'Connection refused'
|
||||
],
|
||||
INFO: [
|
||||
'Development build',
|
||||
'Vue devtools',
|
||||
'[HMR]',
|
||||
'Hot reload',
|
||||
'DevTools',
|
||||
'webpack',
|
||||
'vite'
|
||||
],
|
||||
|
||||
/**
|
||||
* Classify a console message based on its content
|
||||
* @param {Object} message - Console message object
|
||||
* @returns {string} Classification level
|
||||
*/
|
||||
classify(message) {
|
||||
const text = message.text || message.error || '';
|
||||
if (this.CRITICAL.some(pattern => text.includes(pattern))) return 'CRITICAL';
|
||||
if (this.WARNING.some(pattern => text.includes(pattern))) return 'WARNING';
|
||||
if (this.INFO.some(pattern => text.includes(pattern))) return 'INFO';
|
||||
return 'UNKNOWN';
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if message should be ignored in tests
|
||||
* @param {Object} message - Console message object
|
||||
* @returns {boolean} True if message should be ignored
|
||||
*/
|
||||
shouldIgnore(message) {
|
||||
const ignoredPatterns = [
|
||||
'DevTools listening',
|
||||
'Debugging information',
|
||||
'Chrome extension',
|
||||
'webpack-dev-server',
|
||||
'Live reload enabled'
|
||||
];
|
||||
|
||||
const text = message.text || message.error || '';
|
||||
return ignoredPatterns.some(pattern => text.includes(pattern));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Performance monitoring utilities
|
||||
*/
|
||||
export const PerformanceMonitor = {
|
||||
/**
|
||||
* Measure page load performance metrics
|
||||
* @param {Page} page - Playwright page object
|
||||
* @returns {Object} Performance metrics
|
||||
*/
|
||||
async measurePageLoad(page) {
|
||||
return await page.evaluate(() => {
|
||||
const timing = performance.timing;
|
||||
const navigation = performance.getEntriesByType('navigation')[0];
|
||||
|
||||
return {
|
||||
domContentLoaded: timing.domContentLoadedEventEnd - timing.navigationStart,
|
||||
loadComplete: timing.loadEventEnd - timing.navigationStart,
|
||||
firstPaint: performance.getEntriesByType('paint').find(p => p.name === 'first-paint')?.startTime || 0,
|
||||
firstContentfulPaint: performance.getEntriesByType('paint').find(p => p.name === 'first-contentful-paint')?.startTime || 0,
|
||||
timeToInteractive: navigation?.domInteractive - navigation?.fetchStart || 0
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Measure API response time
|
||||
* @param {Page} page - Playwright page object
|
||||
* @param {string} apiPattern - API endpoint pattern to monitor
|
||||
* @returns {Promise<number>} Response time in milliseconds
|
||||
*/
|
||||
async measureApiResponse(page, apiPattern) {
|
||||
const startTime = Date.now();
|
||||
await page.waitForResponse(response => response.url().includes(apiPattern), { timeout: 10000 });
|
||||
return Date.now() - startTime;
|
||||
},
|
||||
|
||||
/**
|
||||
* Monitor network performance during test execution
|
||||
* @param {Page} page - Playwright page object
|
||||
* @returns {Object} Network performance data
|
||||
*/
|
||||
async getNetworkMetrics(page) {
|
||||
const resourceTiming = await page.evaluate(() => {
|
||||
return performance.getEntriesByType('resource').map(entry => ({
|
||||
name: entry.name,
|
||||
duration: entry.duration,
|
||||
transferSize: entry.transferSize,
|
||||
type: entry.initiatorType
|
||||
}));
|
||||
});
|
||||
|
||||
const slowResources = resourceTiming
|
||||
.filter(resource => resource.duration > 1000)
|
||||
.sort((a, b) => b.duration - a.duration);
|
||||
|
||||
return {
|
||||
totalResources: resourceTiming.length,
|
||||
slowResources: slowResources.slice(0, 5),
|
||||
averageResponseTime: resourceTiming.reduce((sum, r) => sum + r.duration, 0) / resourceTiming.length
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Console monitoring setup function for test beforeEach hooks
|
||||
* @param {Page} page - Playwright page object
|
||||
* @returns {Object} Monitoring data collectors
|
||||
*/
|
||||
export function setupConsoleCapture(page) {
|
||||
const consoleMessages = [];
|
||||
const networkErrors = [];
|
||||
const performanceMetrics = {
|
||||
startTime: Date.now(),
|
||||
apiCalls: []
|
||||
};
|
||||
|
||||
// Capture console messages
|
||||
page.on('console', msg => {
|
||||
const message = {
|
||||
type: msg.type(),
|
||||
text: msg.text(),
|
||||
location: msg.location(),
|
||||
timestamp: new Date().toISOString(),
|
||||
args: msg.args()
|
||||
};
|
||||
|
||||
if (!ErrorClassifier.shouldIgnore(message)) {
|
||||
consoleMessages.push(message);
|
||||
}
|
||||
});
|
||||
|
||||
// Capture JavaScript errors
|
||||
page.on('pageerror', error => {
|
||||
const errorMessage = {
|
||||
type: 'pageerror',
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
consoleMessages.push(errorMessage);
|
||||
});
|
||||
|
||||
// Capture network failures
|
||||
page.on('requestfailed', request => {
|
||||
const networkError = {
|
||||
url: request.url(),
|
||||
method: request.method(),
|
||||
failure: request.failure(),
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
networkErrors.push(networkError);
|
||||
});
|
||||
|
||||
// Monitor API responses
|
||||
page.on('response', response => {
|
||||
if (response.url().includes('/api/')) {
|
||||
performanceMetrics.apiCalls.push({
|
||||
url: response.url(),
|
||||
status: response.status(),
|
||||
timing: Date.now() - performanceMetrics.startTime
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Store collectors on page object for test access
|
||||
page.consoleMessages = consoleMessages;
|
||||
page.networkErrors = networkErrors;
|
||||
page.performanceMetrics = performanceMetrics;
|
||||
|
||||
return { consoleMessages, networkErrors, performanceMetrics };
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate comprehensive error report
|
||||
* @param {Page} page - Playwright page object
|
||||
* @param {string} testName - Name of the test
|
||||
* @returns {Object} Error report
|
||||
*/
|
||||
export function generateErrorReport(page, testName) {
|
||||
const consoleMessages = page.consoleMessages || [];
|
||||
const networkErrors = page.networkErrors || [];
|
||||
|
||||
// Classify console messages
|
||||
const classified = {
|
||||
critical: consoleMessages.filter(msg => ErrorClassifier.classify(msg) === 'CRITICAL'),
|
||||
warning: consoleMessages.filter(msg => ErrorClassifier.classify(msg) === 'WARNING'),
|
||||
info: consoleMessages.filter(msg => ErrorClassifier.classify(msg) === 'INFO'),
|
||||
unknown: consoleMessages.filter(msg => ErrorClassifier.classify(msg) === 'UNKNOWN')
|
||||
};
|
||||
|
||||
// Find error patterns
|
||||
const errorPatterns = {};
|
||||
consoleMessages.forEach(msg => {
|
||||
if (msg.type === 'error') {
|
||||
const pattern = findErrorPattern(msg.text);
|
||||
errorPatterns[pattern] = (errorPatterns[pattern] || 0) + 1;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
testName,
|
||||
timestamp: new Date().toISOString(),
|
||||
summary: {
|
||||
totalConsoleMessages: consoleMessages.length,
|
||||
totalNetworkErrors: networkErrors.length,
|
||||
classifications: {
|
||||
critical: classified.critical.length,
|
||||
warning: classified.warning.length,
|
||||
info: classified.info.length,
|
||||
unknown: classified.unknown.length
|
||||
}
|
||||
},
|
||||
details: {
|
||||
criticalErrors: classified.critical,
|
||||
warnings: classified.warning,
|
||||
networkErrors,
|
||||
errorPatterns
|
||||
},
|
||||
performance: page.performanceMetrics
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Find common error patterns in console messages
|
||||
* @param {string} errorText - Error message text
|
||||
* @returns {string} Error pattern category
|
||||
*/
|
||||
function findErrorPattern(errorText) {
|
||||
const patterns = [
|
||||
{ pattern: /Failed to fetch/, category: 'Network Error' },
|
||||
{ pattern: /404.*not found/i, category: '404 Error' },
|
||||
{ pattern: /Uncaught TypeError/, category: 'JavaScript TypeError' },
|
||||
{ pattern: /Vue warn/, category: 'Vue Warning' },
|
||||
{ pattern: /Component.*not found/, category: 'Component Error' },
|
||||
{ pattern: /Oracle.*connection/, category: 'Database Error' },
|
||||
{ pattern: /Authentication.*failed/, category: 'Auth Error' }
|
||||
];
|
||||
|
||||
const match = patterns.find(p => p.pattern.test(errorText));
|
||||
return match ? match.category : 'Unknown Error';
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert no critical console errors in test
|
||||
* @param {Page} page - Playwright page object
|
||||
* @param {Object} expect - Playwright expect object
|
||||
*/
|
||||
export function assertNoCriticalErrors(page, expect) {
|
||||
const consoleMessages = page.consoleMessages || [];
|
||||
const criticalErrors = consoleMessages.filter(msg =>
|
||||
ErrorClassifier.classify(msg) === 'CRITICAL'
|
||||
);
|
||||
|
||||
if (criticalErrors.length > 0) {
|
||||
const errorDetails = criticalErrors.map(err =>
|
||||
`${err.type}: ${err.text || err.error} at ${err.location?.url || 'unknown'}:${err.location?.lineNumber || 0}`
|
||||
).join('\n');
|
||||
|
||||
expect(criticalErrors, `Critical console errors found:\n${errorDetails}`).toHaveLength(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performance baselines for ROA2WEB application
|
||||
*/
|
||||
export const 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
|
||||
};
|
||||
|
||||
/**
|
||||
* Assert performance meets baselines
|
||||
* @param {number} actualTime - Actual measured time
|
||||
* @param {number} baseline - Performance baseline
|
||||
* @param {string} operation - Operation name
|
||||
* @param {Object} expect - Playwright expect object
|
||||
*/
|
||||
export function assertPerformanceBaseline(actualTime, baseline, operation, expect) {
|
||||
expect(actualTime, `${operation} took ${actualTime}ms, expected < ${baseline}ms`).toBeLessThan(baseline);
|
||||
}
|
||||
194
reports-app/frontend/tests/utils/helpers.js
Normal file
194
reports-app/frontend/tests/utils/helpers.js
Normal file
@@ -0,0 +1,194 @@
|
||||
/**
|
||||
* Test utility functions for ROA2WEB frontend testing
|
||||
*/
|
||||
|
||||
/**
|
||||
* Wait for element to be visible with custom timeout
|
||||
* @param {Page} page - Playwright page object
|
||||
* @param {string} selector - CSS selector
|
||||
* @param {number} timeout - Timeout in milliseconds
|
||||
*/
|
||||
export async function waitForVisible(page, selector, timeout = 10000) {
|
||||
await page.waitForSelector(selector, {
|
||||
state: 'visible',
|
||||
timeout
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for all API calls to complete
|
||||
* @param {Page} page - Playwright page object
|
||||
*/
|
||||
export async function waitForApiCalls(page) {
|
||||
await page.waitForLoadState('networkidle');
|
||||
}
|
||||
|
||||
/**
|
||||
* Take screenshot with timestamp
|
||||
* @param {Page} page - Playwright page object
|
||||
* @param {string} name - Screenshot name
|
||||
*/
|
||||
export async function takeTimestampedScreenshot(page, name) {
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
await page.screenshot({
|
||||
path: `test-results/${name}-${timestamp}.png`,
|
||||
fullPage: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if element exists in DOM (without waiting)
|
||||
* @param {Page} page - Playwright page object
|
||||
* @param {string} selector - CSS selector
|
||||
* @returns {boolean} True if element exists
|
||||
*/
|
||||
export async function elementExists(page, selector) {
|
||||
const element = await page.$(selector);
|
||||
return element !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get element text content safely
|
||||
* @param {Page} page - Playwright page object
|
||||
* @param {string} selector - CSS selector
|
||||
* @returns {string|null} Text content or null if element not found
|
||||
*/
|
||||
export async function getTextContent(page, selector) {
|
||||
try {
|
||||
const element = await page.locator(selector);
|
||||
if (await element.isVisible()) {
|
||||
return await element.textContent();
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`Element not found: ${selector}`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for toast message and return its content
|
||||
* @param {Page} page - Playwright page object
|
||||
* @param {string} type - Toast type: 'error', 'success', 'info', 'warn'
|
||||
* @param {number} timeout - Timeout in milliseconds
|
||||
* @returns {string|null} Toast message content
|
||||
*/
|
||||
export async function waitForToast(page, type = 'error', timeout = 5000) {
|
||||
try {
|
||||
const toastSelector = `.p-toast-message-${type}`;
|
||||
await page.waitForSelector(toastSelector, { timeout });
|
||||
return await page.locator(toastSelector).textContent();
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill form fields from object
|
||||
* @param {Page} page - Playwright page object
|
||||
* @param {Object} fields - Object with selector: value pairs
|
||||
*/
|
||||
export async function fillForm(page, fields) {
|
||||
for (const [selector, value] of Object.entries(fields)) {
|
||||
await page.fill(selector, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if current URL matches pattern
|
||||
* @param {Page} page - Playwright page object
|
||||
* @param {string} pattern - URL pattern to match
|
||||
* @returns {boolean} True if URL matches
|
||||
*/
|
||||
export function urlMatches(page, pattern) {
|
||||
return page.url().includes(pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock successful authentication for tests
|
||||
* @param {Page} page - Playwright page object
|
||||
*/
|
||||
export async function mockSuccessfulAuth(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'
|
||||
}
|
||||
}),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock companies API
|
||||
* @param {Page} page - Playwright page object
|
||||
* @param {Array} companies - Array of company objects
|
||||
*/
|
||||
export async function mockCompanies(page, companies = []) {
|
||||
const defaultCompanies = [
|
||||
{ code: 'COMP1', name: 'Test Company 1' },
|
||||
{ code: 'COMP2', name: 'Test Company 2' }
|
||||
];
|
||||
|
||||
await page.route('**/api/companies', async route => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify(companies.length > 0 ? companies : defaultCompanies),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock API error response
|
||||
* @param {Page} page - Playwright page object
|
||||
* @param {string} endpoint - API endpoint pattern
|
||||
* @param {number} status - HTTP status code
|
||||
* @param {string} message - Error message
|
||||
*/
|
||||
export async function mockApiError(page, endpoint, status = 500, message = 'Internal server error') {
|
||||
await page.route(endpoint, async route => {
|
||||
await route.fulfill({
|
||||
status,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
detail: message
|
||||
}),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all localStorage data
|
||||
* @param {Page} page - Playwright page object
|
||||
*/
|
||||
export async function clearStorage(page) {
|
||||
await page.evaluate(() => {
|
||||
localStorage.clear();
|
||||
sessionStorage.clear();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set viewport to common device size
|
||||
* @param {Page} page - Playwright page object
|
||||
* @param {string} device - Device name: 'mobile', 'tablet', 'desktop'
|
||||
*/
|
||||
export async function setDeviceViewport(page, device) {
|
||||
const viewports = {
|
||||
mobile: { width: 375, height: 667 },
|
||||
tablet: { width: 768, height: 1024 },
|
||||
desktop: { width: 1024, height: 768 },
|
||||
wide: { width: 1920, height: 1080 }
|
||||
};
|
||||
|
||||
if (viewports[device]) {
|
||||
await page.setViewportSize(viewports[device]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user