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 { Page, expect } from '@playwright/test';
|
||||||
import { BasePage } from './BasePage';
|
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 {
|
export class CertificatePage extends BasePage {
|
||||||
// Generate Certificates page selectors
|
// Selectors for Generate Certificates page
|
||||||
private readonly generateCertificatesTitle = 'h1:has-text("Generate Certificates")';
|
private readonly generatePageTitle = 'h1:text("Generate Certificates")';
|
||||||
private readonly eventSelector = 'select[name="event_id"]';
|
private readonly eventSelectDropdown = '#event_id';
|
||||||
private readonly eventSearchInput = 'input[name="event_search"]';
|
private readonly selectedEventName = '.hvac-selected-event strong';
|
||||||
private readonly selectAllCheckbox = 'input[name="select_all"]';
|
private readonly attendeeList = '.hvac-attendee-list';
|
||||||
private readonly attendeeCheckboxes = 'input[name="attendees[]"]';
|
private readonly attendeeItems = '.hvac-attendee-item';
|
||||||
private readonly generateButton = 'button:has-text("Generate Certificates")';
|
private readonly checkedInLabel = '.hvac-checked-in';
|
||||||
private readonly previewButton = 'button:has-text("Preview Certificate")';
|
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 successMessage = '.hvac-success-message';
|
||||||
private readonly errorMessage = '.hvac-error-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';
|
|
||||||
|
|
||||||
// Certificate Reports page selectors
|
// Selectors for Certificate Reports page
|
||||||
private readonly certificateReportsTitle = 'h1:has-text("Certificate Reports")';
|
private readonly reportsPageTitle = 'h1:text("Certificate Reports")';
|
||||||
private readonly certificateFilterInput = 'input[name="certificate_search"]';
|
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 certificateTable = '.hvac-certificate-table';
|
||||||
private readonly certificateTableRows = '.hvac-certificate-table tbody tr';
|
private readonly certificateItems = '.hvac-certificate-item';
|
||||||
private readonly viewCertificateButton = 'button:has-text("View")';
|
private readonly viewCertificateLinks = 'a: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 certificatePreview = '.hvac-certificate-preview';
|
||||||
private readonly closeModalButton = '.hvac-modal-close';
|
private readonly closePreviewButton = '.hvac-preview-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';
|
|
||||||
|
|
||||||
constructor(page: Page) {
|
constructor(page: Page) {
|
||||||
super(page);
|
super(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Common methods
|
/**
|
||||||
|
* Navigate to the Generate Certificates page
|
||||||
|
*/
|
||||||
async navigateToGenerateCertificates(): Promise<void> {
|
async navigateToGenerateCertificates(): Promise<void> {
|
||||||
await this.page.goto(Config.generateCertificatesUrl);
|
await this.page.goto('/generate-certificates/');
|
||||||
await this.page.waitForLoadState('networkidle');
|
await this.page.waitForLoadState('networkidle');
|
||||||
await this.screenshot('generate-certificates-page');
|
await this.page.waitForSelector(this.generatePageTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to the Certificate Reports page
|
||||||
|
*/
|
||||||
async navigateToCertificateReports(): Promise<void> {
|
async navigateToCertificateReports(): Promise<void> {
|
||||||
await this.page.goto(Config.certificateReportsUrl);
|
await this.page.goto('/certificate-reports/');
|
||||||
await this.page.waitForLoadState('networkidle');
|
await this.page.waitForLoadState('networkidle');
|
||||||
await this.screenshot('certificate-reports-page');
|
await this.page.waitForSelector(this.reportsPageTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate Certificates page methods
|
/**
|
||||||
|
* Check if the Generate Certificates page is visible
|
||||||
|
*/
|
||||||
async isGenerateCertificatesPageVisible(): Promise<boolean> {
|
async isGenerateCertificatesPageVisible(): Promise<boolean> {
|
||||||
return await this.isVisible(this.generateCertificatesTitle);
|
return await this.page.isVisible(this.generatePageTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
async selectEvent(eventName: string): Promise<void> {
|
/**
|
||||||
// If there's a search input, try using it
|
* Check if the Certificate Reports page is visible
|
||||||
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> {
|
async isCertificateReportsPageVisible(): Promise<boolean> {
|
||||||
return await this.isVisible(this.certificateReportsTitle);
|
return await this.page.isVisible(this.reportsPageTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
async searchCertificates(query: string): Promise<void> {
|
/**
|
||||||
await this.fill(this.certificateFilterInput, query);
|
* Select an event from the dropdown on Generate Certificates page
|
||||||
await this.page.waitForTimeout(Config.shortWait); // Wait for search results
|
*/
|
||||||
|
async selectEvent(eventName: string): Promise<void> {
|
||||||
|
await this.page.selectOption(this.eventSelectDropdown, {
|
||||||
|
label: new RegExp(eventName, 'i')
|
||||||
|
});
|
||||||
|
await this.page.waitForSelector(this.attendeeList);
|
||||||
|
|
||||||
// Wait for loading indicator to disappear if it's present
|
// Verify the selected event
|
||||||
const loadingElement = this.page.locator(this.loadingIndicator);
|
const selectedText = await this.page.textContent(this.selectedEventName);
|
||||||
if (await loadingElement.isVisible()) {
|
expect(selectedText).toContain(eventName);
|
||||||
await loadingElement.waitFor({ state: 'hidden', timeout: Config.defaultTimeout });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.screenshot('certificate-search');
|
/**
|
||||||
|
* 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> {
|
async getCertificateCount(): Promise<number> {
|
||||||
return await this.page.locator(this.certificateTableRows).count();
|
return await this.page.locator(this.certificateItems).count();
|
||||||
}
|
}
|
||||||
|
|
||||||
async viewCertificate(index: number = 0): Promise<void> {
|
/**
|
||||||
const viewButtons = this.page.locator(this.viewCertificateButton);
|
* View a certificate by index
|
||||||
await viewButtons.nth(index).click();
|
*/
|
||||||
|
async viewCertificate(index: number): Promise<void> {
|
||||||
|
const viewLinks = this.page.locator(this.viewCertificateLinks);
|
||||||
|
const count = await viewLinks.count();
|
||||||
|
|
||||||
// Wait for the preview modal to appear
|
if (index >= count) {
|
||||||
await this.waitForElement(this.certificateModal);
|
throw new Error(`Cannot view certificate at index ${index}. Only ${count} certificates available.`);
|
||||||
await this.screenshot('view-certificate');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async emailCertificate(index: number = 0): Promise<void> {
|
await viewLinks.nth(index).click();
|
||||||
const emailButtons = this.page.locator(this.emailCertificateButton);
|
await this.page.waitForSelector(this.certificatePreview);
|
||||||
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');
|
/**
|
||||||
}
|
* Close the certificate preview
|
||||||
|
*/
|
||||||
async revokeCertificate(index: number = 0): Promise<void> {
|
async closePreview(): Promise<void> {
|
||||||
const revokeButtons = this.page.locator(this.revokeCertificateButton);
|
await this.page.click(this.closePreviewButton);
|
||||||
await revokeButtons.nth(index).click();
|
await this.page.waitForSelector(this.certificatePreview, { state: 'hidden' });
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,181 +1,200 @@
|
||||||
import { Page } from '@playwright/test';
|
import { Page } from '@playwright/test';
|
||||||
import { VerbosityController } from './VerbosityController';
|
|
||||||
import { Config } from './Config';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class to set up test data for certificate testing
|
* Utility class for creating test data for certificate tests
|
||||||
* This helps ensure we have events with both checked-in and non-checked-in attendees
|
*
|
||||||
|
* 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 {
|
export class CertificateTestData {
|
||||||
private page: Page;
|
private page: Page;
|
||||||
private verbosity: VerbosityController;
|
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) {
|
constructor(page: Page) {
|
||||||
this.page = page;
|
this.page = page;
|
||||||
this.verbosity = VerbosityController.getInstance();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Login as the test trainer
|
* Login as a trainer user for test setup
|
||||||
*/
|
*/
|
||||||
async loginAsTrainer(): Promise<void> {
|
async loginAsTrainer(): Promise<void> {
|
||||||
this.verbosity.log('Logging in as test_trainer');
|
await this.page.goto(`${this.baseUrl}${this.loginUrl}`);
|
||||||
await this.page.goto(Config.loginUrl);
|
await this.page.fill('#user_login', this.username);
|
||||||
await this.page.fill('#user_login', Config.testTrainer.username);
|
await this.page.fill('#user_pass', this.password);
|
||||||
await this.page.fill('#user_pass', Config.testTrainer.password);
|
|
||||||
await this.page.click('#wp-submit');
|
await this.page.click('#wp-submit');
|
||||||
await this.page.waitForLoadState('networkidle');
|
await this.page.waitForLoadState('networkidle');
|
||||||
|
|
||||||
|
// 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}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a test event with a specified name and future date
|
* Set up a test event with attendees for certificate testing
|
||||||
|
* @returns The name of the created test event
|
||||||
*/
|
*/
|
||||||
async createTestEvent(eventName: string): Promise<string | null> {
|
async setupCertificateTestEvent(): Promise<string | null> {
|
||||||
this.verbosity.log(`Creating test event: ${eventName}`);
|
// Generate a unique event name with timestamp
|
||||||
|
const timestamp = new Date().getTime();
|
||||||
|
this.testEventName = `Certificate Test Event ${timestamp}`;
|
||||||
|
|
||||||
await this.page.goto(Config.createEventUrl);
|
// Navigate to create event page
|
||||||
|
await this.page.goto(`${this.baseUrl}/community-events/`);
|
||||||
await this.page.waitForLoadState('networkidle');
|
await this.page.waitForLoadState('networkidle');
|
||||||
|
|
||||||
// Fill in event details
|
// Populate the event form with test data
|
||||||
await this.page.fill('#post_title, input[name="post_title"]', eventName);
|
await this.page.fill('#post_title', this.testEventName);
|
||||||
|
|
||||||
// Add description
|
// Set event date (today + 1 day)
|
||||||
const newEventFrame = this.page.frameLocator('iframe[id*="_ifr"]');
|
const tomorrow = new Date();
|
||||||
const newEventBody = newEventFrame.locator('body');
|
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||||
await newEventBody.fill(`This is a test event created for certificate testing: ${eventName}`);
|
const dateString = tomorrow.toISOString().split('T')[0]; // Format as YYYY-MM-DD
|
||||||
|
|
||||||
// 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()}`;
|
|
||||||
|
|
||||||
|
// Fill event date fields
|
||||||
await this.page.fill('input[name="EventStartDate"]', dateString);
|
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="EventEndDate"]', dateString);
|
||||||
await this.page.fill('input[name="EventEndTime"]', '04:00 PM');
|
|
||||||
|
|
||||||
// Add a ticket for $100
|
// Set content using TinyMCE if available
|
||||||
await this.addTicket('Certificate Test Ticket', '100');
|
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.');
|
||||||
|
}
|
||||||
|
|
||||||
// Submit the event
|
// Fill other required fields
|
||||||
const submitButton = this.page.locator('input[value="Submit Event"], button:has-text("Submit Event")');
|
await this.page.fill('#EventVenueName', 'Test Venue');
|
||||||
await submitButton.click();
|
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');
|
await this.page.waitForLoadState('networkidle');
|
||||||
|
|
||||||
// Get the event ID from the URL if possible
|
// Extract the event ID from the URL or response
|
||||||
const url = this.page.url();
|
const url = this.page.url();
|
||||||
const match = url.match(/post=(\d+)/);
|
const match = url.match(/post=(\d+)/);
|
||||||
if (match && match[1]) {
|
if (match && match[1]) {
|
||||||
return match[1];
|
this.testEventId = match[1];
|
||||||
}
|
console.log(`Created test event with ID: ${this.testEventId}`);
|
||||||
|
|
||||||
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 {
|
} else {
|
||||||
this.verbosity.log('Warning: Ticket creation UI not found');
|
console.error('Failed to extract event ID from URL:', url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create test attendees via API for efficiency
|
||||||
|
await this.createTestAttendees();
|
||||||
|
|
||||||
|
return this.testEventName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simulates attendee registrations for an event
|
* Create test attendees for the event
|
||||||
* Creates a mix of checked-in and non-checked-in attendees
|
* Creates 5 attendees: 3 checked-in, 2 not checked-in
|
||||||
*/
|
*/
|
||||||
async createTestAttendees(eventId: string, count: number = 5): Promise<void> {
|
private async createTestAttendees(): Promise<void> {
|
||||||
this.verbosity.log(`Creating ${count} test attendees for event ${eventId}`);
|
if (!this.testEventId) {
|
||||||
|
throw new Error('Cannot create attendees: No test event ID available');
|
||||||
|
}
|
||||||
|
|
||||||
// First, navigate to the admin area to access the event
|
// Login to admin to create test attendees
|
||||||
await this.page.goto(`${Config.stagingUrl}/wp-admin/post.php?post=${eventId}&action=edit`);
|
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');
|
||||||
|
|
||||||
// Check if we're on the login page and log in if needed
|
// Create 5 test attendees
|
||||||
if (this.page.url().includes('wp-login.php')) {
|
const attendeeData = [
|
||||||
await this.page.fill('#user_login', Config.testTrainer.username);
|
{ name: 'Test Attendee 1', email: 'test1@example.com', checkedIn: true },
|
||||||
await this.page.fill('#user_pass', Config.testTrainer.password);
|
{ 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.click('#wp-submit');
|
||||||
await this.page.waitForLoadState('networkidle');
|
await this.page.waitForLoadState('networkidle');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Navigate to the attendees tab - this is implementation-specific and may need adjustment
|
// Move the event to trash
|
||||||
const attendeesTab = this.page.locator('a:has-text("Attendees")');
|
await this.page.goto(`${this.baseUrl}${this.adminUrl}post.php?post=${this.testEventId}&action=trash`);
|
||||||
if (await attendeesTab.isVisible()) {
|
await this.page.waitForLoadState('networkidle');
|
||||||
await attendeesTab.click();
|
|
||||||
await this.page.waitForTimeout(Config.shortWait);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Look for "Add New" button
|
console.log(`Cleaned up test event with ID: ${this.testEventId}`);
|
||||||
const addNewButton = this.page.locator('a:has-text("Add attendee"), button:has-text("Add attendee")');
|
this.testEventId = null;
|
||||||
|
this.testEventName = null;
|
||||||
for (let i = 1; i <= count; i++) {
|
} catch (error) {
|
||||||
// Click "Add New" for each attendee
|
console.error('Error during test data cleanup:', error);
|
||||||
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