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:
parent
a24d3af81b
commit
8dde809062
4 changed files with 823 additions and 0 deletions
242
wordpress-dev/bin/optimize-e2e-tests.sh
Executable file
242
wordpress-dev/bin/optimize-e2e-tests.sh
Executable 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 ====="
|
||||
165
wordpress-dev/tests/e2e/TESTING-STRATEGY.md
Normal file
165
wordpress-dev/tests/e2e/TESTING-STRATEGY.md
Normal 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
|
||||
213
wordpress-dev/tests/e2e/optimized-certificate-tests.ts
Normal file
213
wordpress-dev/tests/e2e/optimized-certificate-tests.ts
Normal 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');
|
||||
});
|
||||
203
wordpress-dev/tests/e2e/pages/AttendeeFilterPage.ts
Normal file
203
wordpress-dev/tests/e2e/pages/AttendeeFilterPage.ts
Normal 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
|
||||
});
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue