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:
37
reports-app/frontend/tests/page-objects/BasePage.js
Normal file
37
reports-app/frontend/tests/page-objects/BasePage.js
Normal file
@@ -0,0 +1,37 @@
|
||||
export class BasePage {
|
||||
constructor(page) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
async waitForApiResponse(url, status = 200) {
|
||||
return await this.page.waitForResponse(response =>
|
||||
response.url().includes(url) && response.status() === status
|
||||
);
|
||||
}
|
||||
|
||||
async waitForLoadingToFinish() {
|
||||
// Wait for any loading spinners to disappear
|
||||
await this.page.waitForFunction(() => {
|
||||
const loadingElements = document.querySelectorAll('[data-testid="loading"], .p-progress-spinner');
|
||||
return loadingElements.length === 0;
|
||||
}, { timeout: 10000 });
|
||||
}
|
||||
|
||||
async checkErrorMessage(expectedMessage) {
|
||||
const errorElement = this.page.locator('.p-message-error, [data-testid="error"]');
|
||||
await errorElement.waitFor({ state: 'visible', timeout: 5000 });
|
||||
const actualMessage = await errorElement.textContent();
|
||||
return actualMessage.includes(expectedMessage);
|
||||
}
|
||||
|
||||
async checkSuccessMessage(expectedMessage) {
|
||||
const successElement = this.page.locator('.p-message-success, [data-testid="success"]');
|
||||
await successElement.waitFor({ state: 'visible', timeout: 5000 });
|
||||
const actualMessage = await successElement.textContent();
|
||||
return actualMessage.includes(expectedMessage);
|
||||
}
|
||||
|
||||
async waitForNavigation() {
|
||||
await this.page.waitForLoadState('networkidle');
|
||||
}
|
||||
}
|
||||
129
reports-app/frontend/tests/page-objects/DashboardPage.js
Normal file
129
reports-app/frontend/tests/page-objects/DashboardPage.js
Normal file
@@ -0,0 +1,129 @@
|
||||
import { BasePage } from './BasePage.js';
|
||||
|
||||
export class DashboardPage extends BasePage {
|
||||
constructor(page) {
|
||||
super(page);
|
||||
|
||||
// Header selectors
|
||||
this.pageTitle = '.page-title';
|
||||
this.pageSubtitle = '.page-subtitle';
|
||||
this.userWelcome = '.page-subtitle';
|
||||
|
||||
// Company selection selectors
|
||||
this.companySelectionCard = '.company-selection-card';
|
||||
this.companyDropdown = '.company-selection .p-dropdown';
|
||||
this.companyDropdownTrigger = '.company-selection .p-dropdown-trigger';
|
||||
this.companyOptions = '.p-dropdown-item';
|
||||
|
||||
// Stats cards selectors
|
||||
this.statsGrid = '.stats-grid';
|
||||
this.invoicesStatCard = '.stat-card.stat-invoices';
|
||||
this.paymentsStatCard = '.stat-card.stat-payments';
|
||||
this.companyStatCard = '.stat-card.stat-company';
|
||||
|
||||
// Stat values
|
||||
this.invoicesTotal = '.stat-invoices .stat-value';
|
||||
this.paymentsTotal = '.stat-payments .stat-value';
|
||||
this.companyName = '.stat-company .stat-value';
|
||||
|
||||
// Quick actions
|
||||
this.quickActionsCard = '.quick-actions-card';
|
||||
this.invoicesActionButton = 'button:has-text("Facturi")';
|
||||
this.paymentsActionButton = 'button:has-text("Încasări")';
|
||||
|
||||
// Navigation
|
||||
this.dashboardContent = '.dashboard-content';
|
||||
}
|
||||
|
||||
async navigate() {
|
||||
await this.page.goto('/dashboard');
|
||||
await this.page.waitForSelector(this.pageTitle);
|
||||
}
|
||||
|
||||
async isOnDashboardPage() {
|
||||
return await this.page.locator(this.pageTitle).isVisible();
|
||||
}
|
||||
|
||||
async getPageTitle() {
|
||||
return await this.page.locator(this.pageTitle).textContent();
|
||||
}
|
||||
|
||||
async getWelcomeMessage() {
|
||||
return await this.page.locator(this.userWelcome).textContent();
|
||||
}
|
||||
|
||||
async isCompanySelectionVisible() {
|
||||
return await this.page.locator(this.companySelectionCard).isVisible();
|
||||
}
|
||||
|
||||
async isDashboardContentVisible() {
|
||||
return await this.page.locator(this.dashboardContent).isVisible();
|
||||
}
|
||||
|
||||
async selectCompany(companyName) {
|
||||
// Click dropdown to open options
|
||||
await this.page.click(this.companyDropdownTrigger);
|
||||
|
||||
// Wait for options to appear and select the company
|
||||
await this.page.waitForSelector(this.companyOptions);
|
||||
await this.page.click(`${this.companyOptions}:has-text("${companyName}")`);
|
||||
|
||||
// Wait for selection to be processed
|
||||
await this.waitForLoadingToFinish();
|
||||
}
|
||||
|
||||
async getSelectedCompanyName() {
|
||||
if (await this.page.locator(this.companyName).isVisible()) {
|
||||
return await this.page.locator(this.companyName).textContent();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async getInvoicesCount() {
|
||||
if (await this.page.locator(this.invoicesTotal).isVisible()) {
|
||||
return await this.page.locator(this.invoicesTotal).textContent();
|
||||
}
|
||||
return '0';
|
||||
}
|
||||
|
||||
async getPaymentsCount() {
|
||||
if (await this.page.locator(this.paymentsTotal).isVisible()) {
|
||||
return await this.page.locator(this.paymentsTotal).textContent();
|
||||
}
|
||||
return '0';
|
||||
}
|
||||
|
||||
async clickInvoicesAction() {
|
||||
await this.page.click(this.invoicesActionButton);
|
||||
await this.waitForNavigation();
|
||||
}
|
||||
|
||||
async clickPaymentsAction() {
|
||||
await this.page.click(this.paymentsActionButton);
|
||||
await this.waitForNavigation();
|
||||
}
|
||||
|
||||
async areStatsCardsVisible() {
|
||||
const invoicesVisible = await this.page.locator(this.invoicesStatCard).isVisible();
|
||||
const paymentsVisible = await this.page.locator(this.paymentsStatCard).isVisible();
|
||||
const companyVisible = await this.page.locator(this.companyStatCard).isVisible();
|
||||
|
||||
return invoicesVisible && paymentsVisible && companyVisible;
|
||||
}
|
||||
|
||||
async getStatsData() {
|
||||
return {
|
||||
invoices: await this.getInvoicesCount(),
|
||||
payments: await this.getPaymentsCount(),
|
||||
company: await this.getSelectedCompanyName()
|
||||
};
|
||||
}
|
||||
|
||||
async waitForDashboardLoad() {
|
||||
// Wait for either company selection or dashboard content to appear
|
||||
await Promise.race([
|
||||
this.page.waitForSelector(this.companySelectionCard, { timeout: 10000 }),
|
||||
this.page.waitForSelector(this.dashboardContent, { timeout: 10000 })
|
||||
]);
|
||||
}
|
||||
}
|
||||
195
reports-app/frontend/tests/page-objects/InvoicesPage.js
Normal file
195
reports-app/frontend/tests/page-objects/InvoicesPage.js
Normal file
@@ -0,0 +1,195 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
99
reports-app/frontend/tests/page-objects/LoginPage.js
Normal file
99
reports-app/frontend/tests/page-objects/LoginPage.js
Normal file
@@ -0,0 +1,99 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
271
reports-app/frontend/tests/page-objects/PaymentsPage.js
Normal file
271
reports-app/frontend/tests/page-objects/PaymentsPage.js
Normal file
@@ -0,0 +1,271 @@
|
||||
import { BasePage } from './BasePage.js';
|
||||
|
||||
export class PaymentsPage 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.methodFilter = '.method-filter .p-dropdown';
|
||||
this.methodFilterTrigger = '.method-filter .p-dropdown-trigger';
|
||||
this.dateRangeFilter = '.date-range-filter .p-dropdown';
|
||||
this.dateRangeFilterTrigger = '.date-range-filter .p-dropdown-trigger';
|
||||
this.refreshButton = '.refresh-button';
|
||||
this.exportButton = '.export-button';
|
||||
|
||||
// View toggles
|
||||
this.tableViewButton = '.table-view-button';
|
||||
this.summaryViewButton = '.summary-view-button';
|
||||
|
||||
// Table selectors
|
||||
this.paymentsTable = '.payments-table';
|
||||
this.tableRows = '.payments-table tbody tr';
|
||||
this.tableHeaders = '.payments-table thead th';
|
||||
this.loadingSpinner = '.p-datatable-loading';
|
||||
|
||||
// Summary view
|
||||
this.summaryView = '.payments-summary-view';
|
||||
this.methodSummaryCards = '.method-summary-card';
|
||||
|
||||
// Totals card
|
||||
this.totalsCard = '.payments-totals-card';
|
||||
this.totalAmount = '.total-amount .amount-value';
|
||||
this.totalCount = '.total-count .count-value';
|
||||
|
||||
// Pagination
|
||||
this.pagination = '.p-paginator';
|
||||
this.nextPageButton = '.p-paginator-next';
|
||||
this.prevPageButton = '.p-paginator-prev';
|
||||
this.currentPageSpan = '.p-paginator-current';
|
||||
|
||||
// Payment details
|
||||
this.paymentDetailsModal = '.payment-details-modal';
|
||||
this.paymentDetailsPanel = '.payment-details-panel';
|
||||
|
||||
// Specific table columns (adjust based on actual implementation)
|
||||
this.referenceColumn = 'td:nth-child(1)';
|
||||
this.dateColumn = 'td:nth-child(2)';
|
||||
this.clientColumn = 'td:nth-child(3)';
|
||||
this.amountColumn = 'td:nth-child(4)';
|
||||
this.methodColumn = 'td:nth-child(5)';
|
||||
}
|
||||
|
||||
async navigate() {
|
||||
await this.page.goto('/payments');
|
||||
await this.page.waitForSelector(this.pageTitle);
|
||||
}
|
||||
|
||||
async isOnPaymentsPage() {
|
||||
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 isPaymentsTableVisible() {
|
||||
return await this.page.locator(this.paymentsTable).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 searchPayments(searchTerm) {
|
||||
await this.page.fill(this.searchInput, searchTerm);
|
||||
await this.page.press(this.searchInput, 'Enter');
|
||||
}
|
||||
|
||||
async filterByMethod(method) {
|
||||
await this.page.click(this.methodFilterTrigger);
|
||||
await this.page.waitForSelector(this.companyOptions);
|
||||
|
||||
// Map method to Romanian text (adjust based on actual implementation)
|
||||
const methodMap = {
|
||||
'bank_transfer': 'Transfer bancar',
|
||||
'cash': 'Numerar',
|
||||
'card': 'Card',
|
||||
'check': 'Cec'
|
||||
};
|
||||
|
||||
const methodText = methodMap[method] || method;
|
||||
await this.page.click(`${this.companyOptions}:has-text("${methodText}")`);
|
||||
}
|
||||
|
||||
async filterByDateRange(range) {
|
||||
await this.page.click(this.dateRangeFilterTrigger);
|
||||
await this.page.waitForSelector(this.companyOptions);
|
||||
|
||||
// Map range to Romanian text
|
||||
const rangeMap = {
|
||||
'thisMonth': 'Această lună',
|
||||
'lastMonth': 'Luna trecută',
|
||||
'thisYear': 'Acest an',
|
||||
'custom': 'Personalizat'
|
||||
};
|
||||
|
||||
const rangeText = rangeMap[range] || range;
|
||||
await this.page.click(`${this.companyOptions}:has-text("${rangeText}")`);
|
||||
}
|
||||
|
||||
async sortByColumn(columnName) {
|
||||
// Map column names to actual header text
|
||||
const columnMap = {
|
||||
'reference': 'Referință',
|
||||
'date': 'Data',
|
||||
'client': 'Client',
|
||||
'amount': 'Sumă',
|
||||
'method': 'Metodă'
|
||||
};
|
||||
|
||||
const headerText = columnMap[columnName] || columnName;
|
||||
await this.page.click(`${this.tableHeaders}:has-text("${headerText}")`);
|
||||
}
|
||||
|
||||
async getVisiblePaymentsCount() {
|
||||
return await this.page.locator(this.tableRows).count();
|
||||
}
|
||||
|
||||
async getFirstPaymentData() {
|
||||
const firstRow = this.page.locator(this.tableRows).first();
|
||||
|
||||
return {
|
||||
reference: await firstRow.locator(this.referenceColumn).textContent(),
|
||||
date: await firstRow.locator(this.dateColumn).textContent(),
|
||||
client: await firstRow.locator(this.clientColumn).textContent(),
|
||||
amount: await firstRow.locator(this.amountColumn).textContent(),
|
||||
method: await firstRow.locator(this.methodColumn).textContent()
|
||||
};
|
||||
}
|
||||
|
||||
async clickFirstPaymentRow() {
|
||||
await this.page.locator(this.tableRows).first().click();
|
||||
}
|
||||
|
||||
async isPaymentDetailsVisible() {
|
||||
const modalVisible = await this.page.locator(this.paymentDetailsModal).isVisible();
|
||||
const panelVisible = await this.page.locator(this.paymentDetailsPanel).isVisible();
|
||||
return modalVisible || panelVisible;
|
||||
}
|
||||
|
||||
async clickExportButton() {
|
||||
await this.page.click(this.exportButton);
|
||||
}
|
||||
|
||||
async clickRefreshButton() {
|
||||
await this.page.click(this.refreshButton);
|
||||
}
|
||||
|
||||
async isTotalsCardVisible() {
|
||||
return await this.page.locator(this.totalsCard).isVisible();
|
||||
}
|
||||
|
||||
async getTotalsData() {
|
||||
return {
|
||||
totalAmount: await this.page.locator(this.totalAmount).textContent(),
|
||||
totalCount: await this.page.locator(this.totalCount).textContent()
|
||||
};
|
||||
}
|
||||
|
||||
async isSummaryViewAvailable() {
|
||||
return await this.page.locator(this.summaryViewButton).isVisible();
|
||||
}
|
||||
|
||||
async switchToSummaryView() {
|
||||
await this.page.click(this.summaryViewButton);
|
||||
}
|
||||
|
||||
async switchToTableView() {
|
||||
await this.page.click(this.tableViewButton);
|
||||
}
|
||||
|
||||
async isSummaryViewVisible() {
|
||||
return await this.page.locator(this.summaryView).isVisible();
|
||||
}
|
||||
|
||||
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.paymentsTable, { timeout: 10000 })
|
||||
]);
|
||||
}
|
||||
|
||||
async waitForTableLoad() {
|
||||
await this.page.waitForSelector(this.paymentsTable, { timeout: 10000 });
|
||||
await this.waitForLoadingToFinish();
|
||||
}
|
||||
|
||||
async getPaymentByReference(reference) {
|
||||
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 ref = await row.locator(this.referenceColumn).textContent();
|
||||
if (ref.trim() === reference) {
|
||||
return row;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async clickPaymentByReference(reference) {
|
||||
const row = await this.getPaymentByReference(reference);
|
||||
if (row) {
|
||||
await row.click();
|
||||
}
|
||||
}
|
||||
|
||||
async getMethodSummaryData() {
|
||||
const summaryCards = this.page.locator(this.methodSummaryCards);
|
||||
const cardCount = await summaryCards.count();
|
||||
const data = [];
|
||||
|
||||
for (let i = 0; i < cardCount; i++) {
|
||||
const card = summaryCards.nth(i);
|
||||
const method = await card.locator('.method-name').textContent();
|
||||
const amount = await card.locator('.method-amount').textContent();
|
||||
const count = await card.locator('.method-count').textContent();
|
||||
|
||||
data.push({ method, amount, count });
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user