From 8dde8090625501cae305e773a29d2cab50d841b5 Mon Sep 17 00:00:00 2001 From: bengizmo Date: Wed, 21 May 2025 09:45:00 -0300 Subject: [PATCH] test: Optimize E2E testing infrastructure for certificates - Add dedicated AttendeeFilterPage for isolating filtering functionality - Create optimized certificate test focusing on attendee search - Document testing strategy and best practices - Add script to analyze and improve Playwright configuration - Create optimized Playwright configuration template - Resolve test stability issues with simplified approach - Improve test isolation and reliability for certificates --- wordpress-dev/bin/optimize-e2e-tests.sh | 242 ++++++++++++++++++ wordpress-dev/tests/e2e/TESTING-STRATEGY.md | 165 ++++++++++++ .../tests/e2e/optimized-certificate-tests.ts | 213 +++++++++++++++ .../tests/e2e/pages/AttendeeFilterPage.ts | 203 +++++++++++++++ 4 files changed, 823 insertions(+) create mode 100755 wordpress-dev/bin/optimize-e2e-tests.sh create mode 100644 wordpress-dev/tests/e2e/TESTING-STRATEGY.md create mode 100644 wordpress-dev/tests/e2e/optimized-certificate-tests.ts create mode 100644 wordpress-dev/tests/e2e/pages/AttendeeFilterPage.ts diff --git a/wordpress-dev/bin/optimize-e2e-tests.sh b/wordpress-dev/bin/optimize-e2e-tests.sh new file mode 100755 index 00000000..a373b253 --- /dev/null +++ b/wordpress-dev/bin/optimize-e2e-tests.sh @@ -0,0 +1,242 @@ +#!/bin/bash + +# E2E Testing Suite Optimization Script +# This script helps troubleshoot and optimize the Playwright E2E testing configuration + +echo "===== HVAC Community Events E2E Testing Suite Optimization =====" +echo + +# Step 1: Check for multiple Playwright installations +echo "Step 1: Checking for duplicate Playwright installations..." +PLAYWRIGHT_GLOBAL=$(npm list -g playwright | grep playwright | wc -l) +PLAYWRIGHT_LOCAL_DEP=$(npm list playwright | grep playwright | wc -l) +PLAYWRIGHT_TEST_DEP=$(npm list @playwright/test | grep @playwright/test | wc -l) + +echo "Playwright global installations: $PLAYWRIGHT_GLOBAL" +echo "Playwright local installations: $PLAYWRIGHT_LOCAL_DEP" +echo "@playwright/test installations: $PLAYWRIGHT_TEST_DEP" + +if [ $PLAYWRIGHT_LOCAL_DEP -gt 1 ] || [ $PLAYWRIGHT_TEST_DEP -gt 1 ]; then + echo "⚠️ Detected multiple Playwright instances. This may cause conflicts." + echo " Consider fixing with: npm uninstall playwright && npm install playwright@latest" +else + echo "✅ Playwright installation looks good." +fi + +echo + +# Step 2: Check for proper directory structure +echo "Step 2: Verifying E2E test directory structure..." +UTILS_DIR="./tests/e2e/utils" +PAGES_DIR="./tests/e2e/pages" +CONFIG_FILE="./playwright.config.ts" + +if [ -d "$UTILS_DIR" ] && [ -d "$PAGES_DIR" ] && [ -f "$CONFIG_FILE" ]; then + echo "✅ Basic directory structure is in place." +else + echo "❌ Missing critical directories or files:" + [ ! -d "$UTILS_DIR" ] && echo " - Missing $UTILS_DIR" + [ ! -d "$PAGES_DIR" ] && echo " - Missing $PAGES_DIR" + [ ! -f "$CONFIG_FILE" ] && echo " - Missing $CONFIG_FILE" +fi + +echo + +# Step 3: Check dependencies in package.json +echo "Step 3: Checking required dependencies..." +MISSING_DEPS=0 + +check_dependency() { + local DEP=$1 + local COUNT=$(npm list $DEP 2>/dev/null | grep $DEP | wc -l) + if [ $COUNT -eq 0 ]; then + echo "❌ Missing dependency: $DEP" + MISSING_DEPS=$((MISSING_DEPS + 1)) + else + local VERSION=$(npm list $DEP | grep $DEP) + echo "✅ Found $VERSION" + fi +} + +check_dependency "@playwright/test" +check_dependency "playwright" +check_dependency "dotenv" +check_dependency "typescript" + +if [ $MISSING_DEPS -gt 0 ]; then + echo + echo "⚠️ Some dependencies are missing. Consider running:" + echo " npm install @playwright/test playwright dotenv typescript --save-dev" +else + echo "✅ All required dependencies are installed." +fi + +echo + +# Step 4: Check Playwright configuration +echo "Step 4: Validating Playwright configuration..." +if [ -f "$CONFIG_FILE" ]; then + CONFIG_ISSUES=0 + + # Check for testDir setting + if grep -q "testDir:" "$CONFIG_FILE"; then + echo "✅ testDir setting found in configuration" + else + echo "❌ Missing 'testDir' setting in Playwright config" + CONFIG_ISSUES=$((CONFIG_ISSUES + 1)) + fi + + # Check for projects setting + if grep -q "projects:" "$CONFIG_FILE"; then + echo "✅ projects setting found in configuration" + else + echo "❌ Missing 'projects' setting in Playwright config" + CONFIG_ISSUES=$((CONFIG_ISSUES + 1)) + fi + + # Check for use setting + if grep -q "use:" "$CONFIG_FILE"; then + echo "✅ use setting found in configuration" + else + echo "❌ Missing 'use' setting in Playwright config" + CONFIG_ISSUES=$((CONFIG_ISSUES + 1)) + fi + + if [ $CONFIG_ISSUES -gt 0 ]; then + echo + echo "⚠️ There are issues with your Playwright configuration." + echo " Review the playwright.config.ts file and fix the noted issues." + else + echo "✅ Basic Playwright configuration looks good." + fi +else + echo "❌ Playwright configuration file not found." +fi + +echo + +# Step 5: Test an optimized certificate test +echo "Step 5: Setting up and testing optimized certificate test..." + +if [ -f "./tests/e2e/optimized-certificate-tests.ts" ]; then + echo "✅ Optimized certificate test found." + echo " Running npx playwright test tests/e2e/optimized-certificate-tests.ts --headed --workers=1" + + # Uncomment to actually run the test + # npx playwright test tests/e2e/optimized-certificate-tests.ts --headed --workers=1 + + echo "⚠️ Test execution commented out. Uncomment in script to run actual test." +else + echo "❌ Optimized certificate test not found." + echo " Make sure optimized-certificate-tests.ts exists in tests/e2e directory." +fi + +echo + +# Step 6: Create improved configuration +echo "Step 6: Creating improved Playwright configuration..." + +cat > playwright.config.optimized.ts << 'EOF' +import { defineConfig, devices } from '@playwright/test'; +import path from 'path'; +import dotenv from 'dotenv'; + +// Load environment variables +dotenv.config(); + +// Base URL for all tests +const BASE_URL = process.env.BASE_URL || 'https://wordpress-974670-5399585.cloudwaysapps.com'; + +/** + * Optimized Playwright configuration + * - Simplified configuration focusing on reliability + * - Reduced dependencies on external utilities + * - Streamlined reporter configuration + */ +export default defineConfig({ + // Basic test configuration + testDir: './tests/e2e', + timeout: 45000, // Increased timeout for WordPress operations + fullyParallel: false, // Disable parallel tests for WordPress to prevent conflicts + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 1, // Add one retry even in development + workers: 1, // Limit to one worker for WordPress tests + + // Simple reporter configuration + reporter: [ + ['list'], // More informative than dot reporter + ['html', { open: 'never' }], // HTML report for better visualization + ], + + // Global test configuration + use: { + baseURL: BASE_URL, + trace: 'retain-on-failure', + screenshot: 'only-on-failure', + video: 'retain-on-failure', + + // Browser context options + viewport: { width: 1280, height: 720 }, + ignoreHTTPSErrors: true, // Needed for many WordPress staging sites + + // Actionability configuration + actionTimeout: 15000, // Increase timeout for WordPress slow UI + }, + + // Test projects - minimal configuration for stability + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + + // Global setup/teardown - minimal stubs + globalSetup: './tests/e2e/global-setup.js', + globalTeardown: './tests/e2e/global-teardown.js', +}); +EOF + +echo "✅ Created optimized Playwright configuration: playwright.config.optimized.ts" + +echo + +# Step 7: Recommendations +echo "Step 7: Recommendations for E2E test suite optimization" +echo +echo "Based on the analysis, here are key recommendations:" +echo +echo "1. Package and Dependency Management:" +echo " - ✅ Use a single version of Playwright and @playwright/test" +echo " - ✅ Pin dependency versions in package.json" +echo " - ✅ Consider using npm-check-updates to safely update dependencies" +echo +echo "2. Configuration Structure:" +echo " - ✅ Use the optimized configuration file created by this script" +echo " - ✅ Reduce dependencies on complex utility functions" +echo " - ✅ Simplify test setup with direct configuration" +echo +echo "3. Test Organization:" +echo " - ✅ Focus on writing independent, self-contained tests" +echo " - ✅ Use the Page Object Model pattern consistently" +echo " - ✅ Keep selectors centralized in Page classes" +echo +echo "4. Troubleshooting Common Issues:" +echo " - ✅ Test describe() and use() conflicts in imports" +echo " - ✅ Check for multiple versions of Playwright" +echo " - ✅ Use --headed and --slow flags for debugging" +echo " - ✅ Add page.pause() for interactive debugging" +echo +echo "5. Certificate Testing Specific:" +echo " - ✅ Use the optimized certificate test as a template" +echo " - ✅ Focus on testing one feature at a time" +echo " - ✅ Add proper screenshots for validation" +echo +echo "To apply the optimized configuration:" +echo " cp playwright.config.optimized.ts playwright.config.ts" +echo +echo "To run certificate tests with the optimized config:" +echo " npx playwright test tests/e2e/optimized-certificate-tests.ts --config=playwright.config.optimized.ts --headed" + +echo +echo "===== E2E TESTING SUITE OPTIMIZATION COMPLETE =====" \ No newline at end of file diff --git a/wordpress-dev/tests/e2e/TESTING-STRATEGY.md b/wordpress-dev/tests/e2e/TESTING-STRATEGY.md new file mode 100644 index 00000000..7682592f --- /dev/null +++ b/wordpress-dev/tests/e2e/TESTING-STRATEGY.md @@ -0,0 +1,165 @@ +# E2E Testing Strategy for HVAC Community Events + +This document outlines the testing strategy and implementation details for the E2E test suite, with a focus on certificate functionality testing. + +## Testing Architecture + +### 1. Page Object Model (POM) + +Our tests follow the Page Object Model pattern, which abstracts UI interactions into page-specific classes: + +- **BasePage.ts**: Base class with common methods for all pages +- **LoginPage.ts**: Handles user authentication +- **DashboardPage.ts**: Interactions with the main dashboard +- **CertificatePage.ts**: Certificate generation and management functionality +- Additional page objects for other key pages + +This approach provides: +- Reusable code for common UI interactions +- Centralized selectors for easier maintenance +- Better abstraction of implementation details + +### 2. Test Organization + +Tests are organized by feature area: + +- **certificate-generation.test.ts**: Tests for generating certificates +- **certificate-management.test.ts**: Tests for certificate management operations +- **optimized-certificate-tests.ts**: Streamlined tests focused on specific features + +### 3. Configuration and Utilities + +- **Config.ts**: Centralized configuration parameters +- **VerbosityController.ts**: Controls logging and screenshot behavior +- **playwright.config.ts**: Playwright test runner configuration + +## Testing Challenges and Solutions + +### Multiple Playwright Versions + +**Problem**: Multiple versions of Playwright and @playwright/test causing conflicts + +**Solution**: +- Pin dependency versions in package.json +- Update to the latest stable version +- Remove duplicate dependencies + +### Configuration Issues + +**Problem**: Complex configuration with dependencies that may not be properly initialized + +**Solution**: +- Simplified configuration focusing on reliability +- Reduced dependencies on external utilities +- Clear separation of configuration and test code + +### Test Independence + +**Problem**: Tests depending on global state or other tests + +**Solution**: +- Self-contained tests that set up their own data +- Explicit cleanup of test data +- Clear test boundaries and responsibilities + +## Certificate Testing Strategy + +### 1. Data Setup + +For certificate testing, we create: +- Test events with known properties +- Test attendees with varied properties +- Checked-in status for some attendees +- Test certificates with different states (emailed, revoked) + +### 2. Feature Testing + +Our certificate tests verify: +- Certificate generation for checked-in attendees +- Certificate viewing functionality +- Certificate emailing operations +- Certificate revocation operations +- Certificate filtering (by event, attendee, status) + +### 3. Attendee Search Testing + +The attendee search feature is tested with multiple patterns: +- Full name search (e.g., "Ben Tester") +- Partial name search (e.g., "Ben" or "Smith") +- Full email search (e.g., "ben@tealmaker.com") +- Partial email search (e.g., "@gmail.com") +- Case-insensitive matching + +## Best Practices + +1. **Test Independence**: + - Each test should create its own test data + - Tests should not depend on the state from other tests + - Clean up test data when possible + +2. **Explicit Waits**: + - Use explicit waits rather than fixed timeouts + - Wait for specific elements/conditions, not fixed times + - Use appropriate timeouts for WordPress's slower operations + +3. **Error Handling**: + - Implement proper error handling in tests + - Use try/catch blocks for potentially unstable operations + - Take screenshots on failures for easier debugging + +4. **Selector Stability**: + - Use CSS selectors that are less likely to change + - Prefer attribute selectors over positional selectors + - Centralize selectors in page objects for easier maintenance + +5. **Descriptive Naming**: + - Use clear, descriptive test names + - Name page object methods according to user actions + - Add comments for complex operations + +## Optimized Testing Workflow + +1. **Initial Setup**: + ```bash + # Install dependencies + npm install + + # Install Playwright browsers + npx playwright install + ``` + +2. **Running Certificate Tests**: + ```bash + # Run optimized certificate tests + npx playwright test tests/e2e/optimized-certificate-tests.ts --headed + + # Run with debugging + npx playwright test tests/e2e/optimized-certificate-tests.ts --headed --debug + ``` + +3. **Visual Verification**: + - Screenshots are taken at key points during test execution + - Visual verification can be done by examining the screenshots + - The HTML report provides a visual overview of test results + +## Future Improvements + +1. **Data Generation**: + - Create a more robust test data generator + - Support for creating varied test scenarios + - Better cleanup of test data + +2. **Test Coverage**: + - Expand tests to cover more edge cases + - Add tests for error handling + - Include mobile viewport testing + +3. **Performance Optimization**: + - Profile and optimize test execution time + - Reduce unnecessary waits and operations + - Improve parallel test execution where possible + +4. **Reporting**: + - Enhanced test reporting with more details + - Integration with CI/CD systems + - Slack/email notifications for test failures \ No newline at end of file diff --git a/wordpress-dev/tests/e2e/optimized-certificate-tests.ts b/wordpress-dev/tests/e2e/optimized-certificate-tests.ts new file mode 100644 index 00000000..0c03cf0b --- /dev/null +++ b/wordpress-dev/tests/e2e/optimized-certificate-tests.ts @@ -0,0 +1,213 @@ +import { test, expect } from '@playwright/test'; +import { Config } from './utils/Config'; +import { BasePage } from './pages/BasePage'; +import { LoginPage } from './pages/LoginPage'; +import { DashboardPage } from './pages/DashboardPage'; + +/** + * Optimized Certificate Tests + * + * This file contains E2E tests for the certificate system with a focus + * on verifying the attendee search filter functionality. + */ + +// Simple CertificatePage implementation that doesn't depend on external files +class CertificatePage extends BasePage { + // Certificate Reports page selectors + private readonly certificateTitle = 'h1:has-text("Certificate Reports")'; + private readonly certificateTable = '.hvac-certificate-table'; + private readonly eventFilter = '#filter_event'; + private readonly attendeeSearch = '#search_attendee'; + private readonly revokedFilter = '#filter_revoked'; + private readonly filterSubmit = 'button[type="submit"]'; + private readonly clearFilters = 'a.hvac-clear-filters'; + private readonly totalCountText = '.hvac-total-count'; + private readonly pagination = '.hvac-pagination'; + private readonly nextPage = '.hvac-pagination a.next'; + private readonly previousPage = '.hvac-pagination a.prev'; + private readonly certificateRows = '.hvac-certificate-table tbody tr'; + private readonly certificateEventCells = 'td.certificate-event'; + private readonly certificateAttendeeCells = 'td.certificate-attendee'; + + async navigateToCertificateReports(): Promise { + await this.page.goto(Config.certificateReportsUrl); + await this.page.waitForLoadState('networkidle'); + } + + async isCertificateReportsPageVisible(): Promise { + return this.page.locator(this.certificateTitle).isVisible(); + } + + async clearAllFilters(): Promise { + if (await this.page.locator(this.clearFilters).isVisible()) { + await this.page.click(this.clearFilters); + await this.page.waitForTimeout(1000); + } + } + + async filterByEvent(eventId: string): Promise { + await this.clearAllFilters(); + await this.page.selectOption(this.eventFilter, eventId); + await this.page.click(this.filterSubmit); + await this.page.waitForSelector(this.certificateTable); + } + + async filterByAttendee(searchTerm: string): Promise { + await this.clearAllFilters(); + await this.page.fill(this.attendeeSearch, searchTerm); + await this.page.click(this.filterSubmit); + await this.page.waitForSelector(this.certificateTable); + } + + async filterByRevoked(revoked: boolean): Promise { + await this.clearAllFilters(); + await this.page.selectOption(this.revokedFilter, revoked ? '1' : '0'); + await this.page.click(this.filterSubmit); + await this.page.waitForSelector(this.certificateTable); + } + + async getCertificateCount(): Promise { + const countText = await this.page.locator(this.totalCountText).textContent(); + const match = countText?.match(/of (\d+) certificates/); + return match ? parseInt(match[1]) : 0; + } + + async getEventCellTexts(): Promise { + return this.page.locator(this.certificateEventCells).allTextContents(); + } + + async getAttendeeCellTexts(): Promise { + return this.page.locator(this.certificateAttendeeCells).allTextContents(); + } + + async hasPagination(): Promise { + return this.page.locator(this.pagination).isVisible(); + } + + async clickNextPage(): Promise { + await this.page.click(this.nextPage); + await this.page.waitForSelector(this.certificateTable); + } +} + +// Simple test that verifies the certificate data and attendee search functionality +test('Certificate data and attendee search verification', async ({ page }) => { + // Create page objects + const loginPage = new LoginPage(page); + const dashboardPage = new DashboardPage(page); + const certificatePage = new CertificatePage(page); + + // Test event IDs + const testEvents = [ + { id: '5641', name: 'HVAC System Design Fundamentals' }, + { id: '5668', name: 'Advanced Refrigeration Technology' }, + { id: '5688', name: 'Building Automation Systems Workshop' } + ]; + + // Step 1: Login + console.log('Step 1: Logging in as test trainer'); + await loginPage.navigate(); + await loginPage.login(Config.testTrainer.username, Config.testTrainer.password); + + // Verify successful login + const isLoggedIn = await dashboardPage.isVisible('h1:has-text("Dashboard")'); + expect(isLoggedIn).toBeTruthy(); + + // Step 2: Navigate to certificate reports + console.log('Step 2: Navigating to certificate reports page'); + await certificatePage.navigateToCertificateReports(); + + // Verify certificate page loaded + const certificatePageVisible = await certificatePage.isCertificateReportsPageVisible(); + expect(certificatePageVisible).toBeTruthy(); + + // Step 3: Check total certificate count + console.log('Step 3: Checking total certificate count'); + const totalCount = await certificatePage.getCertificateCount(); + console.log(`Total certificates: ${totalCount}`); + expect(totalCount).toBeGreaterThanOrEqual(47); // We expect at least 47 certificates from our test data + + // Step 4: Test event filtering + console.log('Step 4: Testing event filtering'); + for (const event of testEvents) { + await certificatePage.filterByEvent(event.id); + const eventCount = await certificatePage.getCertificateCount(); + console.log(`Event "${event.name}" has ${eventCount} certificates`); + + // Verify all event cells contain the correct name + const eventCells = await certificatePage.getEventCellTexts(); + for (const cellText of eventCells) { + expect(cellText).toContain(event.name); + } + + // Take screenshot for verification + await page.screenshot({ path: `event-filter-${event.id}.png` }); + } + + // Step 5: Test attendee name filtering + console.log('Step 5: Testing attendee name filtering'); + await certificatePage.filterByAttendee('Ben Tester'); + const benCount = await certificatePage.getCertificateCount(); + console.log(`Attendee "Ben Tester" has ${benCount} certificates`); + + // Verify attendee cells contain Ben Tester + const benCells = await certificatePage.getAttendeeCellTexts(); + for (const cellText of benCells) { + expect(cellText.toLowerCase()).toContain('ben tester'); + } + + // Take screenshot for verification + await page.screenshot({ path: 'attendee-name-filter.png' }); + + // Step 6: Test attendee email filtering + console.log('Step 6: Testing attendee email filtering'); + await certificatePage.filterByAttendee('ben@tealmaker.com'); + const emailCount = await certificatePage.getCertificateCount(); + console.log(`Email "ben@tealmaker.com" has ${emailCount} certificates`); + + // Take screenshot for verification + await page.screenshot({ path: 'attendee-email-filter.png' }); + + // Step 7: Test partial name filtering + console.log('Step 7: Testing partial name filtering'); + await certificatePage.filterByAttendee('Smith'); + const smithCount = await certificatePage.getCertificateCount(); + console.log(`Partial name "Smith" has ${smithCount} certificates`); + + // Take screenshot for verification + await page.screenshot({ path: 'partial-name-filter.png' }); + + // Step 8: Test partial email filtering + console.log('Step 8: Testing partial email filtering'); + await certificatePage.filterByAttendee('@gmail'); + const gmailCount = await certificatePage.getCertificateCount(); + console.log(`Partial email "@gmail" has ${gmailCount} certificates`); + + // Take screenshot for verification + await page.screenshot({ path: 'partial-email-filter.png' }); + + // Step 9: Test revoked filtering + console.log('Step 9: Testing revocation status filtering'); + await certificatePage.filterByRevoked(true); + const revokedCount = await certificatePage.getCertificateCount(); + console.log(`Found ${revokedCount} revoked certificates`); + + // Take screenshot for verification + await page.screenshot({ path: 'revoked-filter.png' }); + + // Step 10: Test pagination + console.log('Step 10: Testing pagination if applicable'); + await certificatePage.clearAllFilters(); + const hasPagination = await certificatePage.hasPagination(); + + if (hasPagination) { + console.log('Pagination is available, testing navigation'); + await certificatePage.clickNextPage(); + // Take screenshot of second page + await page.screenshot({ path: 'pagination-page-2.png' }); + } else { + console.log('No pagination available (not enough certificates)'); + } + + console.log('Certificate verification tests completed'); +}); \ No newline at end of file diff --git a/wordpress-dev/tests/e2e/pages/AttendeeFilterPage.ts b/wordpress-dev/tests/e2e/pages/AttendeeFilterPage.ts new file mode 100644 index 00000000..cf98bca1 --- /dev/null +++ b/wordpress-dev/tests/e2e/pages/AttendeeFilterPage.ts @@ -0,0 +1,203 @@ +import { Page, expect } from '@playwright/test'; +import { BasePage } from './BasePage'; +import { Config } from '../utils/Config'; + +/** + * AttendeeFilterPage + * + * A focused page object for handling attendee filtering functionality + * in the certificate reports page. This is designed as a minimal, + * reusable component that can be easily incorporated into existing tests. + */ +export class AttendeeFilterPage extends BasePage { + // Selectors for certificate filters + private readonly pageTitle = 'h1:has-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 filterSubmitButton = 'button[type="submit"]'; + private readonly clearFiltersLink = 'a.hvac-clear-filters'; + private readonly certificateTable = '.hvac-certificate-table'; + private readonly certificateCount = '.hvac-total-count'; + private readonly noResultsMessage = '.hvac-no-certificates'; + private readonly pagination = '.hvac-pagination'; + + // Selectors for certificate table columns + private readonly eventColumn = 'td.certificate-event'; + private readonly attendeeColumn = 'td.certificate-attendee'; + private readonly emailColumn = 'td.certificate-email'; + private readonly statusColumn = 'td.certificate-status'; + + constructor(page: Page) { + super(page); + } + + /** + * Navigate to the certificate reports page + */ + async navigate(): Promise { + await this.page.goto(Config.certificateReportsUrl); + await this.page.waitForLoadState('networkidle'); + } + + /** + * Check if the certificate reports page is visible + */ + async isPageVisible(): Promise { + return this.page.locator(this.pageTitle).isVisible(); + } + + /** + * Clear all existing filters + */ + async clearFilters(): Promise { + const clearLink = this.page.locator(this.clearFiltersLink); + if (await clearLink.isVisible()) { + await clearLink.click(); + await this.page.waitForTimeout(Config.shortWait); + await this.page.waitForSelector(this.certificateTable); + } + } + + /** + * Filter certificates by event + * @param eventId The event ID to filter by + */ + async filterByEvent(eventId: string): Promise { + await this.clearFilters(); + await this.page.selectOption(this.eventFilterSelect, eventId); + await this.page.click(this.filterSubmitButton); + await this.page.waitForSelector(this.certificateTable); + } + + /** + * Filter certificates by attendee name or email + * @param searchTerm The attendee name or email to search for + */ + async filterByAttendee(searchTerm: string): Promise { + await this.clearFilters(); + await this.page.fill(this.attendeeSearchInput, searchTerm); + await this.page.click(this.filterSubmitButton); + await this.page.waitForSelector(this.certificateTable); + } + + /** + * Filter certificates by revocation status + * @param revoked Whether to show revoked (true) or non-revoked (false) certificates + */ + async filterByRevoked(revoked: boolean): Promise { + await this.clearFilters(); + await this.page.selectOption(this.revokedFilterSelect, revoked ? '1' : '0'); + await this.page.click(this.filterSubmitButton); + await this.page.waitForSelector(this.certificateTable); + } + + /** + * Apply multiple filters at once + * @param filters An object containing filter criteria + */ + async applyFilters(filters: { + eventId?: string; + attendeeSearch?: string; + revoked?: boolean; + }): Promise { + await this.clearFilters(); + + if (filters.eventId) { + await this.page.selectOption(this.eventFilterSelect, filters.eventId); + } + + if (filters.attendeeSearch) { + await this.page.fill(this.attendeeSearchInput, filters.attendeeSearch); + } + + if (filters.revoked !== undefined) { + await this.page.selectOption(this.revokedFilterSelect, filters.revoked ? '1' : '0'); + } + + await this.page.click(this.filterSubmitButton); + await this.page.waitForSelector(this.certificateTable); + } + + /** + * Get the total number of certificates matching the current filters + */ + async getCertificateCount(): Promise { + // Check for no results message + if (await this.page.locator(this.noResultsMessage).isVisible()) { + return 0; + } + + // Extract count from text like "Showing 1-20 of 54 certificates" + const countText = await this.page.locator(this.certificateCount).textContent(); + const match = countText?.match(/of (\d+) certificates/); + return match ? parseInt(match[1]) : 0; + } + + /** + * Check if any certificates match the current filters + */ + async hasCertificates(): Promise { + return !(await this.page.locator(this.noResultsMessage).isVisible()); + } + + /** + * Get all event names from the current certificate table + */ + async getEventNames(): Promise { + return this.page.locator(this.eventColumn).allTextContents(); + } + + /** + * Get all attendee names from the current certificate table + */ + async getAttendeeNames(): Promise { + return this.page.locator(this.attendeeColumn).allTextContents(); + } + + /** + * Get all email addresses from the current certificate table + */ + async getEmails(): Promise { + return this.page.locator(this.emailColumn).allTextContents(); + } + + /** + * Get all status values from the current certificate table + */ + async getStatuses(): Promise { + return this.page.locator(this.statusColumn).allTextContents(); + } + + /** + * Check if pagination is available + */ + async hasPagination(): Promise { + return this.page.locator(this.pagination).isVisible(); + } + + /** + * Navigate to the next page of results + */ + async goToNextPage(): Promise { + const nextPageLink = this.page.locator(`${this.pagination} a.next`); + if (await nextPageLink.isVisible()) { + await nextPageLink.click(); + await this.page.waitForSelector(this.certificateTable); + return true; + } + return false; + } + + /** + * Take a screenshot of the current filter results + * @param name Name to include in the screenshot filename + */ + async screenshotResults(name: string): Promise { + await this.page.screenshot({ + path: `certificate-filter-${name}.png`, + fullPage: true + }); + } +} \ No newline at end of file