Files
roa2web-service-auto/reports-app/frontend/tests/page-objects/InvoicesPage.js
Marius Mutu 6b13ffa183 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
2025-10-25 14:55:08 +03:00

195 lines
5.9 KiB
JavaScript

import { BasePage } from './BasePage.js';
export class InvoicesPage extends BasePage {
constructor(page) {
super(page);
// Page selectors
this.pageTitle = '.page-title';
this.pageSubtitle = '.page-subtitle';
// Company selection
this.companySelectionCard = '.company-selection-card';
this.companyDropdown = '.company-selection .p-dropdown';
this.companyDropdownTrigger = '.company-selection .p-dropdown-trigger';
this.companyOptions = '.p-dropdown-item';
// Search and filters
this.searchInput = '.search-input input';
this.statusFilter = '.status-filter .p-dropdown';
this.statusFilterTrigger = '.status-filter .p-dropdown-trigger';
this.refreshButton = '.refresh-button';
this.exportButton = '.export-button';
// Table selectors
this.invoicesTable = '.invoices-table';
this.tableRows = '.invoices-table tbody tr';
this.tableHeaders = '.invoices-table thead th';
this.loadingSpinner = '.p-datatable-loading';
// Pagination
this.pagination = '.p-paginator';
this.nextPageButton = '.p-paginator-next';
this.prevPageButton = '.p-paginator-prev';
this.currentPageSpan = '.p-paginator-current';
// Invoice details
this.invoiceDetailsModal = '.invoice-details-modal';
this.invoiceDetailsPanel = '.invoice-details-panel';
// Specific table columns (adjust based on actual implementation)
this.numberColumn = 'td:nth-child(1)';
this.dateColumn = 'td:nth-child(2)';
this.clientColumn = 'td:nth-child(3)';
this.amountColumn = 'td:nth-child(4)';
this.statusColumn = 'td:nth-child(5)';
}
async navigate() {
await this.page.goto('/invoices');
await this.page.waitForSelector(this.pageTitle);
}
async isOnInvoicesPage() {
return await this.page.locator(this.pageTitle).isVisible();
}
async getPageTitle() {
return await this.page.locator(this.pageTitle).textContent();
}
async isCompanySelectionVisible() {
return await this.page.locator(this.companySelectionCard).isVisible();
}
async isInvoicesTableVisible() {
return await this.page.locator(this.invoicesTable).isVisible();
}
async selectCompany(companyName) {
await this.page.click(this.companyDropdownTrigger);
await this.page.waitForSelector(this.companyOptions);
await this.page.click(`${this.companyOptions}:has-text("${companyName}")`);
await this.waitForLoadingToFinish();
}
async searchInvoices(searchTerm) {
await this.page.fill(this.searchInput, searchTerm);
await this.page.press(this.searchInput, 'Enter');
}
async filterByStatus(status) {
await this.page.click(this.statusFilterTrigger);
await this.page.waitForSelector(this.companyOptions);
// Map status to Romanian text (adjust based on actual implementation)
const statusMap = {
'paid': 'Plătit',
'unpaid': 'Neplătit',
'overdue': 'Întârziat'
};
const statusText = statusMap[status] || status;
await this.page.click(`${this.companyOptions}:has-text("${statusText}")`);
}
async sortByColumn(columnName) {
// Map column names to actual header text
const columnMap = {
'number': 'Număr',
'date': 'Data',
'client': 'Client',
'amount': 'Sumă',
'status': 'Status'
};
const headerText = columnMap[columnName] || columnName;
await this.page.click(`${this.tableHeaders}:has-text("${headerText}")`);
}
async getVisibleInvoicesCount() {
return await this.page.locator(this.tableRows).count();
}
async getFirstInvoiceData() {
const firstRow = this.page.locator(this.tableRows).first();
return {
number: await firstRow.locator(this.numberColumn).textContent(),
date: await firstRow.locator(this.dateColumn).textContent(),
client: await firstRow.locator(this.clientColumn).textContent(),
amount: await firstRow.locator(this.amountColumn).textContent(),
status: await firstRow.locator(this.statusColumn).textContent()
};
}
async clickFirstInvoiceRow() {
await this.page.locator(this.tableRows).first().click();
}
async isInvoiceDetailsVisible() {
const modalVisible = await this.page.locator(this.invoiceDetailsModal).isVisible();
const panelVisible = await this.page.locator(this.invoiceDetailsPanel).isVisible();
return modalVisible || panelVisible;
}
async clickExportButton() {
await this.page.click(this.exportButton);
}
async clickRefreshButton() {
await this.page.click(this.refreshButton);
}
async isPaginationVisible() {
return await this.page.locator(this.pagination).isVisible();
}
async goToNextPage() {
await this.page.click(this.nextPageButton);
}
async goToPrevPage() {
await this.page.click(this.prevPageButton);
}
async getCurrentPage() {
const pageText = await this.page.locator(this.currentPageSpan).textContent();
// Extract page number from text like "Page 2 of 5"
const match = pageText.match(/(\d+)/);
return match ? parseInt(match[1]) : 1;
}
async waitForPageLoad() {
await Promise.race([
this.page.waitForSelector(this.companySelectionCard, { timeout: 10000 }),
this.page.waitForSelector(this.invoicesTable, { timeout: 10000 })
]);
}
async waitForTableLoad() {
await this.page.waitForSelector(this.invoicesTable, { timeout: 10000 });
await this.waitForLoadingToFinish();
}
async getInvoiceByNumber(invoiceNumber) {
const rows = this.page.locator(this.tableRows);
const rowCount = await rows.count();
for (let i = 0; i < rowCount; i++) {
const row = rows.nth(i);
const number = await row.locator(this.numberColumn).textContent();
if (number.trim() === invoiceNumber) {
return row;
}
}
return null;
}
async clickInvoiceByNumber(invoiceNumber) {
const row = await this.getInvoiceByNumber(invoiceNumber);
if (row) {
await row.click();
}
}
}