// @ts-check const { test, expect } = require('@playwright/test'); /** * E2E Tests for HVAC Trainer Announcements System * * Tests the complete user journey for: * - Master trainers creating and managing announcements * - Regular trainers viewing announcements * - Email notifications * - Security and permissions */ // Test configuration const BASE_URL = process.env.UPSKILL_STAGING_URL || 'https://upskill-staging.measurequick.com'; const ADMIN_USER = process.env.STAGING_ADMIN_USER || 'benr'; const ADMIN_PASS = process.env.STAGING_ADMIN_PASS || 'Xy8$kP#mN2@Lq9Z!'; // Test users const MASTER_TRAINER = { username: 'JoeMedosch@gmail.com', password: 'JoeTrainer2025@', displayName: 'Joe Medosch' }; const REGULAR_TRAINER = { username: 'test_trainer', password: 'TestTrainer123!', displayName: 'Test Trainer' }; // Helper function to login async function loginAsUser(page, username, password) { await page.goto(`${BASE_URL}/wp-login.php`); await page.fill('#user_login', username); await page.fill('#user_pass', password); await page.click('#wp-submit'); await page.waitForURL(/\/wp-admin|\/trainer\//); } // Helper function to navigate to trainer page async function navigateToTrainerPage(page, path) { await page.goto(`${BASE_URL}/trainer/${path}/`); await page.waitForLoadState('networkidle'); } test.describe('HVAC Announcements - Master Trainer Features', () => { test.beforeEach(async ({ page }) => { await loginAsUser(page, MASTER_TRAINER.username, MASTER_TRAINER.password); }); test('Master trainer can access announcements management page', async ({ page }) => { // Navigate to master announcements page await navigateToTrainerPage(page, 'master-trainer/manage-announcements'); // Verify page loaded await expect(page.locator('h1')).toContainText('Manage Announcements'); // Verify admin interface elements await expect(page.locator('#announcements-admin-container')).toBeVisible(); await expect(page.locator('.announcement-header')).toBeVisible(); await expect(page.locator('#announcement-list')).toBeVisible(); // Verify action buttons await expect(page.locator('#new-announcement-btn')).toBeVisible(); await expect(page.locator('#new-announcement-btn')).toHaveText('New Announcement'); }); test('Master trainer can create a new announcement', async ({ page }) => { await navigateToTrainerPage(page, 'master-trainer/manage-announcements'); // Click new announcement button await page.click('#new-announcement-btn'); // Wait for form to appear await expect(page.locator('#announcement-form')).toBeVisible(); // Fill in announcement details const testTitle = `E2E Test Announcement ${Date.now()}`; const testContent = 'This is a test announcement created by Playwright E2E tests.'; const testExcerpt = 'Test excerpt for E2E'; await page.fill('#announcement-title', testTitle); // Handle rich text editor (TinyMCE or plain textarea) const editorFrame = page.frameLocator('#announcement-content_ifr'); const hasIframe = await editorFrame.locator('body').count() > 0; if (hasIframe) { // TinyMCE editor await editorFrame.locator('body').fill(testContent); } else { // Plain textarea await page.fill('#announcement-content', testContent); } await page.fill('#announcement-excerpt', testExcerpt); // Select status await page.selectOption('#announcement-status', 'draft'); // Add categories (if available) const categoryCheckboxes = await page.locator('.category-checkbox').count(); if (categoryCheckboxes > 0) { await page.locator('.category-checkbox').first().check(); } // Add tags await page.fill('#announcement-tags', 'e2e-test, playwright, automated'); // Save announcement await page.click('#save-announcement-btn'); // Wait for success message await expect(page.locator('.notice-success')).toBeVisible({ timeout: 10000 }); await expect(page.locator('.notice-success')).toContainText(/created|saved|success/i); // Verify announcement appears in list await expect(page.locator('#announcement-list')).toContainText(testTitle); }); test('Master trainer can edit an existing announcement', async ({ page }) => { await navigateToTrainerPage(page, 'master-trainer/manage-announcements'); // Wait for announcements to load await page.waitForSelector('.announcement-item', { timeout: 10000 }); // Click edit on first announcement const firstAnnouncement = page.locator('.announcement-item').first(); await firstAnnouncement.locator('.edit-announcement').click(); // Wait for form to load with data await expect(page.locator('#announcement-form')).toBeVisible(); await expect(page.locator('#announcement-title')).not.toBeEmpty(); // Edit the title const currentTitle = await page.locator('#announcement-title').inputValue(); const updatedTitle = currentTitle + ' (Updated)'; await page.fill('#announcement-title', updatedTitle); // Update status to publish await page.selectOption('#announcement-status', 'publish'); // Save changes await page.click('#save-announcement-btn'); // Wait for success message await expect(page.locator('.notice-success')).toBeVisible({ timeout: 10000 }); // Verify updated title in list await expect(page.locator('#announcement-list')).toContainText(updatedTitle); }); test('Master trainer can delete an announcement', async ({ page }) => { await navigateToTrainerPage(page, 'master-trainer/manage-announcements'); // Wait for announcements to load await page.waitForSelector('.announcement-item', { timeout: 10000 }); // Count initial announcements const initialCount = await page.locator('.announcement-item').count(); // Find an announcement with "E2E Test" in title (if exists) const testAnnouncements = page.locator('.announcement-item:has-text("E2E Test")'); const hasTestAnnouncement = await testAnnouncements.count() > 0; if (hasTestAnnouncement) { // Click delete on test announcement await testAnnouncements.first().locator('.delete-announcement').click(); // Confirm deletion page.on('dialog', dialog => dialog.accept()); // Wait for announcement to be removed await expect(page.locator('.notice-success')).toBeVisible({ timeout: 10000 }); // Verify count decreased const newCount = await page.locator('.announcement-item').count(); expect(newCount).toBeLessThan(initialCount); } }); test('Master trainer can filter announcements by status', async ({ page }) => { await navigateToTrainerPage(page, 'master-trainer/manage-announcements'); // Wait for filter controls await expect(page.locator('#announcement-status-filter')).toBeVisible(); // Filter by published await page.selectOption('#announcement-status-filter', 'publish'); await page.waitForTimeout(1000); // Wait for filter to apply // Verify only published announcements shown const statusBadges = await page.locator('.status-badge').allTextContents(); statusBadges.forEach(status => { expect(status.toLowerCase()).toContain('publish'); }); // Filter by draft await page.selectOption('#announcement-status-filter', 'draft'); await page.waitForTimeout(1000); // Verify only draft announcements shown (if any) const draftBadges = await page.locator('.status-badge').allTextContents(); if (draftBadges.length > 0) { draftBadges.forEach(status => { expect(status.toLowerCase()).toContain('draft'); }); } }); test('Master trainer can search announcements', async ({ page }) => { await navigateToTrainerPage(page, 'master-trainer/manage-announcements'); // Wait for search box await expect(page.locator('#announcement-search')).toBeVisible(); // Search for specific term await page.fill('#announcement-search', 'training'); await page.press('#announcement-search', 'Enter'); // Wait for search results await page.waitForTimeout(1000); // Verify search results contain search term (if any results) const searchResults = await page.locator('.announcement-item').count(); if (searchResults > 0) { const titles = await page.locator('.announcement-title').allTextContents(); const contents = await page.locator('.announcement-excerpt').allTextContents(); const allText = [...titles, ...contents].join(' ').toLowerCase(); expect(allText).toContain('training'); } }); }); test.describe('HVAC Announcements - Regular Trainer Features', () => { test.beforeEach(async ({ page }) => { await loginAsUser(page, REGULAR_TRAINER.username, REGULAR_TRAINER.password); }); test('Regular trainer can view announcements on resources page', async ({ page }) => { // Navigate to resources page await navigateToTrainerPage(page, 'resources'); // Verify page loaded await expect(page.locator('h1')).toContainText('Training Resources'); // Verify announcements timeline is visible await expect(page.locator('.hvac-announcements-timeline')).toBeVisible(); // Verify at least one announcement is displayed (if any published) const announcementCount = await page.locator('.timeline-item').count(); if (announcementCount > 0) { // Verify announcement structure await expect(page.locator('.timeline-item').first()).toBeVisible(); await expect(page.locator('.timeline-title').first()).toBeVisible(); await expect(page.locator('.timeline-date').first()).toBeVisible(); await expect(page.locator('.timeline-excerpt').first()).toBeVisible(); } }); test('Regular trainer can click to view announcement details', async ({ page }) => { await navigateToTrainerPage(page, 'resources'); // Wait for announcements to load const announcements = await page.locator('.announcement-link').count(); if (announcements > 0) { // Click first announcement await page.locator('.announcement-link').first().click(); // Wait for modal to appear await expect(page.locator('#announcement-modal')).toBeVisible(); await expect(page.locator('.modal-content')).toBeVisible(); // Verify modal content await expect(page.locator('.announcement-full')).toBeVisible(); await expect(page.locator('.announcement-header h2')).toBeVisible(); await expect(page.locator('.announcement-content')).toBeVisible(); // Close modal await page.locator('.modal-close').click(); await expect(page.locator('#announcement-modal')).not.toBeVisible(); } }); test('Regular trainer cannot access management page', async ({ page }) => { // Try to access master announcements page const response = await page.goto(`${BASE_URL}/trainer/master-trainer/manage-announcements/`); // Should be redirected or show error expect(response.status()).not.toBe(200); // Verify error message or redirect const url = page.url(); expect(url).not.toContain('/manage-announcements'); }); test('Regular trainer can use load more functionality', async ({ page }) => { await navigateToTrainerPage(page, 'resources'); // Check if load more button exists const hasLoadMore = await page.locator('.load-more-announcements').count() > 0; if (hasLoadMore) { // Count initial announcements const initialCount = await page.locator('.timeline-item').count(); // Click load more await page.click('.load-more-announcements'); // Wait for new announcements to load await page.waitForTimeout(2000); // Verify more announcements loaded const newCount = await page.locator('.timeline-item').count(); expect(newCount).toBeGreaterThan(initialCount); } }); test('Regular trainer can view Google Drive resources', async ({ page }) => { await navigateToTrainerPage(page, 'resources'); // Check if Google Drive embed exists const hasDriveEmbed = await page.locator('.hvac-google-drive-embed').count() > 0; if (hasDriveEmbed) { // Verify iframe is present await expect(page.locator('.hvac-google-drive-embed iframe')).toBeVisible(); // Verify iframe has correct attributes const iframe = page.locator('.hvac-google-drive-embed iframe'); await expect(iframe).toHaveAttribute('src', /drive\.google\.com/); } }); }); test.describe('HVAC Announcements - Security and Permissions', () => { test('Unauthenticated user cannot access announcements', async ({ page }) => { // Try to access resources page without login await page.goto(`${BASE_URL}/trainer/resources/`); // Should be redirected to login await expect(page).toHaveURL(/wp-login\.php/); }); test('Subscriber cannot view announcements', async ({ page }) => { // Create or use a subscriber account // This would need a subscriber test account to be set up // For now, we'll test that non-trainer roles are blocked // Attempt to access resources page directly const response = await page.goto(`${BASE_URL}/trainer/resources/`); // Should not have access if (response.ok()) { // If page loads, verify no announcements shown const hasPermissionError = await page.locator(':text("You do not have permission")').count() > 0; expect(hasPermissionError).toBeTruthy(); } }); test('XSS prevention in announcement content', async ({ page }) => { await loginAsUser(page, MASTER_TRAINER.username, MASTER_TRAINER.password); await navigateToTrainerPage(page, 'master-trainer/manage-announcements'); // Create announcement with potential XSS await page.click('#new-announcement-btn'); await page.fill('#announcement-title', 'XSS Test '); await page.fill('#announcement-content', 'Test content'); await page.fill('#announcement-excerpt', 'Excerpt'); await page.selectOption('#announcement-status', 'publish'); await page.click('#save-announcement-btn'); // Wait for save await page.waitForTimeout(2000); // Now view as regular trainer await loginAsUser(page, REGULAR_TRAINER.username, REGULAR_TRAINER.password); await navigateToTrainerPage(page, 'resources'); // Verify no script execution page.on('dialog', dialog => { // Fail test if any alert appears expect(dialog.message()).not.toContain('XSS'); dialog.dismiss(); }); // Check that dangerous content is escaped const pageContent = await page.content(); expect(pageContent).not.toContain('