diff --git a/wordpress-dev/bin/test-certificate-filter.sh b/wordpress-dev/bin/test-certificate-filter.sh new file mode 100755 index 00000000..93a9b343 --- /dev/null +++ b/wordpress-dev/bin/test-certificate-filter.sh @@ -0,0 +1,172 @@ +#!/bin/bash + +# Certificate filtering test script +# This script runs certificate filtering tests with various filter combinations + +# Colors for output +GREEN='\033[0;32m' +RED='\033[0;31m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Change to the project root directory +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +cd "$SCRIPT_DIR/.." || exit 1 +echo "Changed working directory to: $(pwd)" + +echo -e "${BLUE}============================================${NC}" +echo -e "${BLUE} Certificate Filtering Test Runner ${NC}" +echo -e "${BLUE}============================================${NC}" + +# Test data for filtering +TEST_EVENTS=( + "HVAC System Design Fundamentals" + "Advanced Refrigeration Technology" + "Building Automation Systems Workshop" +) + +TEST_ATTENDEES=( + "John Smith" + "Sarah Johnson" + "example.com" +) + +# Function to run a test with specific filter +run_certificate_test() { + local test_type=$1 + local filter_value=$2 + local additional_args=$3 + + echo -e "\n${YELLOW}Running certificate test with ${test_type} filter: ${filter_value}${NC}" + + # Construct test command with proper escaping + local command="FILTER_TYPE=\"${test_type}\" FILTER_VALUE=\"${filter_value}\" npx playwright test tests/e2e/certificates.test.ts --config=tests/e2e/optimized-playwright.config.ts ${additional_args}" + + echo -e "${YELLOW}$ ${command}${NC}" + + # Run the test command + eval "$command" + + # Check if test passed + local exit_code=$? + if [ $exit_code -eq 0 ]; then + echo -e "${GREEN}✓ Test passed${NC}" + else + echo -e "${RED}✗ Test failed with exit code ${exit_code}${NC}" + fi + + echo -e "${YELLOW}-----------------------------------------------${NC}" +} + +# Function to print test summary +print_summary() { + echo -e "\n${BLUE}============================================${NC}" + echo -e "${BLUE} Certificate Filtering Test Summary ${NC}" + echo -e "${BLUE}============================================${NC}" + echo -e "${YELLOW}Filter tests run:${NC} $1" + echo -e "${GREEN}Tests passed:${NC} $2" + echo -e "${RED}Tests failed:${NC} $3" + echo -e "${BLUE}============================================${NC}" +} + +# Check if the certificates test exists +if [ ! -f "tests/e2e/certificates.test.ts" ]; then + echo -e "${RED}Error: certificates.test.ts not found!${NC}" + echo -e "Make sure the certificate test file exists at tests/e2e/certificates.test.ts" + exit 1 +fi + +# Check if optimized config exists +if [ ! -f "tests/e2e/optimized-playwright.config.ts" ]; then + echo -e "${RED}Error: optimized-playwright.config.ts not found!${NC}" + echo -e "Make sure the optimized configuration file exists at tests/e2e/optimized-playwright.config.ts" + exit 1 +fi + +# Ask if we should run all tests or just a specific one +echo -e "\n${YELLOW}Select test mode:${NC}" +echo "1) Run all certificate filter tests" +echo "2) Run event filtering tests only" +echo "3) Run attendee filtering tests only" +echo "4) Run custom filter test" +read -p "Enter your choice (1-4): " TEST_MODE + +# Track test statistics +TOTAL_TESTS=0 +PASSED_TESTS=0 +FAILED_TESTS=0 + +case $TEST_MODE in + 1) + # Run all test combinations + echo -e "\n${BLUE}Running all certificate filter tests...${NC}" + + # Event filter tests + for event in "${TEST_EVENTS[@]}"; do + run_certificate_test "event" "$event" "--headed" + TOTAL_TESTS=$((TOTAL_TESTS+1)) + [ $? -eq 0 ] && PASSED_TESTS=$((PASSED_TESTS+1)) || FAILED_TESTS=$((FAILED_TESTS+1)) + done + + # Attendee filter tests + for attendee in "${TEST_ATTENDEES[@]}"; do + run_certificate_test "attendee" "$attendee" "--headed" + TOTAL_TESTS=$((TOTAL_TESTS+1)) + [ $? -eq 0 ] && PASSED_TESTS=$((PASSED_TESTS+1)) || FAILED_TESTS=$((FAILED_TESTS+1)) + done + + # Combined filter test (first event + first attendee) + run_certificate_test "combined" "${TEST_EVENTS[0]}|${TEST_ATTENDEES[0]}" "--headed" + TOTAL_TESTS=$((TOTAL_TESTS+1)) + [ $? -eq 0 ] && PASSED_TESTS=$((PASSED_TESTS+1)) || FAILED_TESTS=$((FAILED_TESTS+1)) + ;; + + 2) + # Run event filter tests only + echo -e "\n${BLUE}Running event filter tests...${NC}" + + for event in "${TEST_EVENTS[@]}"; do + run_certificate_test "event" "$event" "--headed" + TOTAL_TESTS=$((TOTAL_TESTS+1)) + [ $? -eq 0 ] && PASSED_TESTS=$((PASSED_TESTS+1)) || FAILED_TESTS=$((FAILED_TESTS+1)) + done + ;; + + 3) + # Run attendee filter tests only + echo -e "\n${BLUE}Running attendee filter tests...${NC}" + + for attendee in "${TEST_ATTENDEES[@]}"; do + run_certificate_test "attendee" "$attendee" "--headed" + TOTAL_TESTS=$((TOTAL_TESTS+1)) + [ $? -eq 0 ] && PASSED_TESTS=$((PASSED_TESTS+1)) || FAILED_TESTS=$((FAILED_TESTS+1)) + done + ;; + + 4) + # Run custom filter test + echo -e "\n${BLUE}Running custom filter test...${NC}" + + read -p "Enter filter type (event, attendee, combined): " CUSTOM_TYPE + read -p "Enter filter value: " CUSTOM_VALUE + read -p "Run with UI? (y/n): " WITH_UI + + UI_FLAG="" + [ "$WITH_UI" = "y" ] && UI_FLAG="--headed" + + run_certificate_test "$CUSTOM_TYPE" "$CUSTOM_VALUE" "$UI_FLAG" + TOTAL_TESTS=$((TOTAL_TESTS+1)) + [ $? -eq 0 ] && PASSED_TESTS=$((PASSED_TESTS+1)) || FAILED_TESTS=$((FAILED_TESTS+1)) + ;; + + *) + echo -e "${RED}Invalid option selected.${NC}" + exit 1 + ;; +esac + +# Print test summary +print_summary $TOTAL_TESTS $PASSED_TESTS $FAILED_TESTS + +exit 0 \ No newline at end of file diff --git a/wordpress-dev/tests/e2e/certificates.test.ts b/wordpress-dev/tests/e2e/certificates.test.ts new file mode 100644 index 00000000..1b0487ba --- /dev/null +++ b/wordpress-dev/tests/e2e/certificates.test.ts @@ -0,0 +1,223 @@ +import { test, expect } from '@playwright/test'; +import { LoginPage } from './pages/LoginPage'; +import { DashboardPage } from './pages/DashboardPage'; +import { CertificatePage } from './pages/CertificatePage'; + +/** + * Certificate Tests + * + * These tests verify the certificate generation, reporting, and filtering functionality + * + * @group @certificate + */ + +const STAGING_URL = 'https://wordpress-974670-5399585.cloudwaysapps.com'; + +// Test data for existing certificates to verify +const TEST_EVENTS = [ + { id: '5641', name: 'HVAC System Design Fundamentals' }, + { id: '5668', name: 'Advanced Refrigeration Technology' }, + { id: '5688', name: 'Building Automation Systems Workshop' } +]; + +// Test data for attendee filtering +const TEST_ATTENDEES = [ + { name: 'John Smith', email: 'john.smith@example.com' }, + { name: 'Sarah Johnson', email: 'sarah.johnson@example.com' }, + { name: 'Michael Brown', email: 'michael.brown@example.com' } +]; + +// Common setup for tests +async function setupTest(page) { + const loginPage = new LoginPage(page); + await loginPage.navigate(); + await loginPage.login('test_trainer', 'Test123!'); + + // Verify login + const dashboardPage = new DashboardPage(page); + await expect(page).toHaveURL(/hvac-dashboard/); + + return { loginPage, dashboardPage }; +} + +test.describe('Certificate Generation and Reports', () => { + test('Navigate to certificate generation page', async ({ page }) => { + const { dashboardPage } = await setupTest(page); + + // Navigate to Generate Certificates page + await dashboardPage.clickGenerateCertificates(); + + // Verify page components + const certificatePage = new CertificatePage(page); + const pageVisible = await certificatePage.isGenerateCertificatesPageVisible(); + expect(pageVisible).toBeTruthy(); + + // Check for event dropdown + await expect(page.locator('#event_id')).toBeVisible(); + }); + + test('Navigate to certificate reports page', async ({ page }) => { + const { dashboardPage } = await setupTest(page); + + // Navigate to Certificate Reports page + await dashboardPage.clickCertificateReports(); + + // Verify page components + const certificatePage = new CertificatePage(page); + const pageVisible = await certificatePage.isCertificateReportsPageVisible(); + expect(pageVisible).toBeTruthy(); + + // Check for filter form + await expect(page.locator('form.hvac-certificate-filters')).toBeVisible(); + }); + + test('Filter certificates by event', async ({ page }) => { + const { dashboardPage } = await setupTest(page); + + // Navigate to Certificate Reports page + await dashboardPage.clickCertificateReports(); + const certificatePage = new CertificatePage(page); + + // Select an event to filter + const testEvent = TEST_EVENTS[0]; + await certificatePage.searchCertificates(testEvent.name); + + // Verify results + await page.waitForLoadState('networkidle'); + const certificateCount = await certificatePage.getCertificateCount(); + console.log(`Found ${certificateCount} certificates for event: ${testEvent.name}`); + + // Should have at least some certificates (can be zero if no certificates exist) + expect(certificateCount).toBeGreaterThanOrEqual(0); + }); + + test('Search certificates by attendee', async ({ page }) => { + const { dashboardPage } = await setupTest(page); + + // Navigate to Certificate Reports page + await dashboardPage.clickCertificateReports(); + const certificatePage = new CertificatePage(page); + + // Search for an attendee + const testAttendee = TEST_ATTENDEES[0]; + await certificatePage.searchAttendee(testAttendee.name); + + // Verify search results + await page.waitForLoadState('networkidle'); + const certificateCount = await certificatePage.getCertificateCount(); + console.log(`Found ${certificateCount} certificates for attendee: ${testAttendee.name}`); + + // Verify filter was applied (search input should have the attendee name) + const searchValue = await page.inputValue('#search_attendee'); + expect(searchValue).toEqual(testAttendee.name); + }); + + test('Reset certificate filters', async ({ page }) => { + const { dashboardPage } = await setupTest(page); + + // Navigate to Certificate Reports page + await dashboardPage.clickCertificateReports(); + const certificatePage = new CertificatePage(page); + + // Apply a filter first + await certificatePage.searchAttendee('test'); + + // Get count with filter + const filteredCount = await certificatePage.getCertificateCount(); + + // Reset filters + await certificatePage.resetFilters(); + + // Get count after reset + const resetCount = await certificatePage.getCertificateCount(); + + // The search input should be empty after reset + const searchValue = await page.inputValue('#search_attendee'); + expect(searchValue).toEqual(''); + + console.log(`Filtered certificates: ${filteredCount}, After reset: ${resetCount}`); + // We can't guarantee counts will differ, but the reset should work + }); + + test('View certificate details', async ({ page }) => { + const { dashboardPage } = await setupTest(page); + + // Navigate to Certificate Reports page + await dashboardPage.clickCertificateReports(); + const certificatePage = new CertificatePage(page); + + // Get initial certificate count + const certificateCount = await certificatePage.getCertificateCount(); + console.log(`Found ${certificateCount} certificates`); + + // Skip test if no certificates + test.skip(certificateCount === 0, 'No certificates available to view'); + + if (certificateCount > 0) { + // View the first certificate + await certificatePage.viewCertificate(0); + + // Verify preview is visible + await expect(page.locator('.hvac-certificate-preview')).toBeVisible(); + + // Close the preview + await certificatePage.closePreview(); + + // Preview should be hidden + await expect(page.locator('.hvac-certificate-preview')).not.toBeVisible(); + } + }); +}); + +// Comprehensive test for attendee search functionality +test('Attendee search functionality', async ({ page }) => { + const { dashboardPage } = await setupTest(page); + + // Navigate to Certificate Reports page + await dashboardPage.clickCertificateReports(); + const certificatePage = new CertificatePage(page); + + // Test partial name search + await certificatePage.searchAttendee('John'); + await page.waitForLoadState('networkidle'); + + // Verify search results + const nameSearchCount = await certificatePage.getCertificateCount(); + console.log(`Found ${nameSearchCount} certificates for attendee name: John`); + + // Reset filters + await certificatePage.resetFilters(); + + // Test email domain search + await certificatePage.searchAttendee('example.com'); + await page.waitForLoadState('networkidle'); + + // Verify search results + const emailSearchCount = await certificatePage.getCertificateCount(); + console.log(`Found ${emailSearchCount} certificates for email domain: example.com`); + + // Reset filters + await certificatePage.resetFilters(); + + // Test combination of filters (event + attendee) + if (TEST_EVENTS.length > 0) { + const testEvent = TEST_EVENTS[0]; + + // Select an event + await certificatePage.searchCertificates(testEvent.name); + + // Then search for an attendee + await certificatePage.searchAttendee('test'); + + // Verify combined results + const combinedCount = await certificatePage.getCertificateCount(); + console.log(`Found ${combinedCount} certificates for event "${testEvent.name}" and attendee "test"`); + + // Verify both filters were applied + const searchValue = await page.inputValue('#search_attendee'); + expect(searchValue).toEqual('test'); + + // Reset filters + await certificatePage.resetFilters(); + } +}); \ No newline at end of file diff --git a/wordpress-dev/tests/e2e/pages/BasePage.ts b/wordpress-dev/tests/e2e/pages/BasePage.ts new file mode 100644 index 00000000..7415b61b --- /dev/null +++ b/wordpress-dev/tests/e2e/pages/BasePage.ts @@ -0,0 +1,92 @@ +import { Page } from '@playwright/test'; + +/** + * Base page object that all page objects inherit from + * Contains common methods and properties used across pages + */ +export class BasePage { + protected readonly page: Page; + protected readonly baseUrl: string = 'https://wordpress-974670-5399585.cloudwaysapps.com'; + + constructor(page: Page) { + this.page = page; + } + + /** + * Log a message to the console + */ + protected log(message: string): void { + console.log(`[${this.constructor.name}] ${message}`); + } + + /** + * Wait for page to be fully loaded + */ + async waitForPageLoad(): Promise { + await this.page.waitForLoadState('domcontentloaded'); + await this.page.waitForLoadState('networkidle'); + } + + /** + * Get the current page title + */ + async getTitle(): Promise { + return await this.page.title(); + } + + /** + * Get the current page URL + */ + async getUrl(): Promise { + return this.page.url(); + } + + /** + * Navigate to a specific URL path + * @param path The path to navigate to (relative to baseUrl) + */ + async navigateTo(path: string): Promise { + await this.page.goto(`${this.baseUrl}${path}`); + await this.waitForPageLoad(); + } + + /** + * Take a screenshot and return the buffer + * @param name Optional name for the screenshot + */ + async takeScreenshot(name?: string): Promise { + const screenshotName = name || `${this.constructor.name}-${new Date().getTime()}`; + return await this.page.screenshot({ + path: `./screenshots/${screenshotName}.png`, + fullPage: true + }); + } + + /** + * Check if an element is visible on the page + * @param selector The selector for the element + */ + async isVisible(selector: string): Promise { + return await this.page.isVisible(selector); + } + + /** + * Get text content of an element + * @param selector The selector for the element + */ + async getText(selector: string): Promise { + return await this.page.textContent(selector); + } + + /** + * Click an element and wait for navigation + * @param selector The selector for the element to click + */ + async clickAndWaitForNavigation(selector: string): Promise { + await Promise.all([ + this.page.waitForNavigation(), + this.page.click(selector) + ]); + await this.waitForPageLoad(); + } +} \ No newline at end of file diff --git a/wordpress-dev/tests/e2e/pages/CertificatePage.ts b/wordpress-dev/tests/e2e/pages/CertificatePage.ts index a1aab627..21299e00 100644 --- a/wordpress-dev/tests/e2e/pages/CertificatePage.ts +++ b/wordpress-dev/tests/e2e/pages/CertificatePage.ts @@ -1,270 +1,195 @@ import { Page, expect } from '@playwright/test'; import { BasePage } from './BasePage'; -import { Config } from '../utils/Config'; +/** + * Page object representing the Certificate-related pages + * Handles both Certificate Generation and Certificate Reports pages + */ 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'; + // Selectors for Generate Certificates page + private readonly generatePageTitle = 'h1:text("Generate Certificates")'; + private readonly eventSelectDropdown = '#event_id'; + private readonly selectedEventName = '.hvac-selected-event strong'; + private readonly attendeeList = '.hvac-attendee-list'; + private readonly attendeeItems = '.hvac-attendee-item'; + private readonly checkedInLabel = '.hvac-checked-in'; + private readonly checkAllButton = 'button:text("Select All")'; + private readonly checkCheckedInButton = 'button:text("Select Checked-In")'; + private readonly generateButton = 'button[type="submit"]:text("Generate Certificates")'; + private readonly successMessage = '.hvac-success-message'; + private readonly errorMessage = '.hvac-error-message'; - // 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")'; - private readonly previousPageButton = '.hvac-pagination-prev'; - private readonly nextPageButton = '.hvac-pagination-next'; + // Selectors for Certificate Reports page + private readonly reportsPageTitle = 'h1:text("Certificate Reports")'; + private readonly filterForm = 'form.hvac-certificate-filters'; + private readonly eventFilterSelect = '#filter_event'; + private readonly attendeeSearchInput = '#search_attendee'; + private readonly revokedFilterSelect = '#filter_revoked'; + private readonly filterButton = 'button[type="submit"]:text("Filter")'; + private readonly resetButton = 'button[type="reset"]:text("Reset Filters")'; + private readonly certificateTable = '.hvac-certificate-table'; + private readonly certificateItems = '.hvac-certificate-item'; + private readonly viewCertificateLinks = 'a:text("View")'; + private readonly certificatePreview = '.hvac-certificate-preview'; + private readonly closePreviewButton = '.hvac-preview-close'; - constructor(page: Page) { - super(page); + constructor(page: Page) { + super(page); + } + + /** + * Navigate to the Generate Certificates page + */ + async navigateToGenerateCertificates(): Promise { + await this.page.goto('/generate-certificates/'); + await this.page.waitForLoadState('networkidle'); + await this.page.waitForSelector(this.generatePageTitle); + } + + /** + * Navigate to the Certificate Reports page + */ + async navigateToCertificateReports(): Promise { + await this.page.goto('/certificate-reports/'); + await this.page.waitForLoadState('networkidle'); + await this.page.waitForSelector(this.reportsPageTitle); + } + + /** + * Check if the Generate Certificates page is visible + */ + async isGenerateCertificatesPageVisible(): Promise { + return await this.page.isVisible(this.generatePageTitle); + } + + /** + * Check if the Certificate Reports page is visible + */ + async isCertificateReportsPageVisible(): Promise { + return await this.page.isVisible(this.reportsPageTitle); + } + + /** + * Select an event from the dropdown on Generate Certificates page + */ + async selectEvent(eventName: string): Promise { + await this.page.selectOption(this.eventSelectDropdown, { + label: new RegExp(eventName, 'i') + }); + await this.page.waitForSelector(this.attendeeList); + + // Verify the selected event + const selectedText = await this.page.textContent(this.selectedEventName); + expect(selectedText).toContain(eventName); + } + + /** + * Get total number of attendees listed + */ + async getAttendeeCount(): Promise { + return await this.page.locator(this.attendeeItems).count(); + } + + /** + * Get number of checked-in attendees + */ + async getCheckedInAttendeeCount(): Promise { + return await this.page.locator(this.checkedInLabel).count(); + } + + /** + * Select all attendees + */ + async selectAllAttendees(): Promise { + await this.page.click(this.checkAllButton); + } + + /** + * Select only checked-in attendees + */ + async selectCheckedInAttendees(): Promise { + await this.page.click(this.checkCheckedInButton); + } + + /** + * Generate certificates for selected attendees + */ + async generateCertificates(): Promise { + await this.page.click(this.generateButton); + await this.page.waitForLoadState('networkidle'); + // Wait for either success or error message + await this.page.waitForSelector(`${this.successMessage}, ${this.errorMessage}`); + } + + /** + * Check if success message is visible + */ + async isSuccessMessageVisible(): Promise { + return await this.page.isVisible(this.successMessage); + } + + /** + * Get success message text + */ + async getSuccessMessage(): Promise { + return await this.page.textContent(this.successMessage); + } + + /** + * Filter certificates by event name + */ + async searchCertificates(eventName: string): Promise { + await this.page.selectOption(this.eventFilterSelect, { + label: new RegExp(eventName, 'i') + }); + await this.page.click(this.filterButton); + await this.page.waitForLoadState('networkidle'); + } + + /** + * Filter certificates by attendee name or email + */ + async searchAttendee(searchTerm: string): Promise { + await this.page.fill(this.attendeeSearchInput, searchTerm); + await this.page.click(this.filterButton); + await this.page.waitForLoadState('networkidle'); + } + + /** + * Reset all filters + */ + async resetFilters(): Promise { + await this.page.click(this.resetButton); + await this.page.waitForLoadState('networkidle'); + } + + /** + * Get the number of certificates in the table + */ + async getCertificateCount(): Promise { + return await this.page.locator(this.certificateItems).count(); + } + + /** + * View a certificate by index + */ + async viewCertificate(index: number): Promise { + const viewLinks = this.page.locator(this.viewCertificateLinks); + const count = await viewLinks.count(); + + if (index >= count) { + throw new Error(`Cannot view certificate at index ${index}. Only ${count} certificates available.`); } + + await viewLinks.nth(index).click(); + await this.page.waitForSelector(this.certificatePreview); + } - // Common methods - async navigateToGenerateCertificates(): Promise { - await this.page.goto(Config.generateCertificatesUrl); - await this.page.waitForLoadState('networkidle'); - await this.screenshot('generate-certificates-page'); - } - - async navigateToCertificateReports(): Promise { - await this.page.goto(Config.certificateReportsUrl); - await this.page.waitForLoadState('networkidle'); - await this.screenshot('certificate-reports-page'); - } - - // Generate Certificates page methods - async isGenerateCertificatesPageVisible(): Promise { - return await this.isVisible(this.generateCertificatesTitle); - } - - async selectEvent(eventName: string): Promise { - // 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(Config.shortWait); - } - - // Select the event from dropdown - await this.page.selectOption(this.eventSelector, { label: eventName }); - await this.page.waitForTimeout(Config.shortWait); - - // 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: Config.defaultTimeout }); - } - - await this.screenshot('event-selected'); - } - - async getAttendeeCount(): Promise { - return await this.page.locator(this.attendeeItem).count(); - } - - async getCheckedInAttendeeCount(): Promise { - 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 { - await this.click(this.selectAllCheckbox); - await this.screenshot('all-attendees-selected'); - } - - async selectCheckedInAttendees(): Promise { - // 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 { - // 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 { - 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: Config.longWait }); - } - - await this.page.waitForTimeout(Config.mediumWait); // Additional wait for any post-processing - await this.screenshot('certificates-generated'); - } - - async previewCertificate(): Promise { - 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 { - if (await this.isVisible(this.closeModalButton)) { - await this.click(this.closeModalButton); - await this.page.waitForTimeout(Config.shortWait); // Wait for modal to close - } - } - - async isSuccessMessageVisible(): Promise { - return await this.isVisible(this.successMessage); - } - - async isErrorMessageVisible(): Promise { - return await this.isVisible(this.errorMessage); - } - - async getSuccessMessage(): Promise { - return await this.getText(this.successMessage); - } - - async getErrorMessage(): Promise { - return await this.getText(this.errorMessage); - } - - // Certificate Reports page methods - async isCertificateReportsPageVisible(): Promise { - return await this.isVisible(this.certificateReportsTitle); - } - - async searchCertificates(query: string): Promise { - await this.fill(this.certificateFilterInput, query); - await this.page.waitForTimeout(Config.shortWait); // 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: Config.defaultTimeout }); - } - - await this.screenshot('certificate-search'); - } - - async getCertificateCount(): Promise { - return await this.page.locator(this.certificateTableRows).count(); - } - - async viewCertificate(index: number = 0): Promise { - 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 { - 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(Config.mediumWait); // Wait for email to be sent - } - - await this.screenshot('email-certificate'); - } - - async revokeCertificate(index: number = 0): Promise { - 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(Config.mediumWait); // Wait for revocation to complete - } - - await this.screenshot('revoke-certificate'); - } - - async isPaginationVisible(): Promise { - return await this.isVisible(this.certificatePagination); - } - - async goToNextPage(): Promise { - if (await this.isVisible(this.nextPageButton)) { - await this.click(this.nextPageButton); - await this.page.waitForTimeout(Config.shortWait); - return true; - } - return false; - } - - async goToPreviousPage(): Promise { - if (await this.isVisible(this.previousPageButton)) { - await this.click(this.previousPageButton); - await this.page.waitForTimeout(Config.shortWait); - return true; - } - return false; - } + /** + * Close the certificate preview + */ + async closePreview(): Promise { + await this.page.click(this.closePreviewButton); + await this.page.waitForSelector(this.certificatePreview, { state: 'hidden' }); + } } \ No newline at end of file diff --git a/wordpress-dev/tests/e2e/utils/CertificateTestData.ts b/wordpress-dev/tests/e2e/utils/CertificateTestData.ts index 1c68e7f1..0e434509 100644 --- a/wordpress-dev/tests/e2e/utils/CertificateTestData.ts +++ b/wordpress-dev/tests/e2e/utils/CertificateTestData.ts @@ -1,181 +1,200 @@ import { Page } from '@playwright/test'; -import { VerbosityController } from './VerbosityController'; -import { Config } from './Config'; /** - * Utility class to set up test data for certificate testing - * This helps ensure we have events with both checked-in and non-checked-in attendees + * Utility class for creating test data for certificate tests + * + * This class handles: + * 1. Setting up test events with attendees + * 2. Checking in some attendees + * 3. Cleaning up test data after tests */ export class CertificateTestData { - private page: Page; - private verbosity: VerbosityController; + private page: Page; + private readonly baseUrl = 'https://wordpress-974670-5399585.cloudwaysapps.com'; + private readonly loginUrl = '/community-login/'; + private readonly dashboardUrl = '/hvac-dashboard/'; + private readonly adminUrl = '/wp-admin/'; + + private readonly username = 'test_trainer'; + private readonly password = 'Test123!'; + + private testEventId: string | null = null; + private testEventName: string | null = null; + + constructor(page: Page) { + this.page = page; + } + + /** + * Login as a trainer user for test setup + */ + async loginAsTrainer(): Promise { + await this.page.goto(`${this.baseUrl}${this.loginUrl}`); + await this.page.fill('#user_login', this.username); + await this.page.fill('#user_pass', this.password); + await this.page.click('#wp-submit'); + await this.page.waitForLoadState('networkidle'); - constructor(page: Page) { - this.page = page; - this.verbosity = VerbosityController.getInstance(); + // Verify login was successful + const url = this.page.url(); + if (!url.includes(this.dashboardUrl)) { + throw new Error(`Login failed. Expected URL to contain ${this.dashboardUrl}, but got ${url}`); + } + } + + /** + * Set up a test event with attendees for certificate testing + * @returns The name of the created test event + */ + async setupCertificateTestEvent(): Promise { + // Generate a unique event name with timestamp + const timestamp = new Date().getTime(); + this.testEventName = `Certificate Test Event ${timestamp}`; + + // Navigate to create event page + await this.page.goto(`${this.baseUrl}/community-events/`); + await this.page.waitForLoadState('networkidle'); + + // Populate the event form with test data + await this.page.fill('#post_title', this.testEventName); + + // Set event date (today + 1 day) + const tomorrow = new Date(); + tomorrow.setDate(tomorrow.getDate() + 1); + const dateString = tomorrow.toISOString().split('T')[0]; // Format as YYYY-MM-DD + + // Fill event date fields + await this.page.fill('input[name="EventStartDate"]', dateString); + await this.page.fill('input[name="EventEndDate"]', dateString); + + // Set content using TinyMCE if available + const tinyMceFrame = this.page.frameLocator('.mce-edit-area iframe'); + if (await tinyMceFrame.count() > 0) { + await tinyMceFrame.locator('body').fill('This is a test event for certificate generation testing.'); + } else { + // Fallback to regular textarea + await this.page.fill('#post_content', 'This is a test event for certificate generation testing.'); } - /** - * Login as the test trainer - */ - async loginAsTrainer(): Promise { - this.verbosity.log('Logging in as test_trainer'); - await this.page.goto(Config.loginUrl); - await this.page.fill('#user_login', Config.testTrainer.username); - await this.page.fill('#user_pass', Config.testTrainer.password); + // Fill other required fields + await this.page.fill('#EventVenueName', 'Test Venue'); + await this.page.fill('#EventVenueAddress', '123 Test Street'); + await this.page.fill('#EventVenueCity', 'Test City'); + await this.page.fill('#EventVenueCountry', 'United States'); + await this.page.fill('#EventVenueZip', '12345'); + + // Create ticket + await this.page.click('text=Tickets'); + await this.page.click('text=Add a new ticket'); + await this.page.fill('.tribe-ticket-field-name input', 'General Admission'); + await this.page.fill('.tribe-ticket-field-price input', '10'); + await this.page.fill('.tribe-ticket-field-capacity input', '100'); + + // Save event + await this.page.click('#community-events-submit'); + await this.page.waitForLoadState('networkidle'); + + // Extract the event ID from the URL or response + const url = this.page.url(); + const match = url.match(/post=(\d+)/); + if (match && match[1]) { + this.testEventId = match[1]; + console.log(`Created test event with ID: ${this.testEventId}`); + } else { + console.error('Failed to extract event ID from URL:', url); + } + + // Create test attendees via API for efficiency + await this.createTestAttendees(); + + return this.testEventName; + } + + /** + * Create test attendees for the event + * Creates 5 attendees: 3 checked-in, 2 not checked-in + */ + private async createTestAttendees(): Promise { + if (!this.testEventId) { + throw new Error('Cannot create attendees: No test event ID available'); + } + + // Login to admin to create test attendees + await this.page.goto(`${this.baseUrl}${this.adminUrl}`); + await this.page.fill('#user_login', this.username); + await this.page.fill('#user_pass', this.password); + await this.page.click('#wp-submit'); + await this.page.waitForLoadState('networkidle'); + + // Create 5 test attendees + const attendeeData = [ + { name: 'Test Attendee 1', email: 'test1@example.com', checkedIn: true }, + { name: 'Test Attendee 2', email: 'test2@example.com', checkedIn: true }, + { name: 'Test Attendee 3', email: 'test3@example.com', checkedIn: true }, + { name: 'Test Attendee 4', email: 'test4@example.com', checkedIn: false }, + { name: 'Test Attendee 5', email: 'test5@example.com', checkedIn: false } + ]; + + // Navigate to Event Tickets > Attendees page + await this.page.goto(`${this.baseUrl}${this.adminUrl}edit.php?post_type=tribe_events&page=tickets-attendees&event_id=${this.testEventId}`); + await this.page.waitForLoadState('networkidle'); + + for (const attendee of attendeeData) { + // Add attendee manually + await this.page.click('a:text("Add a new attendee")'); + await this.page.waitForSelector('form.tribe-attendees-page-add-attendee-form'); + + // Fill attendee details + await this.page.selectOption('select[name="ticket_id"]', { index: 0 }); // Select the first ticket + await this.page.fill('input[name="attendee[full_name]"]', attendee.name); + await this.page.fill('input[name="attendee[email]"]', attendee.email); + + // Submit the form + await this.page.click('button:text("Add")'); + await this.page.waitForLoadState('networkidle'); + + // Check in the attendee if needed + if (attendee.checkedIn) { + // Find the attendee row + const attendeeRow = this.page.locator(`tr:has-text("${attendee.email}")`).first(); + + // Click the check-in button + await attendeeRow.locator('.check-in').click(); + await this.page.waitForLoadState('networkidle'); + } + } + + console.log(`Created ${attendeeData.length} test attendees for event ${this.testEventId}`); + } + + /** + * Clean up test data + */ + async cleanup(): Promise { + if (!this.testEventId) { + console.log('No test data to clean up'); + return; + } + + try { + // Login to admin if needed + if (!this.page.url().includes(this.adminUrl)) { + await this.page.goto(`${this.baseUrl}${this.adminUrl}`); + await this.page.fill('#user_login', this.username); + await this.page.fill('#user_pass', this.password); await this.page.click('#wp-submit'); await this.page.waitForLoadState('networkidle'); + } + + // Move the event to trash + await this.page.goto(`${this.baseUrl}${this.adminUrl}post.php?post=${this.testEventId}&action=trash`); + await this.page.waitForLoadState('networkidle'); + + console.log(`Cleaned up test event with ID: ${this.testEventId}`); + this.testEventId = null; + this.testEventName = null; + } catch (error) { + console.error('Error during test data cleanup:', error); } - - /** - * Creates a test event with a specified name and future date - */ - async createTestEvent(eventName: string): Promise { - this.verbosity.log(`Creating test event: ${eventName}`); - - await this.page.goto(Config.createEventUrl); - await this.page.waitForLoadState('networkidle'); - - // Fill in event details - await this.page.fill('#post_title, input[name="post_title"]', eventName); - - // Add description - const newEventFrame = this.page.frameLocator('iframe[id*="_ifr"]'); - const newEventBody = newEventFrame.locator('body'); - await newEventBody.fill(`This is a test event created for certificate testing: ${eventName}`); - - // Set dates (30 days from now) - const futureDate = new Date(); - futureDate.setDate(futureDate.getDate() + 30); - const dateString = `${(futureDate.getMonth() + 1).toString().padStart(2, '0')}/${futureDate.getDate().toString().padStart(2, '0')}/${futureDate.getFullYear()}`; - - await this.page.fill('input[name="EventStartDate"]', dateString); - await this.page.fill('input[name="EventStartTime"]', '10:00 AM'); - await this.page.fill('input[name="EventEndDate"]', dateString); - await this.page.fill('input[name="EventEndTime"]', '04:00 PM'); - - // Add a ticket for $100 - await this.addTicket('Certificate Test Ticket', '100'); - - // Submit the event - const submitButton = this.page.locator('input[value="Submit Event"], button:has-text("Submit Event")'); - await submitButton.click(); - await this.page.waitForLoadState('networkidle'); - - // Get the event ID from the URL if possible - const url = this.page.url(); - const match = url.match(/post=(\d+)/); - if (match && match[1]) { - return match[1]; - } - - return null; - } - - /** - * Adds a ticket to an event - */ - private async addTicket(ticketName: string, price: string): Promise { - // Look for ticket creation UI elements - const ticketNameField = this.page.locator('#tribe-tickets-editor-tickets-name'); - const ticketPriceField = this.page.locator('#tribe-tickets-editor-tickets-price'); - const addTicketButton = this.page.locator('button:has-text("Add Ticket")'); - - // If the ticket UI isn't visible, try to open it - if (!await ticketNameField.isVisible()) { - const addTicketsSection = this.page.locator('a:has-text("Add Tickets")'); - if (await addTicketsSection.isVisible()) { - await addTicketsSection.click(); - await this.page.waitForTimeout(Config.shortWait); - } - } - - // Fill in ticket details - if (await ticketNameField.isVisible()) { - await ticketNameField.fill(ticketName); - await ticketPriceField.fill(price); - await addTicketButton.click(); - await this.page.waitForTimeout(Config.mediumWait); - } else { - this.verbosity.log('Warning: Ticket creation UI not found'); - } - } - - /** - * Simulates attendee registrations for an event - * Creates a mix of checked-in and non-checked-in attendees - */ - async createTestAttendees(eventId: string, count: number = 5): Promise { - this.verbosity.log(`Creating ${count} test attendees for event ${eventId}`); - - // First, navigate to the admin area to access the event - await this.page.goto(`${Config.stagingUrl}/wp-admin/post.php?post=${eventId}&action=edit`); - - // Check if we're on the login page and log in if needed - if (this.page.url().includes('wp-login.php')) { - await this.page.fill('#user_login', Config.testTrainer.username); - await this.page.fill('#user_pass', Config.testTrainer.password); - await this.page.click('#wp-submit'); - await this.page.waitForLoadState('networkidle'); - } - - // Navigate to the attendees tab - this is implementation-specific and may need adjustment - const attendeesTab = this.page.locator('a:has-text("Attendees")'); - if (await attendeesTab.isVisible()) { - await attendeesTab.click(); - await this.page.waitForTimeout(Config.shortWait); - } - - // Look for "Add New" button - const addNewButton = this.page.locator('a:has-text("Add attendee"), button:has-text("Add attendee")'); - - for (let i = 1; i <= count; i++) { - // Click "Add New" for each attendee - if (await addNewButton.isVisible()) { - await addNewButton.click(); - await this.page.waitForTimeout(Config.shortWait); - - // Fill in attendee info - await this.page.fill('input[name="attendee[email]"]', `test.attendee${i}@example.com`); - await this.page.fill('input[name="attendee[full_name]"]', `Test Attendee ${i}`); - - // Mark every other attendee as checked in - if (i % 2 === 0) { - const checkinCheckbox = this.page.locator('input[name="attendee[check_in]"]'); - if (await checkinCheckbox.isVisible()) { - await checkinCheckbox.check(); - } - } - - // Save the attendee - const saveButton = this.page.locator('button:has-text("Add")'); - await saveButton.click(); - await this.page.waitForTimeout(Config.shortWait); - } else { - this.verbosity.log('Warning: Add attendee button not found'); - break; - } - } - } - - /** - * Creates a complete test event with attendees for certificate testing - */ - async setupCertificateTestEvent(): Promise { - // Create a uniquely named event - const timestamp = new Date().getTime(); - const eventName = `Certificate Test Event ${timestamp}`; - - // Create the event - const eventId = await this.createTestEvent(eventName); - if (!eventId) { - this.verbosity.log('Failed to create test event'); - return null; - } - - // Add test attendees (mix of checked-in and non-checked-in) - await this.createTestAttendees(eventId, 6); - - return eventName; - } + } } \ No newline at end of file