This commit implements Phase 1 of the Communication Schedule system, providing: Core Infrastructure: - HVAC_Communication_Scheduler: Main controller with cron integration and AJAX handlers - HVAC_Communication_Schedule_Manager: CRUD operations and database interactions - HVAC_Communication_Trigger_Engine: Automation logic and recipient management - HVAC_Communication_Logger: Execution logging and performance tracking - HVAC_Communication_Installer: Database table creation and management Features: - Event-based triggers (before/after event, on registration) - Custom date scheduling with recurring options - Flexible recipient targeting (all attendees, confirmed, custom lists) - Template integration with placeholder replacement - WordPress cron integration for automated execution - Comprehensive AJAX API for schedule management - Template quickstart options for common scenarios UI Components: - Communication Schedules page with full management interface - Form-based schedule creation with validation - Schedule listing with filtering and status management - Modal recipient preview functionality - Pre-configured schedule templates for quick setup Database Design: - hvac_communication_schedules: Schedule configurations - hvac_communication_logs: Execution history and statistics - hvac_event_communication_tracking: Individual email tracking The system integrates with existing email templates and provides a foundation for automated communication workflows for HVAC trainers. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
556 lines
No EOL
21 KiB
TypeScript
556 lines
No EOL
21 KiB
TypeScript
import { test, expect } from './fixtures/auth';
|
|
import { CommonActions } from './utils/common-actions';
|
|
|
|
/**
|
|
* Communication Templates Validation E2E Tests
|
|
*
|
|
* Comprehensive tests for CRUD operations, AJAX functionality,
|
|
* and template management system
|
|
*/
|
|
|
|
test.describe('Communication Templates Full Validation', () => {
|
|
|
|
test('Navigate to templates page and verify scripts load correctly', async ({ authenticatedPage: page }) => {
|
|
test.setTimeout(30000);
|
|
const actions = new CommonActions(page);
|
|
|
|
// Monitor console for JavaScript errors
|
|
const jsErrors: string[] = [];
|
|
page.on('console', (msg) => {
|
|
if (msg.type() === 'error') {
|
|
jsErrors.push(msg.text());
|
|
}
|
|
});
|
|
|
|
// Navigate to communication templates page (critical for script loading)
|
|
await actions.navigateAndWait('/communication-templates/');
|
|
await actions.screenshot('templates-page-initial-load');
|
|
|
|
// Verify page loaded correctly
|
|
await expect(page.locator('h1')).toContainText('Communication Templates');
|
|
await expect(page.locator('.hvac-templates-wrapper')).toBeVisible();
|
|
|
|
// Check that hvacTemplates object is initialized with ajaxUrl
|
|
const hvacTemplatesConfig = await page.evaluate(() => {
|
|
if (typeof window.hvacTemplates !== 'undefined') {
|
|
return {
|
|
exists: true,
|
|
hasAjaxUrl: !!window.hvacTemplates.ajaxUrl,
|
|
ajaxUrl: window.hvacTemplates.ajaxUrl
|
|
};
|
|
}
|
|
return { exists: false };
|
|
});
|
|
|
|
expect(hvacTemplatesConfig.exists).toBe(true);
|
|
expect(hvacTemplatesConfig.hasAjaxUrl).toBe(true);
|
|
console.log(`✓ hvacTemplates object loaded with AJAX URL: ${hvacTemplatesConfig.ajaxUrl}`);
|
|
|
|
// Verify no critical JavaScript errors
|
|
const criticalErrors = jsErrors.filter(error =>
|
|
error.includes('hvacTemplates') ||
|
|
error.includes('communication-templates') ||
|
|
error.includes('Uncaught')
|
|
);
|
|
expect(criticalErrors.length).toBe(0);
|
|
});
|
|
|
|
test('Create new template - Full CRUD operation', async ({ authenticatedPage: page }) => {
|
|
test.setTimeout(45000);
|
|
const actions = new CommonActions(page);
|
|
|
|
// Navigate to templates page first (required for scripts)
|
|
await actions.navigateAndWait('/communication-templates/');
|
|
await actions.screenshot('templates-create-start');
|
|
|
|
// Generate unique test data
|
|
const testData = actions.generateTestData('Template');
|
|
const templateData = {
|
|
title: testData.title,
|
|
content: `Dear {attendee_name},\n\nThank you for registering for {event_title}.\n\nEvent Details:\n- Date: {event_date}\n- Time: {event_time}\n- Location: {venue_name}\n\nBest regards,\n{trainer_name}`,
|
|
category: 'registration'
|
|
};
|
|
|
|
// Check if we need to install default templates first
|
|
const gettingStarted = page.locator('.hvac-getting-started');
|
|
if (await gettingStarted.isVisible()) {
|
|
const installButton = page.locator('a:has-text("Install Default Templates")');
|
|
if (await installButton.isVisible()) {
|
|
await installButton.click();
|
|
await actions.waitForComplexAjax();
|
|
await page.waitForTimeout(2000); // Wait for templates to load
|
|
await actions.screenshot('default-templates-installed');
|
|
}
|
|
}
|
|
|
|
// Click create new template button
|
|
const createButton = page.locator('button:has-text("Create New Template")');
|
|
await expect(createButton).toBeVisible();
|
|
await createButton.click();
|
|
await page.waitForTimeout(1000);
|
|
await actions.screenshot('template-form-opened');
|
|
|
|
// Fill in template form
|
|
await page.fill('#hvac_template_title', templateData.title);
|
|
|
|
// Try to fill content (handle TinyMCE or textarea)
|
|
const contentField = page.locator('#hvac_template_content');
|
|
if (await contentField.isVisible()) {
|
|
await contentField.fill(templateData.content);
|
|
} else {
|
|
// Try TinyMCE
|
|
await actions.fillTinyMCE('#hvac_template_content', templateData.content);
|
|
}
|
|
|
|
// Select category
|
|
await page.selectOption('#hvac_template_category', templateData.category);
|
|
await actions.screenshot('template-form-filled');
|
|
|
|
// Test placeholder insertion
|
|
const placeholderItems = page.locator('.hvac-placeholder-item');
|
|
if (await placeholderItems.count() > 0) {
|
|
// Click a placeholder to test insertion
|
|
await placeholderItems.filter({ hasText: '{venue_address}' }).click();
|
|
await page.waitForTimeout(500);
|
|
console.log('✓ Placeholder insertion tested');
|
|
}
|
|
|
|
// Save the template
|
|
const saveButton = page.locator('button:has-text("Save Template")');
|
|
await expect(saveButton).toBeVisible();
|
|
await saveButton.click();
|
|
|
|
// Wait for AJAX save operation
|
|
await actions.waitForComplexAjax();
|
|
await actions.screenshot('template-saved');
|
|
|
|
// Verify template was created
|
|
await page.waitForTimeout(2000); // Allow UI to update
|
|
const templateCard = page.locator('.hvac-template-card').filter({ hasText: testData.title });
|
|
await expect(templateCard).toBeVisible();
|
|
|
|
console.log(`✓ Template created successfully: ${testData.title}`);
|
|
return testData; // Return for use in other tests
|
|
});
|
|
|
|
test('Edit existing template', async ({ authenticatedPage: page }) => {
|
|
test.setTimeout(45000);
|
|
const actions = new CommonActions(page);
|
|
|
|
// Navigate to templates page
|
|
await actions.navigateAndWait('/communication-templates/');
|
|
await actions.screenshot('templates-edit-start');
|
|
|
|
// Find a template to edit
|
|
const templateCards = page.locator('.hvac-template-card');
|
|
const cardCount = await templateCards.count();
|
|
|
|
if (cardCount > 0) {
|
|
// Click edit on the first template
|
|
const firstCard = templateCards.first();
|
|
const editButton = firstCard.locator('button:has-text("Edit")');
|
|
|
|
if (await editButton.isVisible()) {
|
|
// Get original title for comparison
|
|
const originalTitle = await firstCard.locator('.hvac-template-card-title').textContent();
|
|
|
|
await editButton.click();
|
|
await page.waitForTimeout(1000);
|
|
await actions.screenshot('template-edit-form-opened');
|
|
|
|
// Modify the template
|
|
const titleField = page.locator('#hvac_template_title');
|
|
await expect(titleField).toBeVisible();
|
|
|
|
const updatedTitle = `Updated - ${originalTitle} - ${Date.now()}`;
|
|
await titleField.fill(updatedTitle);
|
|
|
|
// Update content
|
|
const contentField = page.locator('#hvac_template_content');
|
|
if (await contentField.isVisible()) {
|
|
const currentContent = await contentField.inputValue();
|
|
await contentField.fill(currentContent + '\n\nUpdated on: ' + new Date().toISOString());
|
|
}
|
|
|
|
await actions.screenshot('template-edit-form-modified');
|
|
|
|
// Save changes
|
|
const saveButton = page.locator('button:has-text("Save Template")');
|
|
await saveButton.click();
|
|
await actions.waitForComplexAjax();
|
|
await actions.screenshot('template-edit-saved');
|
|
|
|
// Verify update
|
|
await page.waitForTimeout(2000);
|
|
const updatedCard = page.locator('.hvac-template-card').filter({ hasText: updatedTitle });
|
|
await expect(updatedCard).toBeVisible();
|
|
|
|
console.log('✓ Template edited successfully');
|
|
} else {
|
|
console.log('⚠ No edit button found on template cards');
|
|
}
|
|
} else {
|
|
console.log('⚠ No templates available to edit');
|
|
}
|
|
});
|
|
|
|
test('Delete template', async ({ authenticatedPage: page }) => {
|
|
test.setTimeout(45000);
|
|
const actions = new CommonActions(page);
|
|
|
|
// Navigate to templates page
|
|
await actions.navigateAndWait('/communication-templates/');
|
|
await actions.screenshot('templates-delete-start');
|
|
|
|
// Find a template to delete (preferably a test template)
|
|
const templateCards = page.locator('.hvac-template-card');
|
|
const testTemplateCard = templateCards.filter({ hasText: /Template \d+/ }).first();
|
|
|
|
if (await testTemplateCard.isVisible()) {
|
|
const templateTitle = await testTemplateCard.locator('.hvac-template-card-title').textContent();
|
|
const deleteButton = testTemplateCard.locator('button:has-text("Delete")');
|
|
|
|
if (await deleteButton.isVisible()) {
|
|
// Set up dialog handler for confirmation
|
|
page.once('dialog', async dialog => {
|
|
console.log(`Dialog message: ${dialog.message()}`);
|
|
await dialog.accept();
|
|
});
|
|
|
|
await deleteButton.click();
|
|
await actions.waitForComplexAjax();
|
|
await actions.screenshot('template-deleted');
|
|
|
|
// Verify template was removed
|
|
await page.waitForTimeout(2000);
|
|
const deletedCard = page.locator('.hvac-template-card').filter({ hasText: templateTitle });
|
|
await expect(deletedCard).not.toBeVisible();
|
|
|
|
console.log(`✓ Template deleted successfully: ${templateTitle}`);
|
|
} else {
|
|
console.log('⚠ No delete button found on test template');
|
|
}
|
|
} else {
|
|
console.log('⚠ No test templates available to delete');
|
|
}
|
|
});
|
|
|
|
test('Load saved templates in email attendees page', async ({ authenticatedPage: page }) => {
|
|
test.setTimeout(45000);
|
|
const actions = new CommonActions(page);
|
|
|
|
// First navigate to dashboard to find an event
|
|
await actions.navigateAndWait('/hvac-dashboard/');
|
|
|
|
// Look for events with email functionality
|
|
const emailLinks = page.locator('a[href*="email-attendees"]');
|
|
const linkCount = await emailLinks.count();
|
|
|
|
if (linkCount > 0) {
|
|
// Navigate to email attendees page
|
|
await emailLinks.first().click();
|
|
await page.waitForLoadState('networkidle');
|
|
await actions.screenshot('email-attendees-page');
|
|
|
|
// Check for template widget
|
|
const templateToggle = page.locator('.hvac-template-toggle');
|
|
if (await templateToggle.isVisible()) {
|
|
await templateToggle.click();
|
|
await page.waitForTimeout(1000);
|
|
await actions.screenshot('template-widget-opened');
|
|
|
|
// Check if templates are loaded
|
|
const templateSelect = page.locator('#hvac_template_select, select[name="template_select"]');
|
|
if (await templateSelect.isVisible()) {
|
|
const options = await templateSelect.locator('option').count();
|
|
expect(options).toBeGreaterThan(1); // Should have more than just default option
|
|
|
|
// Select a template
|
|
await templateSelect.selectOption({ index: 1 });
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Click load template button
|
|
const loadButton = page.locator('button:has-text("Load Template")');
|
|
if (await loadButton.isVisible()) {
|
|
await loadButton.click();
|
|
await actions.waitForAjax();
|
|
await actions.screenshot('template-loaded');
|
|
|
|
// Verify content was loaded into email form
|
|
const emailContent = page.locator('#email_message, textarea[name="email_message"]');
|
|
const content = await emailContent.inputValue();
|
|
expect(content.length).toBeGreaterThan(0);
|
|
|
|
console.log('✓ Template loaded successfully in email form');
|
|
}
|
|
} else {
|
|
console.log('⚠ Template select dropdown not found');
|
|
}
|
|
} else {
|
|
console.log('⚠ Template widget not found on email attendees page');
|
|
}
|
|
} else {
|
|
console.log('⚠ No events with email functionality found');
|
|
}
|
|
});
|
|
|
|
test('Test placeholder functionality in templates', async ({ authenticatedPage: page }) => {
|
|
test.setTimeout(30000);
|
|
const actions = new CommonActions(page);
|
|
|
|
// Navigate to templates page
|
|
await actions.navigateAndWait('/communication-templates/');
|
|
await actions.screenshot('placeholder-test-start');
|
|
|
|
// Open create template form
|
|
const createButton = page.locator('button:has-text("Create New Template")');
|
|
if (await createButton.isVisible()) {
|
|
await createButton.click();
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Check placeholder helper is visible
|
|
const placeholderHelper = page.locator('.hvac-placeholder-helper');
|
|
await expect(placeholderHelper).toBeVisible();
|
|
|
|
// Test all available placeholders
|
|
const placeholders = [
|
|
'{attendee_name}',
|
|
'{attendee_email}',
|
|
'{event_title}',
|
|
'{event_date}',
|
|
'{event_time}',
|
|
'{venue_name}',
|
|
'{venue_address}',
|
|
'{trainer_name}',
|
|
'{trainer_email}',
|
|
'{certificate_link}'
|
|
];
|
|
|
|
// Verify placeholders are displayed
|
|
for (const placeholder of placeholders) {
|
|
const placeholderItem = page.locator('.hvac-placeholder-item').filter({ hasText: placeholder });
|
|
if (await placeholderItem.isVisible()) {
|
|
console.log(`✓ Placeholder ${placeholder} is available`);
|
|
}
|
|
}
|
|
|
|
// Test clicking placeholders to insert
|
|
const contentField = page.locator('#hvac_template_content');
|
|
await contentField.clear();
|
|
|
|
// Click multiple placeholders
|
|
await page.locator('.hvac-placeholder-item').filter({ hasText: '{attendee_name}' }).click();
|
|
await page.waitForTimeout(300);
|
|
await page.locator('.hvac-placeholder-item').filter({ hasText: '{event_title}' }).click();
|
|
await page.waitForTimeout(300);
|
|
|
|
// Verify placeholders were inserted
|
|
const content = await contentField.inputValue();
|
|
expect(content).toContain('{attendee_name}');
|
|
expect(content).toContain('{event_title}');
|
|
|
|
await actions.screenshot('placeholders-inserted');
|
|
console.log('✓ Placeholder insertion functionality working');
|
|
}
|
|
});
|
|
|
|
test('Verify AJAX operations work correctly', async ({ authenticatedPage: page }) => {
|
|
test.setTimeout(45000);
|
|
const actions = new CommonActions(page);
|
|
|
|
// Monitor network requests
|
|
const ajaxRequests: string[] = [];
|
|
page.on('request', request => {
|
|
if (request.url().includes('admin-ajax.php')) {
|
|
ajaxRequests.push(request.postData() || 'GET request');
|
|
}
|
|
});
|
|
|
|
// Navigate to templates page
|
|
await actions.navigateAndWait('/communication-templates/');
|
|
|
|
// Test various AJAX operations
|
|
|
|
// 1. Test loading templates (should happen on page load)
|
|
await page.waitForTimeout(2000);
|
|
const loadRequests = ajaxRequests.filter(req => req.includes('hvac_get_templates'));
|
|
expect(loadRequests.length).toBeGreaterThan(0);
|
|
console.log(`✓ Template loading AJAX requests: ${loadRequests.length}`);
|
|
|
|
// 2. Test creating a template via AJAX
|
|
const createButton = page.locator('button:has-text("Create New Template")');
|
|
if (await createButton.isVisible()) {
|
|
await createButton.click();
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Fill minimal data
|
|
await page.fill('#hvac_template_title', `AJAX Test ${Date.now()}`);
|
|
await page.fill('#hvac_template_content', 'Testing AJAX save functionality');
|
|
await page.selectOption('#hvac_template_category', 'general');
|
|
|
|
// Clear previous requests
|
|
ajaxRequests.length = 0;
|
|
|
|
// Save template
|
|
const saveButton = page.locator('button:has-text("Save Template")');
|
|
await saveButton.click();
|
|
await actions.waitForComplexAjax();
|
|
|
|
// Check for save AJAX request
|
|
const saveRequests = ajaxRequests.filter(req => req.includes('hvac_save_template'));
|
|
expect(saveRequests.length).toBeGreaterThan(0);
|
|
console.log('✓ Template save AJAX request successful');
|
|
}
|
|
|
|
// 3. Test loading template in email form (if available)
|
|
await actions.navigateAndWait('/hvac-dashboard/');
|
|
const emailLink = page.locator('a[href*="email-attendees"]').first();
|
|
|
|
if (await emailLink.isVisible()) {
|
|
await emailLink.click();
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const templateToggle = page.locator('.hvac-template-toggle');
|
|
if (await templateToggle.isVisible()) {
|
|
await templateToggle.click();
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Clear requests
|
|
ajaxRequests.length = 0;
|
|
|
|
// Load a template
|
|
const templateSelect = page.locator('#hvac_template_select, select[name="template_select"]');
|
|
if (await templateSelect.isVisible() && await templateSelect.locator('option').count() > 1) {
|
|
await templateSelect.selectOption({ index: 1 });
|
|
const loadButton = page.locator('button:has-text("Load Template")');
|
|
|
|
if (await loadButton.isVisible()) {
|
|
await loadButton.click();
|
|
await actions.waitForAjax();
|
|
|
|
// Check for load template AJAX request
|
|
const loadTemplateRequests = ajaxRequests.filter(req =>
|
|
req.includes('hvac_load_template') || req.includes('hvac_get_template')
|
|
);
|
|
expect(loadTemplateRequests.length).toBeGreaterThan(0);
|
|
console.log('✓ Template load in email form AJAX request successful');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
await actions.screenshot('ajax-operations-complete');
|
|
console.log(`✓ Total AJAX requests captured: ${ajaxRequests.length}`);
|
|
});
|
|
|
|
test('Verify template categories work correctly', async ({ authenticatedPage: page }) => {
|
|
test.setTimeout(30000);
|
|
const actions = new CommonActions(page);
|
|
|
|
// Navigate to templates page
|
|
await actions.navigateAndWait('/communication-templates/');
|
|
await actions.screenshot('categories-test-start');
|
|
|
|
// Check if category filter exists
|
|
const categoryFilter = page.locator('.hvac-category-filter, select[name="category_filter"]');
|
|
if (await categoryFilter.isVisible()) {
|
|
// Get available categories
|
|
const options = await categoryFilter.locator('option').allTextContents();
|
|
console.log(`Available categories: ${options.join(', ')}`);
|
|
|
|
// Test filtering by each category
|
|
for (let i = 1; i < Math.min(options.length, 4); i++) { // Test up to 3 categories
|
|
await categoryFilter.selectOption({ index: i });
|
|
await actions.waitForAjax();
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Check if templates are filtered
|
|
const visibleCards = await page.locator('.hvac-template-card:visible').count();
|
|
console.log(`✓ Category "${options[i]}" shows ${visibleCards} templates`);
|
|
|
|
await actions.screenshot(`category-filter-${options[i].toLowerCase()}`);
|
|
}
|
|
|
|
// Reset to all categories
|
|
await categoryFilter.selectOption({ index: 0 });
|
|
await actions.waitForAjax();
|
|
} else {
|
|
console.log('⚠ Category filter not found - may not have enough templates');
|
|
}
|
|
});
|
|
|
|
test('Full end-to-end template workflow', async ({ authenticatedPage: page }) => {
|
|
test.setTimeout(60000);
|
|
const actions = new CommonActions(page);
|
|
|
|
console.log('Starting full end-to-end template workflow test...');
|
|
|
|
// Step 1: Navigate to templates page
|
|
await actions.navigateAndWait('/communication-templates/');
|
|
await actions.screenshot('e2e-workflow-start');
|
|
|
|
// Step 2: Create a new template
|
|
const testData = actions.generateTestData('E2E Template');
|
|
const createButton = page.locator('button:has-text("Create New Template")');
|
|
|
|
if (await createButton.isVisible()) {
|
|
await createButton.click();
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Fill template with placeholders
|
|
await page.fill('#hvac_template_title', testData.title);
|
|
await page.fill('#hvac_template_content',
|
|
`Hello {attendee_name},\n\nThis is a test template for {event_title}.\n\nSee you at {venue_name}!\n\n{trainer_name}`
|
|
);
|
|
await page.selectOption('#hvac_template_category', 'reminder');
|
|
|
|
// Save template
|
|
await page.locator('button:has-text("Save Template")').click();
|
|
await actions.waitForComplexAjax();
|
|
console.log('✓ Template created');
|
|
}
|
|
|
|
// Step 3: Navigate to email attendees and use the template
|
|
await actions.navigateAndWait('/hvac-dashboard/');
|
|
const emailLink = page.locator('a[href*="email-attendees"]').first();
|
|
|
|
if (await emailLink.isVisible()) {
|
|
await emailLink.click();
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Open template widget
|
|
const templateToggle = page.locator('.hvac-template-toggle');
|
|
if (await templateToggle.isVisible()) {
|
|
await templateToggle.click();
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Select our created template
|
|
const templateSelect = page.locator('#hvac_template_select, select[name="template_select"]');
|
|
if (await templateSelect.isVisible()) {
|
|
// Find our template in the dropdown
|
|
const optionWithText = page.locator(`option:has-text("${testData.title}")`);
|
|
if (await optionWithText.count() > 0) {
|
|
const optionValue = await optionWithText.getAttribute('value');
|
|
await templateSelect.selectOption(optionValue);
|
|
|
|
// Load the template
|
|
await page.locator('button:has-text("Load Template")').click();
|
|
await actions.waitForAjax();
|
|
|
|
// Verify content loaded
|
|
const emailContent = page.locator('#email_message, textarea[name="email_message"]');
|
|
const loadedContent = await emailContent.inputValue();
|
|
expect(loadedContent).toContain('{attendee_name}');
|
|
expect(loadedContent).toContain('{event_title}');
|
|
|
|
console.log('✓ Template loaded and ready for use');
|
|
await actions.screenshot('e2e-workflow-complete');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
console.log('✓ Full end-to-end workflow completed successfully');
|
|
});
|
|
|
|
}); |