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
This commit is contained in:
bengizmo 2025-05-21 09:45:00 -03:00
parent a24d3af81b
commit 8dde809062
4 changed files with 823 additions and 0 deletions

View file

@ -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 ====="

View file

@ -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

View file

@ -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<void> {
await this.page.goto(Config.certificateReportsUrl);
await this.page.waitForLoadState('networkidle');
}
async isCertificateReportsPageVisible(): Promise<boolean> {
return this.page.locator(this.certificateTitle).isVisible();
}
async clearAllFilters(): Promise<void> {
if (await this.page.locator(this.clearFilters).isVisible()) {
await this.page.click(this.clearFilters);
await this.page.waitForTimeout(1000);
}
}
async filterByEvent(eventId: string): Promise<void> {
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<void> {
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<void> {
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<number> {
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<string[]> {
return this.page.locator(this.certificateEventCells).allTextContents();
}
async getAttendeeCellTexts(): Promise<string[]> {
return this.page.locator(this.certificateAttendeeCells).allTextContents();
}
async hasPagination(): Promise<boolean> {
return this.page.locator(this.pagination).isVisible();
}
async clickNextPage(): Promise<void> {
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');
});

View file

@ -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<void> {
await this.page.goto(Config.certificateReportsUrl);
await this.page.waitForLoadState('networkidle');
}
/**
* Check if the certificate reports page is visible
*/
async isPageVisible(): Promise<boolean> {
return this.page.locator(this.pageTitle).isVisible();
}
/**
* Clear all existing filters
*/
async clearFilters(): Promise<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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<number> {
// 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<boolean> {
return !(await this.page.locator(this.noResultsMessage).isVisible());
}
/**
* Get all event names from the current certificate table
*/
async getEventNames(): Promise<string[]> {
return this.page.locator(this.eventColumn).allTextContents();
}
/**
* Get all attendee names from the current certificate table
*/
async getAttendeeNames(): Promise<string[]> {
return this.page.locator(this.attendeeColumn).allTextContents();
}
/**
* Get all email addresses from the current certificate table
*/
async getEmails(): Promise<string[]> {
return this.page.locator(this.emailColumn).allTextContents();
}
/**
* Get all status values from the current certificate table
*/
async getStatuses(): Promise<string[]> {
return this.page.locator(this.statusColumn).allTextContents();
}
/**
* Check if pagination is available
*/
async hasPagination(): Promise<boolean> {
return this.page.locator(this.pagination).isVisible();
}
/**
* Navigate to the next page of results
*/
async goToNextPage(): Promise<boolean> {
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<void> {
await this.page.screenshot({
path: `certificate-filter-${name}.png`,
fullPage: true
});
}
}