import { Page } from '@playwright/test'; import { BasePage } from './BasePage'; export class DashboardPage extends BasePage { private readonly createEventButton = 'a:has-text("Create Event")'; private readonly viewProfileButton = 'a:has-text("View Profile")'; private readonly logoutButton = 'a:has-text("Logout")'; private readonly generateCertificatesButton = 'a:has-text("Generate Certificates")'; private readonly certificateReportsButton = 'a:has-text("Certificate Reports")'; private readonly eventsTable = 'table'; // Updated Stats row layout selectors private readonly statsSection = '.hvac-stats-row'; private readonly statsColumns = '.hvac-stat-col'; private readonly totalEventsCard = '.hvac-stat-card:has-text("Total Events")'; private readonly upcomingEventsCard = '.hvac-stat-card:has-text("Upcoming Events")'; private readonly pastEventsCard = '.hvac-stat-card:has-text("Past Events")'; private readonly totalRevenueCard = '.hvac-stat-card:has-text("Total Revenue")'; // Event filters selectors private readonly filterButtons = '.hvac-event-filters a'; private readonly allFilterButton = '.hvac-event-filters a:has-text("All")'; private readonly publishFilterButton = '.hvac-event-filters a:has-text("Publish")'; private readonly draftFilterButton = '.hvac-event-filters a:has-text("Draft")'; private readonly pendingFilterButton = '.hvac-event-filters a:has-text("Pending")'; private readonly privateFilterButton = '.hvac-event-filters a:has-text("Private")'; private readonly activeFilterClass = 'hvac-filter-active'; private readonly loadingIndicator = '.hvac-loading'; constructor(page: Page) { super(page); } async navigate(): Promise { const STAGING_URL = 'https://wordpress-974670-5399585.cloudwaysapps.com'; await this.page.goto(`${STAGING_URL}/hvac-dashboard/`); await this.page.waitForLoadState('networkidle'); } async navigateToDashboard(): Promise { await this.navigate('/hvac-dashboard/'); } async clickCreateEvent(): Promise { await this.click(this.createEventButton); await this.waitForNavigation(); } async clickViewProfile(): Promise { await this.click(this.viewProfileButton); await this.waitForNavigation(); } async clickGenerateCertificates(): Promise { await this.page.click(this.generateCertificatesButton); await this.page.waitForLoadState('networkidle'); } async clickCertificateReports(): Promise { await this.page.click(this.certificateReportsButton); await this.page.waitForLoadState('networkidle'); } async logout(): Promise { await this.click(this.logoutButton); await this.waitForNavigation(); } async getStatistics(): Promise<{ totalEvents: string; upcomingEvents: string; pastEvents: string; revenue: string; }> { return { totalEvents: await this.page.locator(this.totalEventsCard).locator('p').textContent() || '0', upcomingEvents: await this.page.locator(this.upcomingEventsCard).locator('p').textContent() || '0', pastEvents: await this.page.locator(this.pastEventsCard).locator('p').textContent() || '0', revenue: await this.page.locator(this.totalRevenueCard).locator('p').textContent() || '$0.00' }; } /** * Get the number of stat columns in the row layout */ async getStatsColumnCount(): Promise { return await this.page.locator(this.statsColumns).count(); } /** * Check if the stats are displayed in a row layout */ async areStatsInRowLayout(): Promise { // First check if the row container exists const statsRow = await this.isVisible(this.statsSection); if (!statsRow) return false; // Get columns const columns = await this.page.locator(this.statsColumns); const count = await columns.count(); // Need at least 2 columns to verify row layout if (count < 2) return false; // Get bounding boxes to verify horizontal layout const box1 = await columns.nth(0).boundingBox(); const box2 = await columns.nth(1).boundingBox(); if (!box1 || !box2) return false; // In a row layout, the second column should be to the right of the first // (This may not be true on very narrow screens where they would wrap) return box2.x > box1.x; } async isEventsTableVisible(): Promise { return await this.isVisible(this.eventsTable); } /** * Get the count of filter buttons */ async getFilterButtonCount(): Promise { return await this.page.locator(this.filterButtons).count(); } /** * Get the currently active filter */ async getActiveFilter(): Promise { const activeFilter = await this.page.locator(`${this.filterButtons}.${this.activeFilterClass}`); if (await activeFilter.count() === 0) { return 'All'; // Default filter is All } return (await activeFilter.textContent() || '').trim(); } /** * Filter events by status * @param status The filter status: 'All', 'Publish', 'Draft', 'Pending', or 'Private' */ async filterEvents(status: 'All' | 'Publish' | 'Draft' | 'Pending' | 'Private'): Promise { this.verbosity.log(`Filtering events by status: ${status}`); // Get the appropriate selector based on status let selector: string; switch (status) { case 'All': selector = this.allFilterButton; break; case 'Publish': selector = this.publishFilterButton; break; case 'Draft': selector = this.draftFilterButton; break; case 'Pending': selector = this.pendingFilterButton; break; case 'Private': selector = this.privateFilterButton; break; default: throw new Error(`Invalid filter status: ${status}`); } // Click the filter button await this.click(selector); // Wait for the loading indicator to disappear if it appears const loadingElement = this.page.locator(this.loadingIndicator); if (await loadingElement.isVisible()) { await loadingElement.waitFor({ state: 'hidden', timeout: 5000 }); } // Wait a moment for the AJAX content to update await this.page.waitForTimeout(500); } /** * Check if filter status appears in URL */ async doesUrlContainFilterStatus(status: string): Promise { const url = await this.getUrl(); // 'All' filter should not have event_status in URL if (status.toLowerCase() === 'all') { return !url.includes('event_status='); } // Other filters should have event_status=status in URL return url.includes(`event_status=${status.toLowerCase()}`); } /** * Check if table has been filtered by the given status * If status is 'All', it checks if the table exists and has any events * Otherwise it verifies that all visible events have the expected status */ async isTableFilteredByStatus(status: 'All' | 'Publish' | 'Draft' | 'Pending' | 'Private'): Promise { // First check if the table exists if (!await this.isEventsTableVisible()) { return false; } // If status is 'All', just check that the table exists if (status === 'All') { return true; } // Get all event rows const rows = await this.page.locator(`${this.eventsTable} tbody tr`); const count = await rows.count(); // If no events, can't verify status if (count === 0) { return false; } // Check for "No events found" message if (count === 1) { const firstRowText = await rows.first().textContent() || ''; if (firstRowText.includes('No events found')) { // This is acceptable if we've filtered to a status with no events return true; } } // For each row, check if the status column matches the expected status for (let i = 0; i < count; i++) { const rowData = await this.getEventRowData(i); // If the status doesn't match, the filter isn't working correctly if (rowData.status.toLowerCase() !== status.toLowerCase()) { return false; } } // All rows have the expected status return true; } async getEventRowData(index: number): Promise<{ status: string; name: string; date: string; organizer: string; capacity: string; soldTickets: string; revenue: string; }> { const row = await this.page.locator(`${this.eventsTable} tbody tr`).nth(index); // Check if this is a "No events found" row const cellCount = await row.locator('td').count(); if (cellCount === 1) { const text = await row.locator('td').textContent(); if (text?.includes('No events found')) { return { status: '', name: text, date: '', organizer: '', capacity: '', soldTickets: '', revenue: '' }; } } return { status: await row.locator('td:nth-child(1)').textContent() || '', name: await row.locator('td:nth-child(2)').textContent() || '', date: await row.locator('td:nth-child(3)').textContent() || '', organizer: await row.locator('td:nth-child(4)').textContent() || '', capacity: await row.locator('td:nth-child(5)').textContent() || '', soldTickets: await row.locator('td:nth-child(6)').textContent() || '', revenue: await row.locator('td:nth-child(7)').textContent() || '' }; } async getEventCount(): Promise { const rows = await this.page.locator(`${this.eventsTable} tbody tr`).count(); // Check if the only row is "No events found" if (rows === 1) { const firstRow = await this.page.locator(`${this.eventsTable} tbody tr`).first(); const cellCount = await firstRow.locator('td').count(); if (cellCount === 1) { const text = await firstRow.locator('td').textContent(); if (text?.includes('No events found')) { return 0; } } } return rows; } async clickEventName(eventName: string): Promise { await this.page.click(`${this.eventsTable} a:has-text("${eventName}")`); await this.waitForNavigation(); } }