upskill-event-manager/tests/test-integration-comprehensive.js
ben 90193ea18c security: implement Phase 1 critical vulnerability fixes
- Add XSS protection with DOMPurify sanitization in rich text editor
- Implement comprehensive file upload security validation
- Enhance server-side content sanitization with wp_kses
- Add comprehensive security test suite with 194+ test cases
- Create security remediation plan documentation

Security fixes address:
- CRITICAL: XSS vulnerability in event description editor
- HIGH: File upload security bypass for malicious files
- HIGH: Enhanced CSRF protection verification
- MEDIUM: Input validation and error handling improvements

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-25 18:53:23 -03:00

649 lines
No EOL
29 KiB
JavaScript

const { test, expect } = require('@playwright/test');
const { HVACTestBase } = require('./page-objects/HVACTestBase');
/**
* Comprehensive Integration Test Suite
*
* Tests complete end-to-end workflows combining all UI/UX enhancement features:
* - Complete event creation workflow with all components
* - Template application with enhanced features
* - Form validation across all enhanced fields
* - TEC integration with ticketing and security
* - Responsive layout behavior
* - Performance and accessibility validation
* - Error recovery and edge cases
*/
test.describe('HVAC Event Creation - Comprehensive Integration', () => {
let hvacTest;
test.beforeEach(async ({ page }) => {
hvacTest = new HVACTestBase(page);
await hvacTest.loginAsTrainer();
await hvacTest.navigateToCreateEvent();
// Wait for all components to be ready
await expect(page.locator('.event-form-container')).toBeVisible();
await page.waitForLoadState('networkidle');
});
test.describe('Complete Event Creation Workflow', () => {
test('should create comprehensive event with all enhanced features', async ({ page }) => {
// Step 1: Basic Event Information
await page.fill('#event_title', 'Advanced HVAC Systems Training');
// Step 2: Rich Text Editor Description
await page.click('#event-description-editor');
await page.keyboard.type('This comprehensive training covers advanced HVAC diagnostic techniques.');
// Apply formatting
await page.keyboard.press('Control+a');
await page.click('[data-command="bold"]');
// Add more content
await page.keyboard.press('ArrowRight');
await page.keyboard.press('Enter');
await page.keyboard.type('Topics include:');
await page.click('[data-command="insertUnorderedList"]');
await page.keyboard.type('System commissioning');
// Verify rich content
const editorContent = await page.locator('#event-description-editor').innerHTML();
expect(editorContent).toContain('<b>');
expect(editorContent).toContain('<ul>');
// Step 3: Featured Image Upload
const imagePath = require('path').join(__dirname, 'fixtures', 'images', 'hvac-training.jpg');
await page.setInputFiles('#featured-image-input', imagePath);
await expect(page.locator('#image-preview img')).toBeVisible();
// Step 4: Multi-select Organizers
await page.click('.organizer-selector input');
await page.locator('.organizer-dropdown .option').first().click();
await page.click('.organizer-selector input');
await page.locator('.organizer-dropdown .option').nth(1).click();
// Verify multiple selections
const organizerCount = await page.locator('.organizer-selector .selected-item').count();
expect(organizerCount).toBe(2);
// Step 5: Multi-select Categories
await page.click('.category-selector input');
await page.locator('.category-dropdown .option').first().click();
// Step 6: Single Venue Selection
await page.click('.venue-selector input');
await page.locator('.venue-dropdown .option').first().click();
await expect(page.locator('.selected-venue .venue-name')).toBeVisible();
// Step 7: Enable Virtual Event
await page.click('.virtual-event-toggle');
await page.fill('#virtual-meeting-url', 'https://zoom.us/j/987654321');
await page.selectOption('#virtual-meeting-platform', 'zoom');
await page.fill('#virtual-meeting-id', '987654321');
await page.fill('#virtual-meeting-password', 'hvac2024');
// Step 8: Enable Ticketing
await page.click('.ticketing-toggle');
await page.fill('#ticket-name', 'Early Bird Registration');
await page.fill('#ticket-price', '149.99');
await page.fill('#ticket-capacity', '50');
// Set sales dates
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
const nextWeek = new Date();
nextWeek.setDate(nextWeek.getDate() + 7);
await page.fill('#ticket-sales-start', tomorrow.toISOString().split('T')[0]);
await page.fill('#ticket-sales-end', nextWeek.toISOString().split('T')[0]);
// Step 9: Set Event Dates and Times
const eventDate = new Date();
eventDate.setDate(eventDate.getDate() + 14);
await page.fill('#event_start_date', eventDate.toISOString().split('T')[0]);
await page.fill('#event_start_time', '09:00');
await page.fill('#event_end_time', '17:00');
// Step 10: Submit Form
await page.click('button[type="submit"]');
// Verify submission success
await expect(page.locator('.success-message')).toContainText('Event created successfully');
// Verify redirect to event management
await expect(page).toHaveURL(/manage-event/);
});
test('should apply template and customize with enhanced features', async ({ page }) => {
// Open template modal
await page.click('.template-selector-btn');
await expect(page.locator('#template-modal')).toBeVisible();
// Select a template
await page.click('.template-option[data-template="manual-j-lidar"]');
await page.click('#apply-template');
// Verify template data applied
const titleValue = await page.locator('#event_title').inputValue();
expect(titleValue).toContain('Manual J LiDAR');
const descriptionContent = await page.locator('#event-description-editor').innerHTML();
expect(descriptionContent).toContain('iPad-based Manual J calculations');
// Customize with enhanced features
// Add featured image
const imagePath = require('path').join(__dirname, 'fixtures', 'images', 'manual-j-training.jpg');
await page.setInputFiles('#featured-image-input', imagePath);
// Enhance description with rich text
await page.click('#event-description-editor');
await page.keyboard.press('Control+a');
await page.click('[data-command="bold"]');
// Add organizer
await page.click('.organizer-selector input');
await page.locator('.organizer-dropdown .option').first().click();
// Enable virtual event with template defaults overridden
await page.click('.virtual-event-toggle');
await page.fill('#virtual-meeting-url', 'https://teams.microsoft.com/custom-meeting');
// Submit customized template
await page.click('button[type="submit"]');
await expect(page.locator('.success-message')).toBeVisible();
});
test('should handle complex multi-step form validation', async ({ page }) => {
// Start filling form with some invalid data
await page.fill('#event_title', 'Te'); // Too short
// Try rich text with XSS attempt
await page.click('#event-description-editor');
await page.keyboard.type('<script>alert("xss")</script>Description');
// Upload invalid file
const invalidFile = require('path').join(__dirname, 'fixtures', 'images', 'malicious-script.js');
if (require('fs').existsSync(invalidFile)) {
await page.setInputFiles('#featured-image-input', invalidFile);
await expect(page.locator('.upload-error')).toContainText('Invalid file type');
}
// Try to exceed organizer limit
await page.click('.organizer-selector input');
for (let i = 0; i < 5; i++) {
const option = page.locator('.organizer-dropdown .option').nth(i);
if (await option.isVisible()) {
await option.click();
await page.click('.organizer-selector input');
}
}
// Should enforce 3 organizer limit
const organizerCount = await page.locator('.organizer-selector .selected-item').count();
expect(organizerCount).toBeLessThanOrEqual(3);
// Enable virtual event with invalid URL
await page.click('.virtual-event-toggle');
await page.fill('#virtual-meeting-url', 'not-a-valid-url');
// Enable ticketing with invalid price
await page.click('.ticketing-toggle');
await page.fill('#ticket-price', '-50');
// Try to submit - should show comprehensive validation
await page.click('button[type="submit"]');
// Verify multiple validation errors
await expect(page.locator('.validation-summary')).toContainText('Please correct the following errors');
await expect(page.locator('#event_title-error')).toBeVisible();
await expect(page.locator('#virtual-meeting-url-error')).toBeVisible();
await expect(page.locator('#ticket-price-error')).toBeVisible();
// Form should remain on page for correction
await expect(page).toHaveURL(/create-event/);
});
});
test.describe('TEC Integration Workflows', () => {
test('should create event with TEC ticketing integration', async ({ page }) => {
// Fill basic event info
await page.fill('#event_title', 'TEC Integration Test Event');
await page.click('#event-description-editor');
await page.keyboard.type('Testing TEC ticketing integration');
// Enable ticketing with TEC-specific fields
await page.click('.ticketing-toggle');
// Configure ticket with TEC fieldset requirements
await page.fill('#ticket-name', 'Standard Registration');
await page.fill('#ticket-price', '99.00');
await page.fill('#ticket-capacity', '100');
// TEC fieldset integration - mandatory attendee fields
await expect(page.locator('.tec-attendee-fields')).toBeVisible();
await expect(page.locator('#require-attendee-info')).toBeChecked(); // Should be mandatory
// Verify TEC fieldset fields are present
await expect(page.locator('.fieldset-field[data-field="first_name"]')).toBeVisible();
await expect(page.locator('.fieldset-field[data-field="last_name"]')).toBeVisible();
// Set event date and venue (required for TEC)
const eventDate = new Date();
eventDate.setDate(eventDate.getDate() + 7);
await page.fill('#event_start_date', eventDate.toISOString().split('T')[0]);
await page.click('.venue-selector input');
await page.locator('.venue-dropdown .option').first().click();
// Mock TEC API response for ticket creation
await page.route('**/wp-admin/admin-ajax.php', async route => {
const postData = route.request().postData() || '';
if (postData.includes('create_tec_event')) {
await route.fulfill({
contentType: 'application/json',
body: JSON.stringify({
success: true,
data: {
event_id: 12345,
ticket_id: 67890,
tec_url: '/events/tec-integration-test-event/'
}
})
});
} else {
await route.continue();
}
});
// Submit and verify TEC integration
await page.click('button[type="submit"]');
await expect(page.locator('.success-message')).toContainText('Event created with TEC integration');
// Should redirect to TEC management interface
await expect(page).toHaveURL(/tec-manage-event/);
});
test('should handle TEC API failures gracefully', async ({ page }) => {
await page.fill('#event_title', 'TEC Failure Test');
await page.click('.ticketing-toggle');
await page.fill('#ticket-price', '50.00');
// Mock TEC API failure
await page.route('**/wp-admin/admin-ajax.php', route => {
if (route.request().postData()?.includes('create_tec_event')) {
route.fulfill({
status: 500,
body: 'TEC API Error'
});
} else {
route.continue();
}
});
await page.click('button[type="submit"]');
// Should show fallback options
await expect(page.locator('.tec-error-fallback')).toBeVisible();
await expect(page.locator('.create-without-tickets-btn')).toBeVisible();
await expect(page.locator('.retry-tec-integration-btn')).toBeVisible();
// Test retry functionality
await page.click('.retry-tec-integration-btn');
await expect(page.locator('.integration-retry-status')).toBeVisible();
});
test('should validate TEC fieldset requirements', async ({ page }) => {
await page.fill('#event_title', 'Fieldset Validation Test');
await page.click('.ticketing-toggle');
// Mock fieldset data from Post ID 6235
await page.addInitScript(() => {
window.tecFieldsetData = {
6235: {
fields: [
{ name: 'first_name', required: true, type: 'text' },
{ name: 'last_name', required: true, type: 'text' },
{ name: 'company', required: false, type: 'text' },
{ name: 'experience_level', required: true, type: 'select' }
]
}
};
});
// Should show fieldset validation
await expect(page.locator('.tec-fieldset-validation')).toBeVisible();
// Required fields should be marked
await expect(page.locator('[data-field="first_name"] .required-indicator')).toBeVisible();
await expect(page.locator('[data-field="last_name"] .required-indicator')).toBeVisible();
// Optional fields should not be marked required
await expect(page.locator('[data-field="company"] .required-indicator')).not.toBeVisible();
});
});
test.describe('Responsive Layout Behavior', () => {
test('should adapt to mobile viewport', async ({ page }) => {
// Test desktop layout first
await page.setViewportSize({ width: 1200, height: 800 });
await page.reload();
await hvacTest.navigateToCreateEvent();
// Verify desktop layout - fields should be in rows
const priceCapacityRow = page.locator('.form-row.price-capacity');
await expect(priceCapacityRow.locator('.form-field')).toHaveCount(2);
// Switch to mobile viewport
await page.setViewportSize({ width: 375, height: 667 });
// Fields should stack vertically
const priceField = page.locator('#ticket-price');
const capacityField = page.locator('#ticket-capacity');
const priceBox = await priceField.boundingBox();
const capacityBox = await capacityField.boundingBox();
if (priceBox && capacityBox) {
expect(capacityBox.y).toBeGreaterThan(priceBox.y + priceBox.height);
}
});
test('should maintain functionality on touch devices', async ({ page }) => {
// Simulate touch device
await page.setViewportSize({ width: 768, height: 1024 });
// Test touch interactions with dropdowns
await page.tap('.organizer-selector input');
await expect(page.locator('.organizer-dropdown')).toBeVisible();
await page.tap('.organizer-dropdown .option:first-child');
await expect(page.locator('.organizer-selector .selected-item')).toBeVisible();
// Test touch interaction with toggles
await page.tap('.virtual-event-toggle');
await expect(page.locator('.virtual-event-config')).toBeVisible();
// Test modal interaction on touch
await page.tap('.organizer-selector .create-new-btn');
await expect(page.locator('#organizer-modal')).toBeVisible();
// Touch outside to close modal
await page.tap('#organizer-modal .modal-backdrop', { position: { x: 10, y: 10 } });
await expect(page.locator('#organizer-modal')).not.toBeVisible();
});
test('should maintain text editor functionality on mobile', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 });
// Rich text editor should work on mobile
await page.tap('#event-description-editor');
await page.keyboard.type('Mobile text editing test');
// Toolbar should be accessible
await page.tap('[data-command="bold"]');
const editorContent = await page.locator('#event-description-editor').innerHTML();
expect(editorContent).toContain('<b>');
// Virtual keyboard should not interfere
await page.keyboard.press('Enter');
await page.keyboard.type('New line text');
expect(await page.locator('#event-description-editor').textContent()).toContain('New line text');
});
});
test.describe('Performance and Load Testing', () => {
test('should handle large datasets efficiently', async ({ page }) => {
// Mock large datasets
await page.addInitScript(() => {
window.mockLargeDatasets = true;
window.organizerCount = 500;
window.venueCount = 200;
window.categoryCount = 100;
});
// Measure selector performance
const startTime = Date.now();
await page.click('.organizer-selector input');
await expect(page.locator('.organizer-dropdown')).toBeVisible();
const loadTime = Date.now() - startTime;
expect(loadTime).toBeLessThan(2000); // Should load within 2 seconds
// Search should be responsive
const searchStart = Date.now();
await page.fill('.organizer-selector input', 'search-term');
await expect(page.locator('.organizer-dropdown .option:visible')).toHaveCount({ min: 1 });
const searchTime = Date.now() - searchStart;
expect(searchTime).toBeLessThan(500); // Search should be near-instant
});
test('should not leak memory during extended use', async ({ page }) => {
// Simulate extended use patterns
for (let i = 0; i < 10; i++) {
// Open and close modals repeatedly
await page.click('.organizer-selector .create-new-btn');
await page.keyboard.press('Escape');
// Toggle features repeatedly
await page.click('.virtual-event-toggle');
await page.click('.virtual-event-toggle');
// Clear and set form data
await page.fill('#event_title', `Test Event ${i}`);
await page.fill('#event_title', '');
}
// Form should remain responsive
await page.click('.organizer-selector input');
await expect(page.locator('.organizer-dropdown')).toBeVisible();
});
test('should optimize network requests', async ({ page }) => {
const networkRequests = [];
page.on('request', request => {
if (request.url().includes('admin-ajax.php')) {
networkRequests.push(request.url());
}
});
// Perform actions that could trigger multiple requests
await page.click('.organizer-selector input'); // Load organizers
await page.click('.category-selector input'); // Load categories
await page.click('.venue-selector input'); // Load venues
// Should batch or cache requests efficiently
const uniqueRequests = [...new Set(networkRequests)];
expect(networkRequests.length).toBeLessThan(10); // Reasonable limit
// Repeated actions should use cache
await page.click('.organizer-selector input');
const requestsAfterCache = networkRequests.length;
await page.click('.organizer-selector input');
expect(networkRequests.length).toBe(requestsAfterCache); // No new requests
});
});
test.describe('Accessibility Comprehensive', () => {
test('should support full keyboard navigation workflow', async ({ page }) => {
// Start keyboard navigation from beginning
await page.keyboard.press('Tab'); // Title field
let activeElement = await page.evaluate(() => document.activeElement.id);
expect(activeElement).toBe('event_title');
// Continue through all interactive elements
const expectedOrder = [
'event_title',
'event-description-editor',
'featured-image-input',
'organizer-selector',
'category-selector',
'venue-selector',
'virtual-event-toggle',
'rsvp-toggle',
'ticketing-toggle'
];
for (let i = 1; i < expectedOrder.length; i++) {
await page.keyboard.press('Tab');
activeElement = await page.evaluate(() =>
document.activeElement.id || document.activeElement.className
);
expect(activeElement).toContain(expectedOrder[i]);
}
});
test('should provide comprehensive screen reader support', async ({ page }) => {
// Check for ARIA landmarks
await expect(page.locator('[role="main"]')).toBeVisible();
await expect(page.locator('[role="form"]')).toBeVisible();
// Check form sections have proper headings
await expect(page.locator('h2, h3').filter({ hasText: 'Event Details' })).toBeVisible();
await expect(page.locator('h2, h3').filter({ hasText: 'Virtual Event Configuration' })).toBeVisible();
// Check for live regions
await expect(page.locator('[aria-live="polite"]')).toBeVisible();
await expect(page.locator('[aria-live="assertive"]')).toBeVisible();
// Test dynamic content announcements
await page.click('.virtual-event-toggle');
await expect(page.locator('[aria-live="polite"]')).toContainText('Virtual event section expanded');
});
test('should meet WCAG 2.1 AA standards', async ({ page }) => {
// Color contrast - check for sufficient contrast ratios
const contrastIssues = await page.evaluate(() => {
// This would typically use axe-core or similar tool
const issues = [];
const buttons = document.querySelectorAll('button');
buttons.forEach(btn => {
const style = getComputedStyle(btn);
// Simplified contrast check
if (style.backgroundColor === style.color) {
issues.push(`Button has insufficient contrast: ${btn.textContent}`);
}
});
return issues;
});
expect(contrastIssues.length).toBe(0);
// Focus indicators should be visible
await page.keyboard.press('Tab');
const hasFocusIndicator = await page.evaluate(() => {
const focused = document.activeElement;
const style = getComputedStyle(focused);
return style.outline !== 'none' || style.boxShadow !== 'none';
});
expect(hasFocusIndicator).toBe(true);
// Text should be resizable to 200% without loss of functionality
await page.evaluate(() => {
document.body.style.fontSize = '200%';
});
// Form should still be usable
await expect(page.locator('#event_title')).toBeVisible();
await expect(page.locator('.submit-btn')).toBeVisible();
});
});
test.describe('Error Recovery and Edge Cases', () => {
test('should recover from network interruptions', async ({ page }) => {
await page.fill('#event_title', 'Network Recovery Test');
// Simulate network failure during form submission
await page.route('**/wp-admin/admin-ajax.php', route => route.abort());
await page.click('button[type="submit"]');
// Should show network error
await expect(page.locator('.network-error')).toBeVisible();
await expect(page.locator('.retry-btn')).toBeVisible();
// Restore network
await page.unroute('**/wp-admin/admin-ajax.php');
// Retry should work
await page.click('.retry-btn');
await expect(page.locator('.success-message')).toBeVisible();
});
test('should handle browser storage limitations', async ({ page }) => {
// Fill form with large amounts of data
await page.fill('#event_title', 'Storage Limit Test');
const largeDescription = 'A'.repeat(50000); // 50KB description
await page.click('#event-description-editor');
await page.keyboard.type(largeDescription.substring(0, 1000)); // Type subset due to performance
// Should handle gracefully without breaking
await expect(page.locator('#event-description-editor')).toBeVisible();
// Autosave should work or show appropriate warning
const hasAutosave = await page.locator('.autosave-status').isVisible();
const hasStorageWarning = await page.locator('.storage-warning').isVisible();
expect(hasAutosave || hasStorageWarning).toBe(true);
});
test('should handle concurrent user modifications', async ({ page }) => {
await page.fill('#event_title', 'Concurrent Modification Test');
// Simulate another user modifying the same resource
await page.evaluate(() => {
// Mock concurrent modification
window.dispatchEvent(new CustomEvent('concurrent-modification-detected', {
detail: { resource: 'event_draft', modifiedBy: 'another_user' }
}));
});
// Should show conflict resolution interface
await expect(page.locator('.conflict-resolution')).toBeVisible();
await expect(page.locator('.merge-changes-btn')).toBeVisible();
await expect(page.locator('.override-changes-btn')).toBeVisible();
// Test conflict resolution
await page.click('.override-changes-btn');
await expect(page.locator('.conflict-resolved')).toBeVisible();
});
test('should maintain data integrity during browser crashes', async ({ page }) => {
// Fill comprehensive form data
await page.fill('#event_title', 'Crash Recovery Test');
await page.click('#event-description-editor');
await page.keyboard.type('Important event description');
// Enable features and fill data
await page.click('.virtual-event-toggle');
await page.fill('#virtual-meeting-url', 'https://zoom.us/j/recovery-test');
await page.click('.ticketing-toggle');
await page.fill('#ticket-price', '75.00');
// Simulate browser crash/refresh
await page.reload();
await hvacTest.navigateToCreateEvent();
// Data should be recovered from autosave or draft
const recoveredTitle = await page.locator('#event_title').inputValue();
const isVirtualEnabled = await page.locator('.virtual-event-toggle').isChecked();
// Should have some form of data recovery
expect(recoveredTitle === 'Crash Recovery Test' ||
await page.locator('.recover-draft-btn').isVisible()).toBe(true);
if (isVirtualEnabled) {
const recoveredUrl = await page.locator('#virtual-meeting-url').inputValue();
expect(recoveredUrl).toBe('https://zoom.us/j/recovery-test');
}
});
});
});