upskill-event-manager/wordpress-dev/tests/e2e/pages/DashboardPage.ts
bengizmo fbc2d818c0 feat: Add comprehensive certificate E2E tests
- Created CertificatePage class for testing certificate functionality
- Updated DashboardPage to support certificate links in navigation
- Implemented test data generator for certificate testing
- Added tests for certificate generation with checked-in users
- Added tests for certificate generation with non-checked-in users
- Added certificate management (view/email/revoke) tests
- Created comprehensive trainer journey test including certificates
- Added utility script to run certificate-specific tests

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-05-20 20:44:42 -03:00

302 lines
No EOL
11 KiB
TypeScript

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<void> {
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<void> {
await this.navigate('/hvac-dashboard/');
}
async clickCreateEvent(): Promise<void> {
await this.click(this.createEventButton);
await this.waitForNavigation();
}
async clickViewProfile(): Promise<void> {
await this.click(this.viewProfileButton);
await this.waitForNavigation();
}
async clickGenerateCertificates(): Promise<void> {
await this.click(this.generateCertificatesButton);
await this.waitForNavigation();
}
async clickCertificateReports(): Promise<void> {
await this.click(this.certificateReportsButton);
await this.waitForNavigation();
}
async logout(): Promise<void> {
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<number> {
return await this.page.locator(this.statsColumns).count();
}
/**
* Check if the stats are displayed in a row layout
*/
async areStatsInRowLayout(): Promise<boolean> {
// 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<boolean> {
return await this.isVisible(this.eventsTable);
}
/**
* Get the count of filter buttons
*/
async getFilterButtonCount(): Promise<number> {
return await this.page.locator(this.filterButtons).count();
}
/**
* Get the currently active filter
*/
async getActiveFilter(): Promise<string> {
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<void> {
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<boolean> {
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<boolean> {
// 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<number> {
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<void> {
await this.page.click(`${this.eventsTable} a:has-text("${eventName}")`);
await this.waitForNavigation();
}
}