- Created admin page for direct event seeding (admin/seed-events-direct.php)
- Added test admin user creation script with master trainer roles
- Implemented comprehensive Playwright tests for event edit workflow
- Verified field population with TEC v5.0.8
- Confirmed 11 core fields properly populate in edit forms
- Added XWayland display configuration for headed browser testing
- Created seeding scripts that add events with complete metadata
Test Results:
- Login functionality: Working
- Event access: 20+ events accessible
- Field population: 11 essential fields confirmed
- Edit workflow: Functional with TEC Community Events
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
		
	
			
		
			
				
	
	
		
			514 lines
		
	
	
		
			No EOL
		
	
	
		
			18 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			514 lines
		
	
	
		
			No EOL
		
	
	
		
			18 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable file
		
	
	
	
	
| #!/usr/bin/env node
 | ||
| 
 | ||
| /**
 | ||
|  * Complete Event Edit Workflow Test
 | ||
|  * Tests the FULL edit cycle: access, populate, edit, save, verify
 | ||
|  */
 | ||
| 
 | ||
| const { chromium } = require('@playwright/test');
 | ||
| const fs = require('fs').promises;
 | ||
| 
 | ||
| const CONFIG = {
 | ||
|     baseUrl: 'https://upskill-staging.measurequick.com',
 | ||
|     credentials: {
 | ||
|         email: 'test_trainer@example.com',
 | ||
|         password: 'TestTrainer123!'
 | ||
|     }
 | ||
| };
 | ||
| 
 | ||
| // Track field values before and after
 | ||
| const FIELD_VALUES = {
 | ||
|     before: {},
 | ||
|     after: {},
 | ||
|     changes: {}
 | ||
| };
 | ||
| 
 | ||
| async function screenshot(page, name) {
 | ||
|     await fs.mkdir('screenshots/edit-workflow', { recursive: true });
 | ||
|     const path = `screenshots/edit-workflow/${name}-${Date.now()}.png`;
 | ||
|     await page.screenshot({ path, fullPage: true });
 | ||
|     console.log(`📸 Screenshot: ${name}`);
 | ||
|     return path;
 | ||
| }
 | ||
| 
 | ||
| async function createTestEvent(page) {
 | ||
|     console.log('\n📝 Creating test event via WordPress Admin...');
 | ||
|     
 | ||
|     await page.goto(`${CONFIG.baseUrl}/wp-admin/post-new.php?post_type=tribe_events`);
 | ||
|     await page.waitForLoadState('domcontentloaded');
 | ||
|     
 | ||
|     // Fill basic event data
 | ||
|     const eventData = {
 | ||
|         title: `Test Event for Edit Verification ${Date.now()}`,
 | ||
|         description: 'This event will be edited to verify all fields save correctly.',
 | ||
|         startDate: '2025-09-15',
 | ||
|         endDate: '2025-09-16',
 | ||
|         startTime: '09:00',
 | ||
|         endTime: '17:00',
 | ||
|         cost: '299',
 | ||
|         venue: 'Test Venue',
 | ||
|         organizer: 'Test Organizer',
 | ||
|         website: 'https://example.com/test'
 | ||
|     };
 | ||
|     
 | ||
|     // Title
 | ||
|     const titleField = await page.$('#title');
 | ||
|     if (titleField) {
 | ||
|         await titleField.fill(eventData.title);
 | ||
|         console.log('✅ Title set');
 | ||
|     }
 | ||
|     
 | ||
|     // Description (try different methods)
 | ||
|     const contentFrame = await page.$('#content_ifr');
 | ||
|     if (contentFrame) {
 | ||
|         const frame = await contentFrame.contentFrame();
 | ||
|         await frame.fill('#tinymce', eventData.description);
 | ||
|         console.log('✅ Description set (TinyMCE)');
 | ||
|     } else {
 | ||
|         const contentArea = await page.$('#content');
 | ||
|         if (contentArea) {
 | ||
|             await contentArea.fill(eventData.description);
 | ||
|             console.log('✅ Description set (textarea)');
 | ||
|         }
 | ||
|     }
 | ||
|     
 | ||
|     // Dates
 | ||
|     const startDate = await page.$('#EventStartDate');
 | ||
|     if (startDate) {
 | ||
|         await startDate.fill(eventData.startDate);
 | ||
|         console.log('✅ Start date set');
 | ||
|     }
 | ||
|     
 | ||
|     const endDate = await page.$('#EventEndDate');
 | ||
|     if (endDate) {
 | ||
|         await endDate.fill(eventData.endDate);
 | ||
|         console.log('✅ End date set');
 | ||
|     }
 | ||
|     
 | ||
|     // Times
 | ||
|     const startTime = await page.$('#EventStartTime');
 | ||
|     if (startTime) {
 | ||
|         await startTime.fill(eventData.startTime);
 | ||
|         console.log('✅ Start time set');
 | ||
|     }
 | ||
|     
 | ||
|     const endTime = await page.$('#EventEndTime');
 | ||
|     if (endTime) {
 | ||
|         await endTime.fill(eventData.endTime);
 | ||
|         console.log('✅ End time set');
 | ||
|     }
 | ||
|     
 | ||
|     // Cost
 | ||
|     const costField = await page.$('#EventCost');
 | ||
|     if (costField) {
 | ||
|         await costField.fill(eventData.cost);
 | ||
|         console.log('✅ Cost set');
 | ||
|     }
 | ||
|     
 | ||
|     // Website
 | ||
|     const urlField = await page.$('#EventURL');
 | ||
|     if (urlField) {
 | ||
|         await urlField.fill(eventData.website);
 | ||
|         console.log('✅ Website set');
 | ||
|     }
 | ||
|     
 | ||
|     // Save as draft first
 | ||
|     const saveDraft = await page.$('#save-post');
 | ||
|     if (saveDraft) {
 | ||
|         await saveDraft.click();
 | ||
|         await page.waitForSelector('.notice-success, #message', { timeout: 10000 });
 | ||
|         console.log('✅ Event saved as draft');
 | ||
|     }
 | ||
|     
 | ||
|     // Now publish
 | ||
|     const publishBtn = await page.$('#publish');
 | ||
|     if (publishBtn) {
 | ||
|         await publishBtn.click();
 | ||
|         await page.waitForSelector('.notice-success, #message', { timeout: 10000 });
 | ||
|         console.log('✅ Event published');
 | ||
|     }
 | ||
|     
 | ||
|     // Get the event ID from URL
 | ||
|     const url = page.url();
 | ||
|     const match = url.match(/post=(\d+)/);
 | ||
|     const eventId = match ? match[1] : null;
 | ||
|     
 | ||
|     console.log(`✅ Event created with ID: ${eventId}`);
 | ||
|     return eventId;
 | ||
| }
 | ||
| 
 | ||
| async function accessEventFromDashboard(page) {
 | ||
|     console.log('\n🔍 Accessing event from dashboard...');
 | ||
|     
 | ||
|     // Go to events list
 | ||
|     await page.goto(`${CONFIG.baseUrl}/trainer/events/`);
 | ||
|     await page.waitForLoadState('domcontentloaded');
 | ||
|     
 | ||
|     await screenshot(page, 'events-list');
 | ||
|     
 | ||
|     // Try multiple selectors for edit links
 | ||
|     const editSelectors = [
 | ||
|         'a[href*="action=edit"]',
 | ||
|         'a[href*="/edit/"]',
 | ||
|         '.edit-link',
 | ||
|         'a:has-text("Edit")',
 | ||
|         'button:has-text("Edit")'
 | ||
|     ];
 | ||
|     
 | ||
|     let editLink = null;
 | ||
|     for (const selector of editSelectors) {
 | ||
|         editLink = await page.$(selector);
 | ||
|         if (editLink) {
 | ||
|             console.log(`✅ Found edit link with selector: ${selector}`);
 | ||
|             break;
 | ||
|         }
 | ||
|     }
 | ||
|     
 | ||
|     if (!editLink) {
 | ||
|         // If no edit links, go directly to admin
 | ||
|         console.log('⚠️ No edit links found on trainer page, using admin...');
 | ||
|         await page.goto(`${CONFIG.baseUrl}/wp-admin/edit.php?post_type=tribe_events`);
 | ||
|         
 | ||
|         const firstEdit = await page.$('#the-list .row-actions .edit a');
 | ||
|         if (firstEdit) {
 | ||
|             await firstEdit.click();
 | ||
|             await page.waitForLoadState('networkidle');
 | ||
|             return true;
 | ||
|         }
 | ||
|         return false;
 | ||
|     }
 | ||
|     
 | ||
|     await editLink.click();
 | ||
|     await page.waitForLoadState('networkidle');
 | ||
|     return true;
 | ||
| }
 | ||
| 
 | ||
| async function captureAllFields(page) {
 | ||
|     console.log('\n📊 Capturing all field values...');
 | ||
|     
 | ||
|     const fields = {};
 | ||
|     
 | ||
|     // Define all TEC fields to check
 | ||
|     const fieldSelectors = {
 | ||
|         'title': '#title, input[name="post_title"]',
 | ||
|         'content': '#content, #tinymce',
 | ||
|         'startDate': '#EventStartDate, input[name="EventStartDate"]',
 | ||
|         'endDate': '#EventEndDate, input[name="EventEndDate"]',
 | ||
|         'startTime': '#EventStartTime, input[name="EventStartTime"]',
 | ||
|         'endTime': '#EventEndTime, input[name="EventEndTime"]',
 | ||
|         'allDay': '#EventAllDay, input[name="EventAllDay"]',
 | ||
|         'timezone': '#event-timezone, select[name="EventTimezone"]',
 | ||
|         'cost': '#EventCost, input[name="EventCost"]',
 | ||
|         'currency': '#EventCurrencySymbol, input[name="EventCurrencySymbol"]',
 | ||
|         'website': '#EventURL, input[name="EventURL"]',
 | ||
|         'venue': '#venue-name, select[name="venue[VenueID]"], input[name="venue[Venue]"]',
 | ||
|         'address': '#VenueAddress, input[name="venue[Address]"]',
 | ||
|         'city': '#VenueCity, input[name="venue[City]"]',
 | ||
|         'state': '#VenueState, input[name="venue[State]"]',
 | ||
|         'zip': '#VenueZip, input[name="venue[Zip]"]',
 | ||
|         'country': '#VenueCountry, select[name="venue[Country]"]',
 | ||
|         'phone': '#VenuePhone, input[name="venue[Phone]"]',
 | ||
|         'organizer': '#organizer-name, select[name="organizer[OrganizerID]"], input[name="organizer[Organizer]"]',
 | ||
|         'organizerEmail': '#OrganizerEmail, input[name="organizer[Email]"]',
 | ||
|         'organizerPhone': '#OrganizerPhone, input[name="organizer[Phone]"]',
 | ||
|         'organizerWebsite': '#OrganizerWebsite, input[name="organizer[Website]"]'
 | ||
|     };
 | ||
|     
 | ||
|     for (const [fieldName, selector] of Object.entries(fieldSelectors)) {
 | ||
|         try {
 | ||
|             const element = await page.$(selector);
 | ||
|             if (element) {
 | ||
|                 // Try different methods to get value
 | ||
|                 let value = await element.inputValue().catch(() => null);
 | ||
|                 
 | ||
|                 if (!value) {
 | ||
|                     value = await element.textContent().catch(() => null);
 | ||
|                 }
 | ||
|                 
 | ||
|                 if (!value) {
 | ||
|                     value = await element.evaluate(el => {
 | ||
|                         if (el.tagName === 'SELECT') {
 | ||
|                             return el.options[el.selectedIndex]?.text || '';
 | ||
|                         }
 | ||
|                         return el.value || el.innerText || '';
 | ||
|                     });
 | ||
|                 }
 | ||
|                 
 | ||
|                 if (value) {
 | ||
|                     fields[fieldName] = value;
 | ||
|                     console.log(`  ${fieldName}: ${value.substring(0, 50)}${value.length > 50 ? '...' : ''}`);
 | ||
|                 }
 | ||
|             } else {
 | ||
|                 console.log(`  ${fieldName}: [field not found]`);
 | ||
|             }
 | ||
|         } catch (error) {
 | ||
|             console.log(`  ${fieldName}: [error reading]`);
 | ||
|         }
 | ||
|     }
 | ||
|     
 | ||
|     // Check for TinyMCE content
 | ||
|     const contentFrame = await page.$('#content_ifr');
 | ||
|     if (contentFrame && !fields.content) {
 | ||
|         try {
 | ||
|             const frame = await contentFrame.contentFrame();
 | ||
|             const content = await frame.$eval('#tinymce', el => el.textContent);
 | ||
|             if (content) {
 | ||
|                 fields.content = content;
 | ||
|                 console.log(`  content (TinyMCE): ${content.substring(0, 50)}...`);
 | ||
|             }
 | ||
|         } catch (error) {
 | ||
|             console.log('  content: [TinyMCE error]');
 | ||
|         }
 | ||
|     }
 | ||
|     
 | ||
|     await screenshot(page, 'fields-captured');
 | ||
|     
 | ||
|     return fields;
 | ||
| }
 | ||
| 
 | ||
| async function editFields(page, originalValues) {
 | ||
|     console.log('\n✏️ Editing fields...');
 | ||
|     
 | ||
|     const changes = {};
 | ||
|     
 | ||
|     // Edit title
 | ||
|     const titleField = await page.$('#title');
 | ||
|     if (titleField && originalValues.title) {
 | ||
|         const newTitle = originalValues.title + ' (EDITED)';
 | ||
|         await titleField.fill(newTitle);
 | ||
|         changes.title = newTitle;
 | ||
|         console.log('✅ Title edited');
 | ||
|     }
 | ||
|     
 | ||
|     // Edit cost
 | ||
|     const costField = await page.$('#EventCost');
 | ||
|     if (costField && originalValues.cost) {
 | ||
|         const newCost = '399';
 | ||
|         await costField.fill(newCost);
 | ||
|         changes.cost = newCost;
 | ||
|         console.log('✅ Cost edited');
 | ||
|     }
 | ||
|     
 | ||
|     // Edit start date
 | ||
|     const startDateField = await page.$('#EventStartDate');
 | ||
|     if (startDateField && originalValues.startDate) {
 | ||
|         const newDate = '2025-10-01';
 | ||
|         await startDateField.fill(newDate);
 | ||
|         changes.startDate = newDate;
 | ||
|         console.log('✅ Start date edited');
 | ||
|     }
 | ||
|     
 | ||
|     // Edit website
 | ||
|     const urlField = await page.$('#EventURL');
 | ||
|     if (urlField) {
 | ||
|         const newUrl = 'https://edited.example.com/event';
 | ||
|         await urlField.fill(newUrl);
 | ||
|         changes.website = newUrl;
 | ||
|         console.log('✅ Website edited');
 | ||
|     }
 | ||
|     
 | ||
|     // Edit description
 | ||
|     const contentFrame = await page.$('#content_ifr');
 | ||
|     if (contentFrame) {
 | ||
|         try {
 | ||
|             const frame = await contentFrame.contentFrame();
 | ||
|             const tinymce = await frame.$('#tinymce');
 | ||
|             if (tinymce) {
 | ||
|                 await tinymce.fill('EDITED: This event description has been updated.');
 | ||
|                 changes.content = 'EDITED: This event description has been updated.';
 | ||
|                 console.log('✅ Description edited');
 | ||
|             }
 | ||
|         } catch (error) {
 | ||
|             // Try textarea
 | ||
|             const contentArea = await page.$('#content');
 | ||
|             if (contentArea) {
 | ||
|                 await contentArea.fill('EDITED: This event description has been updated.');
 | ||
|                 changes.content = 'EDITED: This event description has been updated.';
 | ||
|                 console.log('✅ Description edited (textarea)');
 | ||
|             }
 | ||
|         }
 | ||
|     }
 | ||
|     
 | ||
|     await screenshot(page, 'fields-edited');
 | ||
|     
 | ||
|     return changes;
 | ||
| }
 | ||
| 
 | ||
| async function saveAndVerify(page, expectedChanges) {
 | ||
|     console.log('\n💾 Saving changes...');
 | ||
|     
 | ||
|     // Find and click update/publish button
 | ||
|     const updateBtn = await page.$('#publish, #save-post, input[name="save"]');
 | ||
|     if (updateBtn) {
 | ||
|         await updateBtn.click();
 | ||
|         
 | ||
|         // Wait for success message
 | ||
|         await page.waitForSelector('.notice-success, #message, .updated', { timeout: 10000 });
 | ||
|         console.log('✅ Changes saved');
 | ||
|         
 | ||
|         await screenshot(page, 'saved-success');
 | ||
|     } else {
 | ||
|         console.log('❌ Save button not found');
 | ||
|         return false;
 | ||
|     }
 | ||
|     
 | ||
|     // Reload the page to verify persistence
 | ||
|     console.log('\n🔄 Reloading to verify persistence...');
 | ||
|     await page.reload();
 | ||
|     await page.waitForLoadState('networkidle');
 | ||
|     
 | ||
|     // Capture fields again
 | ||
|     const afterValues = await captureAllFields(page);
 | ||
|     
 | ||
|     // Verify changes persisted
 | ||
|     console.log('\n✅ Verifying changes persisted...');
 | ||
|     let allChangesPersisted = true;
 | ||
|     
 | ||
|     for (const [field, expectedValue] of Object.entries(expectedChanges)) {
 | ||
|         const actualValue = afterValues[field];
 | ||
|         
 | ||
|         if (actualValue && actualValue.includes(expectedValue)) {
 | ||
|             console.log(`  ✅ ${field}: Change persisted`);
 | ||
|         } else {
 | ||
|             console.log(`  ❌ ${field}: Expected "${expectedValue}", got "${actualValue}"`);
 | ||
|             allChangesPersisted = false;
 | ||
|         }
 | ||
|     }
 | ||
|     
 | ||
|     return allChangesPersisted;
 | ||
| }
 | ||
| 
 | ||
| async function runCompleteEditWorkflow() {
 | ||
|     console.log('🎯 COMPLETE EVENT EDIT WORKFLOW TEST');
 | ||
|     console.log('=' .repeat(60));
 | ||
|     console.log('This test will:');
 | ||
|     console.log('1. Create/access an event');
 | ||
|     console.log('2. Verify ALL fields populate');
 | ||
|     console.log('3. Edit multiple fields');
 | ||
|     console.log('4. Save and verify persistence');
 | ||
|     console.log('=' .repeat(60));
 | ||
|     
 | ||
|     const browser = await chromium.launch({ headless: true });
 | ||
|     const page = await browser.newPage();
 | ||
|     
 | ||
|     const results = {
 | ||
|         steps: [],
 | ||
|         fieldsBefore: 0,
 | ||
|         fieldsAfter: 0,
 | ||
|         changesPersisted: 0,
 | ||
|         totalChanges: 0
 | ||
|     };
 | ||
|     
 | ||
|     try {
 | ||
|         // Login
 | ||
|         console.log('\n🔐 Logging in...');
 | ||
|         await page.goto(`${CONFIG.baseUrl}/wp-login.php`);
 | ||
|         await page.fill('#user_login', CONFIG.credentials.email);
 | ||
|         await page.fill('#user_pass', CONFIG.credentials.password);
 | ||
|         await page.click('#wp-submit');
 | ||
|         await page.waitForURL(/dashboard|admin/, { timeout: 10000 });
 | ||
|         console.log('✅ Logged in successfully');
 | ||
|         results.steps.push({ step: 'Login', status: 'passed' });
 | ||
|         
 | ||
|         // Create a test event first
 | ||
|         const eventId = await createTestEvent(page);
 | ||
|         if (eventId) {
 | ||
|             results.steps.push({ step: 'Create Test Event', status: 'passed', eventId });
 | ||
|         }
 | ||
|         
 | ||
|         // Access event for editing
 | ||
|         const accessed = await accessEventFromDashboard(page);
 | ||
|         if (accessed) {
 | ||
|             console.log('✅ Accessed event edit form');
 | ||
|             results.steps.push({ step: 'Access Edit Form', status: 'passed' });
 | ||
|         } else {
 | ||
|             console.log('❌ Could not access edit form');
 | ||
|             results.steps.push({ step: 'Access Edit Form', status: 'failed' });
 | ||
|         }
 | ||
|         
 | ||
|         // Capture all field values
 | ||
|         FIELD_VALUES.before = await captureAllFields(page);
 | ||
|         results.fieldsBefore = Object.keys(FIELD_VALUES.before).length;
 | ||
|         console.log(`\n📊 Fields populated: ${results.fieldsBefore}`);
 | ||
|         results.steps.push({ 
 | ||
|             step: 'Capture Fields', 
 | ||
|             status: results.fieldsBefore > 0 ? 'passed' : 'failed',
 | ||
|             fieldsFound: results.fieldsBefore 
 | ||
|         });
 | ||
|         
 | ||
|         // Edit fields
 | ||
|         FIELD_VALUES.changes = await editFields(page, FIELD_VALUES.before);
 | ||
|         results.totalChanges = Object.keys(FIELD_VALUES.changes).length;
 | ||
|         console.log(`\n✏️ Fields edited: ${results.totalChanges}`);
 | ||
|         results.steps.push({ 
 | ||
|             step: 'Edit Fields', 
 | ||
|             status: results.totalChanges > 0 ? 'passed' : 'failed',
 | ||
|             changesAttempted: results.totalChanges 
 | ||
|         });
 | ||
|         
 | ||
|         // Save and verify
 | ||
|         const changesPersisted = await saveAndVerify(page, FIELD_VALUES.changes);
 | ||
|         results.changesPersisted = changesPersisted ? results.totalChanges : 0;
 | ||
|         results.steps.push({ 
 | ||
|             step: 'Save and Verify', 
 | ||
|             status: changesPersisted ? 'passed' : 'failed',
 | ||
|             changesPersisted: results.changesPersisted 
 | ||
|         });
 | ||
|         
 | ||
|     } catch (error) {
 | ||
|         console.error('❌ Error:', error.message);
 | ||
|         results.steps.push({ step: 'Error', status: 'failed', error: error.message });
 | ||
|     } finally {
 | ||
|         await browser.close();
 | ||
|     }
 | ||
|     
 | ||
|     // Generate final report
 | ||
|     console.log('\n' + '=' .repeat(60));
 | ||
|     console.log('📊 FINAL RESULTS');
 | ||
|     console.log('=' .repeat(60));
 | ||
|     
 | ||
|     const passed = results.steps.filter(s => s.status === 'passed').length;
 | ||
|     const failed = results.steps.filter(s => s.status === 'failed').length;
 | ||
|     const successRate = ((passed / results.steps.length) * 100).toFixed(0);
 | ||
|     
 | ||
|     console.log(`\nWorkflow Steps: ${results.steps.length}`);
 | ||
|     console.log(`✅ Passed: ${passed}`);
 | ||
|     console.log(`❌ Failed: ${failed}`);
 | ||
|     console.log(`Success Rate: ${successRate}%`);
 | ||
|     
 | ||
|     console.log('\n📋 Details:');
 | ||
|     console.log(`- Fields found before edit: ${results.fieldsBefore}`);
 | ||
|     console.log(`- Fields edited: ${results.totalChanges}`);
 | ||
|     console.log(`- Changes persisted: ${results.changesPersisted}/${results.totalChanges}`);
 | ||
|     
 | ||
|     console.log('\n🔍 Step Results:');
 | ||
|     results.steps.forEach(step => {
 | ||
|         const icon = step.status === 'passed' ? '✅' : '❌';
 | ||
|         console.log(`${icon} ${step.step}: ${step.status.toUpperCase()}`);
 | ||
|         if (step.fieldsFound) console.log(`   Fields: ${step.fieldsFound}`);
 | ||
|         if (step.changesAttempted) console.log(`   Changes: ${step.changesAttempted}`);
 | ||
|         if (step.error) console.log(`   Error: ${step.error}`);
 | ||
|     });
 | ||
|     
 | ||
|     console.log('\n🎯 ASSESSMENT:');
 | ||
|     if (successRate === '100' && results.changesPersisted === results.totalChanges) {
 | ||
|         console.log('✅ COMPLETE EDIT WORKFLOW VERIFIED!');
 | ||
|         console.log('All fields populate correctly and changes persist.');
 | ||
|     } else if (successRate >= '60') {
 | ||
|         console.log('⚠️ PARTIAL SUCCESS');
 | ||
|         console.log('Some aspects work but issues detected.');
 | ||
|     } else {
 | ||
|         console.log('❌ EDIT WORKFLOW NEEDS ATTENTION');
 | ||
|         console.log('Significant issues with field population or persistence.');
 | ||
|     }
 | ||
|     
 | ||
|     console.log('\n📁 Screenshots: screenshots/edit-workflow/');
 | ||
|     console.log('=' .repeat(60));
 | ||
| }
 | ||
| 
 | ||
| // Run the test
 | ||
| console.log('Starting complete edit workflow test...\n');
 | ||
| runCompleteEditWorkflow().catch(error => {
 | ||
|     console.error('Fatal error:', error);
 | ||
|     process.exit(1);
 | ||
| }); |