diff --git a/wordpress-dev/tests/e2e/certificate-core.test.ts b/wordpress-dev/tests/e2e/certificate-core.test.ts new file mode 100644 index 00000000..c9f59ab3 --- /dev/null +++ b/wordpress-dev/tests/e2e/certificate-core.test.ts @@ -0,0 +1,133 @@ +import { test, expect } from './fixtures/auth'; +import { CommonActions } from './utils/common-actions'; +import { STAGING_URL } from './config/staging-config'; + +/** + * Core certificate functionality tests + * Tests: generation, viewing, and basic functionality + * @tag @certificates @core + */ + +test.describe('Certificate Core Functionality', () => { + test('Certificate generation and viewing flow', async ({ authenticatedPage: page }) => { + const actions = new CommonActions(page); + + // Navigate to Generate Certificates page + await actions.navigateAndWait('/generate-certificates/'); + await actions.screenshot('certificate-generation-page'); + + // Verify page loaded correctly + await expect(page.locator('h1, h2').filter({ hasText: /generate certificates/i })).toBeVisible(); + + // Test AJAX functionality - get events + const eventSelect = page.locator('select[name="event_id"]'); + await expect(eventSelect).toBeVisible(); + + // Check if events are available + const eventOptions = await eventSelect.locator('option').count(); + if (eventOptions > 1) { + // Select first available event + await eventSelect.selectOption({ index: 1 }); + await actions.waitForAjax(); + + // Wait for attendees to load + await page.waitForSelector('input[name="attendee_ids[]"]', { timeout: 10000 }); + + // Verify attendees loaded + const attendeeCheckboxes = page.locator('input[name="attendee_ids[]"]'); + const attendeeCount = await attendeeCheckboxes.count(); + + if (attendeeCount > 0) { + console.log(`Found ${attendeeCount} attendees for certificate generation`); + + // Select first attendee + await attendeeCheckboxes.first().check(); + await actions.screenshot('attendee-selected'); + + // Generate certificate + await page.click('button[type="submit"], input[type="submit"]'); + await actions.waitForAjax(); + + // Verify success message or download + const successIndicators = [ + page.locator('text=Certificate generated'), + page.locator('text=Download'), + page.locator('a[href*=".pdf"]'), + page.locator('.success'), + page.locator('.notice-success') + ]; + + let foundSuccess = false; + for (const indicator of successIndicators) { + if (await indicator.count() > 0) { + foundSuccess = true; + break; + } + } + + expect(foundSuccess).toBeTruthy(); + await actions.screenshot('certificate-generated'); + } + } + }); + + test('Certificate Reports page functionality', async ({ authenticatedPage: page }) => { + const actions = new CommonActions(page); + + // Navigate to Certificate Reports + await actions.navigateAndWait('/certificate-reports/'); + await actions.screenshot('certificate-reports-page'); + + // Verify page loaded + await expect(page.locator('h1, h2').filter({ hasText: /certificate reports/i })).toBeVisible(); + + // Verify navigation + await actions.verifyNavigation(); + + // Check for statistics + const statElements = page.locator('.stat-value, .stat-number, .dashboard-stat'); + const statCount = await statElements.count(); + + if (statCount > 0) { + console.log(`Found ${statCount} certificate statistics`); + + // Verify statistics are numbers + for (let i = 0; i < Math.min(statCount, 4); i++) { + const statText = await statElements.nth(i).textContent(); + const statNumber = parseInt(statText?.replace(/[^\d]/g, '') || '0'); + expect(statNumber).toBeGreaterThanOrEqual(0); + } + } + + await actions.screenshot('certificate-reports-verified'); + }); + + test('Certificate system navigation and integration', async ({ authenticatedPage: page }) => { + const actions = new CommonActions(page); + + // Test navigation between certificate pages + const certificatePages = [ + { path: '/certificate-reports/', name: 'Certificate Reports' }, + { path: '/generate-certificates/', name: 'Generate Certificates' } + ]; + + for (const certPage of certificatePages) { + await actions.navigateAndWait(certPage.path); + + // Verify page loaded + await expect(page.locator('h1, h2').filter({ + hasText: new RegExp(certPage.name, 'i') + })).toBeVisible(); + + // Verify navigation buttons work + await actions.verifyNavigation(); + + await actions.screenshot(`${certPage.name.toLowerCase().replace(/\s+/g, '-')}-navigation`); + } + + // Test return to dashboard + await page.click('text=Dashboard'); + await actions.waitForAjax(); + await expect(page).toHaveURL(/hvac-dashboard/); + }); +}); \ No newline at end of file diff --git a/wordpress-dev/tests/e2e/certificate-edge-cases.test.ts b/wordpress-dev/tests/e2e/certificate-edge-cases.test.ts new file mode 100644 index 00000000..2e7d88d3 --- /dev/null +++ b/wordpress-dev/tests/e2e/certificate-edge-cases.test.ts @@ -0,0 +1,241 @@ +import { test, expect } from './fixtures/auth'; +import { CommonActions } from './utils/common-actions'; + +/** + * Certificate edge cases and error handling tests + * Tests: validation, error scenarios, boundary conditions + * @tag @certificates @edge-cases + */ + +test.describe('Certificate Edge Cases', () => { + test('Error handling for invalid certificate generation', async ({ authenticatedPage: page }) => { + const actions = new CommonActions(page); + + // Navigate to Generate Certificates page + await actions.navigateAndWait('/generate-certificates/'); + + // Test submitting without selecting an event + const submitButton = page.locator('button[type="submit"], input[type="submit"]'); + if (await submitButton.count() > 0) { + await submitButton.click(); + await actions.waitForAjax(); + + // Look for error messages + const errorSelectors = [ + '.error', + '.notice-error', + 'div:has-text(/error/i)', + 'span:has-text(/required/i)', + 'p:has-text(/select/i)' + ]; + + let foundError = false; + for (const selector of errorSelectors) { + if (await page.locator(selector).count() > 0) { + foundError = true; + console.log('Found appropriate error message for invalid submission'); + break; + } + } + + // Either error message shown or no action taken (both are valid) + await actions.screenshot('invalid-submission-handled'); + } + }); + + test('Certificate generation with no attendees', async ({ authenticatedPage: page }) => { + const actions = new CommonActions(page); + + await actions.navigateAndWait('/generate-certificates/'); + + const eventSelect = page.locator('select[name="event_id"]'); + const eventOptions = await eventSelect.locator('option').count(); + + if (eventOptions > 1) { + // Try to find an event with no attendees by testing each option + for (let i = 1; i < Math.min(eventOptions, 5); i++) { + await eventSelect.selectOption({ index: i }); + await actions.waitForAjax(); + + // Wait a moment for AJAX to complete + await page.waitForTimeout(2000); + + const attendeeCheckboxes = page.locator('input[name="attendee_ids[]"]'); + const attendeeCount = await attendeeCheckboxes.count(); + + if (attendeeCount === 0) { + console.log(`Found event with no attendees at index ${i}`); + + // Try to submit with no attendees + const submitButton = page.locator('button[type="submit"], input[type="submit"]'); + if (await submitButton.count() > 0) { + await submitButton.click(); + await actions.waitForAjax(); + + // Should show appropriate message + const noAttendeesMessages = [ + page.locator('text=No attendees'), + page.locator('text=No participants'), + page.locator('div:has-text(/no.*attendees/i)'), + page.locator('p:has-text(/select.*attendees/i)') + ]; + + let foundMessage = false; + for (const msg of noAttendeesMessages) { + if (await msg.count() > 0) { + foundMessage = true; + console.log('Found appropriate no-attendees message'); + break; + } + } + + await actions.screenshot('no-attendees-handled'); + } + break; + } + } + } + }); + + test('Certificate page accessibility and performance', async ({ authenticatedPage: page }) => { + const actions = new CommonActions(page); + + // Test page load performance + const startTime = Date.now(); + await actions.navigateAndWait('/generate-certificates/'); + const loadTime = Date.now() - startTime; + + console.log(`Certificate generation page loaded in ${loadTime}ms`); + expect(loadTime).toBeLessThan(10000); // Should load within 10 seconds + + // Test basic accessibility + const accessibilityChecks = [ + { selector: 'h1, h2', name: 'Page has heading' }, + { selector: 'label', name: 'Form has labels' }, + { selector: 'button, input[type="submit"]', name: 'Page has interactive elements' } + ]; + + for (const check of accessibilityChecks) { + const elements = page.locator(check.selector); + const count = await elements.count(); + expect(count).toBeGreaterThan(0); + console.log(`✓ ${check.name}: Found ${count} elements`); + } + + await actions.screenshot('accessibility-verified'); + }); + + test('Certificate system under load simulation', async ({ authenticatedPage: page }) => { + const actions = new CommonActions(page); + + // Simulate rapid navigation between certificate pages + const pages = [ + '/certificate-reports/', + '/generate-certificates/', + '/certificate-reports/', + '/generate-certificates/' + ]; + + for (let i = 0; i < pages.length; i++) { + const startTime = Date.now(); + await actions.navigateAndWait(pages[i]); + const loadTime = Date.now() - startTime; + + console.log(`Page ${i + 1} loaded in ${loadTime}ms`); + + // Verify page loaded correctly + const hasContent = await page.locator('h1, h2, .content, main').count() > 0; + expect(hasContent).toBeTruthy(); + + // Short pause to simulate user behavior + await page.waitForTimeout(500); + } + + await actions.screenshot('load-simulation-completed'); + }); + + test('Certificate data validation and sanitization', async ({ authenticatedPage: page }) => { + const actions = new CommonActions(page); + + await actions.navigateAndWait('/generate-certificates/'); + + // Test XSS prevention in any input fields + const inputFields = page.locator('input[type="text"], input[type="search"], textarea'); + const inputCount = await inputFields.count(); + + if (inputCount > 0) { + const testInput = ''; + await inputFields.first().fill(testInput); + + // Verify the input is sanitized or escaped + const value = await inputFields.first().inputValue(); + expect(value).not.toContain('