- 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>
649 lines
No EOL
29 KiB
JavaScript
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');
|
|
}
|
|
});
|
|
});
|
|
}); |