- 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>
		
			
				
	
	
		
			251 lines
		
	
	
		
			No EOL
		
	
	
		
			10 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			251 lines
		
	
	
		
			No EOL
		
	
	
		
			10 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import { Page } from '@playwright/test';
 | |
| import { BasePage } from './BasePage';
 | |
| 
 | |
| export class CertificatePage extends BasePage {
 | |
|     // Generate Certificates page selectors
 | |
|     private readonly generateCertificatesTitle = 'h1:has-text("Generate Certificates")';
 | |
|     private readonly eventSelector = 'select[name="event_id"]';
 | |
|     private readonly eventSearchInput = 'input[name="event_search"]';
 | |
|     private readonly selectAllCheckbox = 'input[name="select_all"]';
 | |
|     private readonly attendeeCheckboxes = 'input[name="attendees[]"]';
 | |
|     private readonly generateButton = 'button:has-text("Generate Certificates")';
 | |
|     private readonly previewButton = 'button:has-text("Preview Certificate")';
 | |
|     private readonly successMessage = '.hvac-success-message';
 | |
|     private readonly errorMessage = '.hvac-error-message';
 | |
|     private readonly attendeeList = '.hvac-attendee-list';
 | |
|     private readonly attendeeItem = '.hvac-attendee-item';
 | |
|     private readonly checkinStatusAttribute = 'data-checkin-status';
 | |
|     private readonly loadingIndicator = '.hvac-loading';
 | |
| 
 | |
|     // Certificate Reports page selectors
 | |
|     private readonly certificateReportsTitle = 'h1:has-text("Certificate Reports")';
 | |
|     private readonly certificateFilterInput = 'input[name="certificate_search"]';
 | |
|     private readonly certificateTable = '.hvac-certificate-table';
 | |
|     private readonly certificateTableRows = '.hvac-certificate-table tbody tr';
 | |
|     private readonly viewCertificateButton = 'button:has-text("View")';
 | |
|     private readonly emailCertificateButton = 'button:has-text("Email")';
 | |
|     private readonly revokeCertificateButton = 'button:has-text("Revoke")';
 | |
|     private readonly certificatePagination = '.hvac-pagination';
 | |
|     private readonly certificateModal = '.hvac-certificate-modal';
 | |
|     private readonly certificatePreview = '.hvac-certificate-preview';
 | |
|     private readonly closeModalButton = '.hvac-modal-close';
 | |
|     private readonly confirmRevocationButton = 'button:has-text("Confirm Revocation")';
 | |
|     private readonly confirmEmailButton = 'button:has-text("Send Email")';
 | |
| 
 | |
|     constructor(page: Page) {
 | |
|         super(page);
 | |
|     }
 | |
| 
 | |
|     // Common methods
 | |
|     async navigateToGenerateCertificates(): Promise<void> {
 | |
|         const STAGING_URL = 'https://wordpress-974670-5399585.cloudwaysapps.com';
 | |
|         await this.page.goto(`${STAGING_URL}/generate-certificates/`);
 | |
|         await this.page.waitForLoadState('networkidle');
 | |
|         await this.screenshot('generate-certificates-page');
 | |
|     }
 | |
| 
 | |
|     async navigateToCertificateReports(): Promise<void> {
 | |
|         const STAGING_URL = 'https://wordpress-974670-5399585.cloudwaysapps.com';
 | |
|         await this.page.goto(`${STAGING_URL}/certificate-reports/`);
 | |
|         await this.page.waitForLoadState('networkidle');
 | |
|         await this.screenshot('certificate-reports-page');
 | |
|     }
 | |
| 
 | |
|     // Generate Certificates page methods
 | |
|     async isGenerateCertificatesPageVisible(): Promise<boolean> {
 | |
|         return await this.isVisible(this.generateCertificatesTitle);
 | |
|     }
 | |
| 
 | |
|     async selectEvent(eventName: string): Promise<void> {
 | |
|         // If there's a search input, try using it
 | |
|         if (await this.isVisible(this.eventSearchInput)) {
 | |
|             await this.fill(this.eventSearchInput, eventName);
 | |
|             await this.page.waitForTimeout(500); // Wait for search results
 | |
|         }
 | |
|         
 | |
|         // Select the event from dropdown
 | |
|         await this.page.selectOption(this.eventSelector, { label: eventName });
 | |
|         await this.page.waitForTimeout(1000); // Wait for attendee list to load
 | |
|         
 | |
|         // Wait for loading indicator to disappear if it's present
 | |
|         const loadingElement = this.page.locator(this.loadingIndicator);
 | |
|         if (await loadingElement.isVisible()) {
 | |
|             await loadingElement.waitFor({ state: 'hidden', timeout: 5000 });
 | |
|         }
 | |
|         
 | |
|         await this.screenshot('event-selected');
 | |
|     }
 | |
| 
 | |
|     async getAttendeeCount(): Promise<number> {
 | |
|         return await this.page.locator(this.attendeeItem).count();
 | |
|     }
 | |
| 
 | |
|     async getCheckedInAttendeeCount(): Promise<number> {
 | |
|         let checkedInCount = 0;
 | |
|         const attendees = this.page.locator(this.attendeeItem);
 | |
|         const count = await attendees.count();
 | |
|         
 | |
|         for (let i = 0; i < count; i++) {
 | |
|             const status = await attendees.nth(i).getAttribute(this.checkinStatusAttribute);
 | |
|             if (status === 'checked-in') {
 | |
|                 checkedInCount++;
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         return checkedInCount;
 | |
|     }
 | |
| 
 | |
|     async selectAllAttendees(): Promise<void> {
 | |
|         await this.click(this.selectAllCheckbox);
 | |
|         await this.screenshot('all-attendees-selected');
 | |
|     }
 | |
| 
 | |
|     async selectCheckedInAttendees(): Promise<void> {
 | |
|         // Deselect "Select All" if it's checked
 | |
|         const selectAllChecked = await this.page.isChecked(this.selectAllCheckbox);
 | |
|         if (selectAllChecked) {
 | |
|             await this.click(this.selectAllCheckbox);
 | |
|         }
 | |
|         
 | |
|         // Select only checked-in attendees
 | |
|         const attendees = this.page.locator(this.attendeeItem);
 | |
|         const count = await attendees.count();
 | |
|         
 | |
|         for (let i = 0; i < count; i++) {
 | |
|             const status = await attendees.nth(i).getAttribute(this.checkinStatusAttribute);
 | |
|             if (status === 'checked-in') {
 | |
|                 const checkbox = attendees.nth(i).locator('input[type="checkbox"]');
 | |
|                 await checkbox.check();
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         await this.screenshot('checked-in-attendees-selected');
 | |
|     }
 | |
| 
 | |
|     async selectNonCheckedInAttendees(): Promise<void> {
 | |
|         // Deselect "Select All" if it's checked
 | |
|         const selectAllChecked = await this.page.isChecked(this.selectAllCheckbox);
 | |
|         if (selectAllChecked) {
 | |
|             await this.click(this.selectAllCheckbox);
 | |
|         }
 | |
|         
 | |
|         // Select only non-checked-in attendees
 | |
|         const attendees = this.page.locator(this.attendeeItem);
 | |
|         const count = await attendees.count();
 | |
|         
 | |
|         for (let i = 0; i < count; i++) {
 | |
|             const status = await attendees.nth(i).getAttribute(this.checkinStatusAttribute);
 | |
|             if (status !== 'checked-in') {
 | |
|                 const checkbox = attendees.nth(i).locator('input[type="checkbox"]');
 | |
|                 await checkbox.check();
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         await this.screenshot('non-checked-in-attendees-selected');
 | |
|     }
 | |
| 
 | |
|     async generateCertificates(): Promise<void> {
 | |
|         await this.click(this.generateButton);
 | |
|         
 | |
|         // Wait for loading indicator to disappear if it's present
 | |
|         const loadingElement = this.page.locator(this.loadingIndicator);
 | |
|         if (await loadingElement.isVisible()) {
 | |
|             await loadingElement.waitFor({ state: 'hidden', timeout: 10000 });
 | |
|         }
 | |
|         
 | |
|         await this.page.waitForTimeout(2000); // Additional wait for any post-processing
 | |
|         await this.screenshot('certificates-generated');
 | |
|     }
 | |
| 
 | |
|     async previewCertificate(): Promise<void> {
 | |
|         await this.click(this.previewButton);
 | |
|         
 | |
|         // Wait for the preview modal to appear
 | |
|         await this.waitForElement(this.certificateModal);
 | |
|         await this.screenshot('certificate-preview');
 | |
|     }
 | |
| 
 | |
|     async closePreview(): Promise<void> {
 | |
|         if (await this.isVisible(this.closeModalButton)) {
 | |
|             await this.click(this.closeModalButton);
 | |
|             await this.page.waitForTimeout(500); // Wait for modal to close
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     async isSuccessMessageVisible(): Promise<boolean> {
 | |
|         return await this.isVisible(this.successMessage);
 | |
|     }
 | |
| 
 | |
|     async isErrorMessageVisible(): Promise<boolean> {
 | |
|         return await this.isVisible(this.errorMessage);
 | |
|     }
 | |
| 
 | |
|     async getSuccessMessage(): Promise<string> {
 | |
|         return await this.getText(this.successMessage);
 | |
|     }
 | |
| 
 | |
|     async getErrorMessage(): Promise<string> {
 | |
|         return await this.getText(this.errorMessage);
 | |
|     }
 | |
| 
 | |
|     // Certificate Reports page methods
 | |
|     async isCertificateReportsPageVisible(): Promise<boolean> {
 | |
|         return await this.isVisible(this.certificateReportsTitle);
 | |
|     }
 | |
| 
 | |
|     async searchCertificates(query: string): Promise<void> {
 | |
|         await this.fill(this.certificateFilterInput, query);
 | |
|         await this.page.waitForTimeout(1000); // Wait for search results
 | |
|         
 | |
|         // Wait for loading indicator to disappear if it's present
 | |
|         const loadingElement = this.page.locator(this.loadingIndicator);
 | |
|         if (await loadingElement.isVisible()) {
 | |
|             await loadingElement.waitFor({ state: 'hidden', timeout: 5000 });
 | |
|         }
 | |
|         
 | |
|         await this.screenshot('certificate-search');
 | |
|     }
 | |
| 
 | |
|     async getCertificateCount(): Promise<number> {
 | |
|         return await this.page.locator(this.certificateTableRows).count();
 | |
|     }
 | |
| 
 | |
|     async viewCertificate(index: number = 0): Promise<void> {
 | |
|         const viewButtons = this.page.locator(this.viewCertificateButton);
 | |
|         await viewButtons.nth(index).click();
 | |
|         
 | |
|         // Wait for the preview modal to appear
 | |
|         await this.waitForElement(this.certificateModal);
 | |
|         await this.screenshot('view-certificate');
 | |
|     }
 | |
| 
 | |
|     async emailCertificate(index: number = 0): Promise<void> {
 | |
|         const emailButtons = this.page.locator(this.emailCertificateButton);
 | |
|         await emailButtons.nth(index).click();
 | |
|         
 | |
|         // Wait for the email confirmation dialog
 | |
|         if (await this.isVisible(this.confirmEmailButton)) {
 | |
|             await this.click(this.confirmEmailButton);
 | |
|             await this.page.waitForTimeout(2000); // Wait for email to be sent
 | |
|         }
 | |
|         
 | |
|         await this.screenshot('email-certificate');
 | |
|     }
 | |
| 
 | |
|     async revokeCertificate(index: number = 0): Promise<void> {
 | |
|         const revokeButtons = this.page.locator(this.revokeCertificateButton);
 | |
|         await revokeButtons.nth(index).click();
 | |
|         
 | |
|         // Wait for the revocation confirmation dialog
 | |
|         if (await this.isVisible(this.confirmRevocationButton)) {
 | |
|             await this.click(this.confirmRevocationButton);
 | |
|             await this.page.waitForTimeout(2000); // Wait for revocation to complete
 | |
|         }
 | |
|         
 | |
|         await this.screenshot('revoke-certificate');
 | |
|     }
 | |
| 
 | |
|     async isPaginationVisible(): Promise<boolean> {
 | |
|         return await this.isVisible(this.certificatePagination);
 | |
|     }
 | |
| } |