test: Optimize E2E testing infrastructure for certificates
- Add CertificatePage for certificate operations - Create BasePage for common page object functionality - Implement CertificateTestData for test data generation - Create optimized certificate tests with improved stability - Add test-certificate-filter.sh script for testing certificate filtering - Improve test organization and reliability
This commit is contained in:
parent
8dde809062
commit
353d951ba7
5 changed files with 861 additions and 430 deletions
172
wordpress-dev/bin/test-certificate-filter.sh
Executable file
172
wordpress-dev/bin/test-certificate-filter.sh
Executable file
|
|
@ -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
|
||||
223
wordpress-dev/tests/e2e/certificates.test.ts
Normal file
223
wordpress-dev/tests/e2e/certificates.test.ts
Normal file
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
92
wordpress-dev/tests/e2e/pages/BasePage.ts
Normal file
92
wordpress-dev/tests/e2e/pages/BasePage.ts
Normal file
|
|
@ -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<void> {
|
||||
await this.page.waitForLoadState('domcontentloaded');
|
||||
await this.page.waitForLoadState('networkidle');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current page title
|
||||
*/
|
||||
async getTitle(): Promise<string> {
|
||||
return await this.page.title();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current page URL
|
||||
*/
|
||||
async getUrl(): Promise<string> {
|
||||
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<void> {
|
||||
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<Buffer> {
|
||||
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<boolean> {
|
||||
return await this.page.isVisible(selector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get text content of an element
|
||||
* @param selector The selector for the element
|
||||
*/
|
||||
async getText(selector: string): Promise<string | null> {
|
||||
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<void> {
|
||||
await Promise.all([
|
||||
this.page.waitForNavigation(),
|
||||
this.page.click(selector)
|
||||
]);
|
||||
await this.waitForPageLoad();
|
||||
}
|
||||
}
|
||||
|
|
@ -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<void> {
|
||||
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<void> {
|
||||
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<boolean> {
|
||||
return await this.page.isVisible(this.generatePageTitle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the Certificate Reports page is visible
|
||||
*/
|
||||
async isCertificateReportsPageVisible(): Promise<boolean> {
|
||||
return await this.page.isVisible(this.reportsPageTitle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select an event from the dropdown on Generate Certificates page
|
||||
*/
|
||||
async selectEvent(eventName: string): Promise<void> {
|
||||
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<number> {
|
||||
return await this.page.locator(this.attendeeItems).count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get number of checked-in attendees
|
||||
*/
|
||||
async getCheckedInAttendeeCount(): Promise<number> {
|
||||
return await this.page.locator(this.checkedInLabel).count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Select all attendees
|
||||
*/
|
||||
async selectAllAttendees(): Promise<void> {
|
||||
await this.page.click(this.checkAllButton);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select only checked-in attendees
|
||||
*/
|
||||
async selectCheckedInAttendees(): Promise<void> {
|
||||
await this.page.click(this.checkCheckedInButton);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate certificates for selected attendees
|
||||
*/
|
||||
async generateCertificates(): Promise<void> {
|
||||
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<boolean> {
|
||||
return await this.page.isVisible(this.successMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get success message text
|
||||
*/
|
||||
async getSuccessMessage(): Promise<string | null> {
|
||||
return await this.page.textContent(this.successMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter certificates by event name
|
||||
*/
|
||||
async searchCertificates(eventName: string): Promise<void> {
|
||||
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<void> {
|
||||
await this.page.fill(this.attendeeSearchInput, searchTerm);
|
||||
await this.page.click(this.filterButton);
|
||||
await this.page.waitForLoadState('networkidle');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all filters
|
||||
*/
|
||||
async resetFilters(): Promise<void> {
|
||||
await this.page.click(this.resetButton);
|
||||
await this.page.waitForLoadState('networkidle');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of certificates in the table
|
||||
*/
|
||||
async getCertificateCount(): Promise<number> {
|
||||
return await this.page.locator(this.certificateItems).count();
|
||||
}
|
||||
|
||||
/**
|
||||
* View a certificate by index
|
||||
*/
|
||||
async viewCertificate(index: number): Promise<void> {
|
||||
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<void> {
|
||||
await this.page.goto(Config.generateCertificatesUrl);
|
||||
await this.page.waitForLoadState('networkidle');
|
||||
await this.screenshot('generate-certificates-page');
|
||||
}
|
||||
|
||||
async navigateToCertificateReports(): Promise<void> {
|
||||
await this.page.goto(Config.certificateReportsUrl);
|
||||
await this.page.waitForLoadState('networkidle');
|
||||
await this.screenshot('certificate-reports-page');
|
||||
}
|
||||
|
||||
// Generate Certificates page methods
|
||||
async isGenerateCertificatesPageVisible(): Promise<boolean> {
|
||||
return await this.isVisible(this.generateCertificatesTitle);
|
||||
}
|
||||
|
||||
async selectEvent(eventName: string): Promise<void> {
|
||||
// If there's a search input, try using it
|
||||
if (await this.isVisible(this.eventSearchInput)) {
|
||||
await this.fill(this.eventSearchInput, eventName);
|
||||
await this.page.waitForTimeout(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<number> {
|
||||
return await this.page.locator(this.attendeeItem).count();
|
||||
}
|
||||
|
||||
async getCheckedInAttendeeCount(): Promise<number> {
|
||||
let checkedInCount = 0;
|
||||
const attendees = this.page.locator(this.attendeeItem);
|
||||
const count = await attendees.count();
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const status = await attendees.nth(i).getAttribute(this.checkinStatusAttribute);
|
||||
if (status === 'checked-in') {
|
||||
checkedInCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return checkedInCount;
|
||||
}
|
||||
|
||||
async selectAllAttendees(): Promise<void> {
|
||||
await this.click(this.selectAllCheckbox);
|
||||
await this.screenshot('all-attendees-selected');
|
||||
}
|
||||
|
||||
async selectCheckedInAttendees(): Promise<void> {
|
||||
// Deselect "Select All" if it's checked
|
||||
const selectAllChecked = await this.page.isChecked(this.selectAllCheckbox);
|
||||
if (selectAllChecked) {
|
||||
await this.click(this.selectAllCheckbox);
|
||||
}
|
||||
|
||||
// Select only checked-in attendees
|
||||
const attendees = this.page.locator(this.attendeeItem);
|
||||
const count = await attendees.count();
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const status = await attendees.nth(i).getAttribute(this.checkinStatusAttribute);
|
||||
if (status === 'checked-in') {
|
||||
const checkbox = attendees.nth(i).locator('input[type="checkbox"]');
|
||||
await checkbox.check();
|
||||
}
|
||||
}
|
||||
|
||||
await this.screenshot('checked-in-attendees-selected');
|
||||
}
|
||||
|
||||
async selectNonCheckedInAttendees(): Promise<void> {
|
||||
// Deselect "Select All" if it's checked
|
||||
const selectAllChecked = await this.page.isChecked(this.selectAllCheckbox);
|
||||
if (selectAllChecked) {
|
||||
await this.click(this.selectAllCheckbox);
|
||||
}
|
||||
|
||||
// Select only non-checked-in attendees
|
||||
const attendees = this.page.locator(this.attendeeItem);
|
||||
const count = await attendees.count();
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const status = await attendees.nth(i).getAttribute(this.checkinStatusAttribute);
|
||||
if (status !== 'checked-in') {
|
||||
const checkbox = attendees.nth(i).locator('input[type="checkbox"]');
|
||||
await checkbox.check();
|
||||
}
|
||||
}
|
||||
|
||||
await this.screenshot('non-checked-in-attendees-selected');
|
||||
}
|
||||
|
||||
async generateCertificates(): Promise<void> {
|
||||
await this.click(this.generateButton);
|
||||
|
||||
// Wait for loading indicator to disappear if it's present
|
||||
const loadingElement = this.page.locator(this.loadingIndicator);
|
||||
if (await loadingElement.isVisible()) {
|
||||
await loadingElement.waitFor({ state: 'hidden', timeout: Config.longWait });
|
||||
}
|
||||
|
||||
await this.page.waitForTimeout(Config.mediumWait); // Additional wait for any post-processing
|
||||
await this.screenshot('certificates-generated');
|
||||
}
|
||||
|
||||
async previewCertificate(): Promise<void> {
|
||||
await this.click(this.previewButton);
|
||||
|
||||
// Wait for the preview modal to appear
|
||||
await this.waitForElement(this.certificateModal);
|
||||
await this.screenshot('certificate-preview');
|
||||
}
|
||||
|
||||
async closePreview(): Promise<void> {
|
||||
if (await this.isVisible(this.closeModalButton)) {
|
||||
await this.click(this.closeModalButton);
|
||||
await this.page.waitForTimeout(Config.shortWait); // Wait for modal to close
|
||||
}
|
||||
}
|
||||
|
||||
async isSuccessMessageVisible(): Promise<boolean> {
|
||||
return await this.isVisible(this.successMessage);
|
||||
}
|
||||
|
||||
async isErrorMessageVisible(): Promise<boolean> {
|
||||
return await this.isVisible(this.errorMessage);
|
||||
}
|
||||
|
||||
async getSuccessMessage(): Promise<string> {
|
||||
return await this.getText(this.successMessage);
|
||||
}
|
||||
|
||||
async getErrorMessage(): Promise<string> {
|
||||
return await this.getText(this.errorMessage);
|
||||
}
|
||||
|
||||
// Certificate Reports page methods
|
||||
async isCertificateReportsPageVisible(): Promise<boolean> {
|
||||
return await this.isVisible(this.certificateReportsTitle);
|
||||
}
|
||||
|
||||
async searchCertificates(query: string): Promise<void> {
|
||||
await this.fill(this.certificateFilterInput, query);
|
||||
await this.page.waitForTimeout(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<number> {
|
||||
return await this.page.locator(this.certificateTableRows).count();
|
||||
}
|
||||
|
||||
async viewCertificate(index: number = 0): Promise<void> {
|
||||
const viewButtons = this.page.locator(this.viewCertificateButton);
|
||||
await viewButtons.nth(index).click();
|
||||
|
||||
// Wait for the preview modal to appear
|
||||
await this.waitForElement(this.certificateModal);
|
||||
await this.screenshot('view-certificate');
|
||||
}
|
||||
|
||||
async emailCertificate(index: number = 0): Promise<void> {
|
||||
const emailButtons = this.page.locator(this.emailCertificateButton);
|
||||
await emailButtons.nth(index).click();
|
||||
|
||||
// Wait for the email confirmation dialog
|
||||
if (await this.isVisible(this.confirmEmailButton)) {
|
||||
await this.click(this.confirmEmailButton);
|
||||
await this.page.waitForTimeout(Config.mediumWait); // Wait for email to be sent
|
||||
}
|
||||
|
||||
await this.screenshot('email-certificate');
|
||||
}
|
||||
|
||||
async revokeCertificate(index: number = 0): Promise<void> {
|
||||
const revokeButtons = this.page.locator(this.revokeCertificateButton);
|
||||
await revokeButtons.nth(index).click();
|
||||
|
||||
// Wait for the revocation confirmation dialog
|
||||
if (await this.isVisible(this.confirmRevocationButton)) {
|
||||
await this.click(this.confirmRevocationButton);
|
||||
await this.page.waitForTimeout(Config.mediumWait); // Wait for revocation to complete
|
||||
}
|
||||
|
||||
await this.screenshot('revoke-certificate');
|
||||
}
|
||||
|
||||
async isPaginationVisible(): Promise<boolean> {
|
||||
return await this.isVisible(this.certificatePagination);
|
||||
}
|
||||
|
||||
async goToNextPage(): Promise<boolean> {
|
||||
if (await this.isVisible(this.nextPageButton)) {
|
||||
await this.click(this.nextPageButton);
|
||||
await this.page.waitForTimeout(Config.shortWait);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async goToPreviousPage(): Promise<boolean> {
|
||||
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<void> {
|
||||
await this.page.click(this.closePreviewButton);
|
||||
await this.page.waitForSelector(this.certificatePreview, { state: 'hidden' });
|
||||
}
|
||||
}
|
||||
|
|
@ -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<void> {
|
||||
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<string | null> {
|
||||
// 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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<string | null> {
|
||||
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<void> {
|
||||
// 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<void> {
|
||||
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<string | null> {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue