upskill-event-manager/tests/e2e/hvac-announcements.test.js
Ben 7c9ca65cf2
Some checks are pending
HVAC Plugin CI/CD Pipeline / Security Analysis (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Code Quality & Standards (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Unit Tests (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Integration Tests (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Deploy to Staging (push) Blocked by required conditions
HVAC Plugin CI/CD Pipeline / Deploy to Production (push) Blocked by required conditions
HVAC Plugin CI/CD Pipeline / Notification (push) Blocked by required conditions
Security Monitoring & Compliance / Dependency Vulnerability Scan (push) Waiting to run
Security Monitoring & Compliance / Secrets & Credential Scan (push) Waiting to run
Security Monitoring & Compliance / WordPress Security Analysis (push) Waiting to run
Security Monitoring & Compliance / Static Code Security Analysis (push) Waiting to run
Security Monitoring & Compliance / Security Compliance Validation (push) Waiting to run
Security Monitoring & Compliance / Security Summary Report (push) Blocked by required conditions
Security Monitoring & Compliance / Security Team Notification (push) Blocked by required conditions
feat: add comprehensive test framework and test files
- Add 90+ test files including E2E, unit, and integration tests
- Implement Page Object Model (POM) architecture
- Add Docker testing environment with comprehensive services
- Include modernized test framework with error recovery
- Add specialized test suites for master trainer and trainer workflows
- Update .gitignore to properly track test infrastructure

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-29 23:23:26 -03:00

535 lines
No EOL
20 KiB
JavaScript

// @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 <script>alert("XSS")</script>');
await page.fill('#announcement-content', '<img src=x onerror="alert(\'XSS\')">Test content');
await page.fill('#announcement-excerpt', '<script>alert("XSS")</script>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('<script>alert');
expect(pageContent).not.toContain('onerror=');
});
});
test.describe('HVAC Announcements - Responsive Design', () => {
[
{ name: 'Mobile', width: 375, height: 667 },
{ name: 'Tablet', width: 768, height: 1024 },
{ name: 'Desktop', width: 1920, height: 1080 }
].forEach(({ name, width, height }) => {
test(`Announcements display correctly on ${name}`, async ({ page }) => {
// Set viewport
await page.setViewportSize({ width, height });
await loginAsUser(page, REGULAR_TRAINER.username, REGULAR_TRAINER.password);
await navigateToTrainerPage(page, 'resources');
// Verify timeline is responsive
await expect(page.locator('.hvac-announcements-timeline')).toBeVisible();
// Check if elements stack properly on mobile
if (width < 768) {
// Mobile layout checks
const timelineItems = await page.locator('.timeline-item').count();
if (timelineItems > 0) {
const item = page.locator('.timeline-item').first();
const box = await item.boundingBox();
expect(box.width).toBeLessThanOrEqual(width - 40); // Account for padding
}
}
// Test modal on different screen sizes
const hasAnnouncements = await page.locator('.announcement-link').count() > 0;
if (hasAnnouncements) {
await page.locator('.announcement-link').first().click();
// Verify modal is responsive
const modal = page.locator('.modal-content');
await expect(modal).toBeVisible();
const modalBox = await modal.boundingBox();
expect(modalBox.width).toBeLessThanOrEqual(width);
// Close modal
await page.locator('.modal-close').click();
}
});
});
});
test.describe('HVAC Announcements - Performance', () => {
test('Announcements page loads within acceptable time', async ({ page }) => {
const startTime = Date.now();
await loginAsUser(page, REGULAR_TRAINER.username, REGULAR_TRAINER.password);
await navigateToTrainerPage(page, 'resources');
// Wait for main content to load
await expect(page.locator('.hvac-announcements-timeline')).toBeVisible();
const loadTime = Date.now() - startTime;
// Page should load within 5 seconds
expect(loadTime).toBeLessThan(5000);
});
test('Pagination loads efficiently', async ({ page }) => {
await loginAsUser(page, REGULAR_TRAINER.username, REGULAR_TRAINER.password);
await navigateToTrainerPage(page, 'resources');
const hasLoadMore = await page.locator('.load-more-announcements').count() > 0;
if (hasLoadMore) {
const startTime = Date.now();
// Click load more
await page.click('.load-more-announcements');
// Wait for new content
await page.waitForTimeout(1000);
const loadTime = Date.now() - startTime;
// Additional content should load within 2 seconds
expect(loadTime).toBeLessThan(2000);
}
});
});
test.describe('HVAC Announcements - Accessibility', () => {
test('Announcements are keyboard navigable', async ({ page }) => {
await loginAsUser(page, REGULAR_TRAINER.username, REGULAR_TRAINER.password);
await navigateToTrainerPage(page, 'resources');
// Tab through announcement links
const announcements = await page.locator('.announcement-link').count();
if (announcements > 0) {
// Focus first announcement with Tab
await page.keyboard.press('Tab');
// Keep tabbing until we reach an announcement link
for (let i = 0; i < 20; i++) {
const focused = await page.evaluate(() => document.activeElement.className);
if (focused.includes('announcement-link')) {
break;
}
await page.keyboard.press('Tab');
}
// Press Enter to open modal
await page.keyboard.press('Enter');
// Verify modal opened
await expect(page.locator('#announcement-modal')).toBeVisible();
// Press Escape to close
await page.keyboard.press('Escape');
// Verify modal closed
await expect(page.locator('#announcement-modal')).not.toBeVisible();
}
});
test('Announcements have proper ARIA labels', async ({ page }) => {
await loginAsUser(page, REGULAR_TRAINER.username, REGULAR_TRAINER.password);
await navigateToTrainerPage(page, 'resources');
// Check for ARIA attributes
const hasAnnouncements = await page.locator('.timeline-item').count() > 0;
if (hasAnnouncements) {
// Check article elements have proper roles
const articles = page.locator('.timeline-item');
await expect(articles.first()).toHaveAttribute('role', 'article');
// Check links have accessible text
const link = page.locator('.announcement-link').first();
const linkText = await link.textContent();
expect(linkText).not.toBeEmpty();
}
});
});