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
99 lines
2.7 KiB
JavaScript
99 lines
2.7 KiB
JavaScript
import { BasePage } from './BasePage.js';
|
|
|
|
export class LoginPage extends BasePage {
|
|
constructor(page) {
|
|
super(page);
|
|
|
|
// Selectors
|
|
this.usernameInput = '#username';
|
|
this.passwordInput = '#password input';
|
|
this.loginButton = 'button[type="submit"]';
|
|
this.errorMessage = '.error-message';
|
|
this.loadingSpinner = '.p-button-loading';
|
|
this.loginTitle = '.login-title';
|
|
this.loginCard = '.login-card';
|
|
|
|
// Form validation selectors
|
|
this.usernameError = '.field:has(#username) .p-error';
|
|
this.passwordError = '.field:has(#password) .p-error';
|
|
this.invalidField = '.p-invalid';
|
|
}
|
|
|
|
async navigate() {
|
|
await this.page.goto('/');
|
|
await this.page.waitForSelector(this.loginCard);
|
|
}
|
|
|
|
async fillCredentials(username, password) {
|
|
await this.page.fill(this.usernameInput, username);
|
|
await this.page.fill(this.passwordInput, password);
|
|
}
|
|
|
|
async clickLogin() {
|
|
await this.page.click(this.loginButton);
|
|
}
|
|
|
|
async login(username, password) {
|
|
await this.fillCredentials(username, password);
|
|
await this.clickLogin();
|
|
}
|
|
|
|
async waitForLoginResult() {
|
|
// Wait for either redirect to dashboard or error message
|
|
try {
|
|
await Promise.race([
|
|
this.page.waitForURL('/dashboard', { timeout: 5000 }),
|
|
this.page.waitForSelector(this.errorMessage, { timeout: 5000 })
|
|
]);
|
|
} catch (error) {
|
|
// Continue - we'll check the state separately
|
|
}
|
|
}
|
|
|
|
async isOnLoginPage() {
|
|
return await this.page.locator(this.loginTitle).isVisible();
|
|
}
|
|
|
|
async isLoginButtonDisabled() {
|
|
return await this.page.locator(this.loginButton).isDisabled();
|
|
}
|
|
|
|
async isLoading() {
|
|
return await this.page.locator(this.loadingSpinner).isVisible();
|
|
}
|
|
|
|
async getErrorMessage() {
|
|
const errorElement = this.page.locator(this.errorMessage);
|
|
if (await errorElement.isVisible()) {
|
|
return await errorElement.textContent();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
async getFieldError(field) {
|
|
const selector = field === 'username' ? this.usernameError : this.passwordError;
|
|
const errorElement = this.page.locator(selector);
|
|
if (await errorElement.isVisible()) {
|
|
return await errorElement.textContent();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
async hasInvalidField() {
|
|
return await this.page.locator(this.invalidField).count() > 0;
|
|
}
|
|
|
|
async clearForm() {
|
|
await this.page.fill(this.usernameInput, '');
|
|
await this.page.fill(this.passwordInput, '');
|
|
}
|
|
|
|
async validateFormFields() {
|
|
// Trigger validation by clicking outside fields
|
|
await this.page.click(this.loginCard);
|
|
}
|
|
|
|
async getPageTitle() {
|
|
return await this.page.locator(this.loginTitle).textContent();
|
|
}
|
|
} |