- Created comprehensive field mapping documentation for TEC v5.0.8
- Documented all meta keys, input selectors, and field types
- Built validation tests using correct TEC v5.0.8 selectors
- Verified working selectors through staging environment testing
- Added best practices guide with implementation patterns
- Included JavaScript and PHP code examples
- Documented common issues and solutions
- Added debugging tips and performance optimizations
Test results show successful field discovery and persistence:
- Title, Start Date, End Date, and URL fields verified working
- Cost field may be hidden when Events Tickets is active
- All date/time fields use expected selectors
- Venue and organizer fields use array notation in names
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
		
	
			
		
			
				
	
	
		
			338 lines
		
	
	
		
			No EOL
		
	
	
		
			13 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			338 lines
		
	
	
		
			No EOL
		
	
	
		
			13 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable file
		
	
	
	
	
| #!/usr/bin/env node
 | ||
| 
 | ||
| /**
 | ||
|  * TEC v5.0.8 Field Discovery Test
 | ||
|  * Discovers and documents the actual field selectors used by The Events Calendar
 | ||
|  */
 | ||
| 
 | ||
| const { chromium } = require('@playwright/test');
 | ||
| const fs = require('fs').promises;
 | ||
| const { execSync } = require('child_process');
 | ||
| 
 | ||
| // Configure XWayland display
 | ||
| process.env.DISPLAY = ':0';
 | ||
| try {
 | ||
|     const xauthFile = execSync('ls /run/user/1000/.mutter-Xwaylandauth.* 2>/dev/null | head -n1', { encoding: 'utf8' }).trim();
 | ||
|     if (xauthFile) {
 | ||
|         process.env.XAUTHORITY = xauthFile;
 | ||
|     }
 | ||
| } catch (e) {
 | ||
|     // Continue without XAUTHORITY
 | ||
| }
 | ||
| 
 | ||
| const CONFIG = {
 | ||
|     baseUrl: 'https://upskill-staging.measurequick.com',
 | ||
|     credentials: {
 | ||
|         username: 'test_admin',
 | ||
|         password: 'TestAdmin2025!'
 | ||
|     }
 | ||
| };
 | ||
| 
 | ||
| async function screenshot(page, name) {
 | ||
|     await fs.mkdir('screenshots/tec-discovery', { recursive: true });
 | ||
|     const path = `screenshots/tec-discovery/${name}-${Date.now()}.png`;
 | ||
|     await page.screenshot({ path, fullPage: true });
 | ||
|     console.log(`📸 Screenshot: ${name}`);
 | ||
|     return path;
 | ||
| }
 | ||
| 
 | ||
| async function discoverTECFields() {
 | ||
|     console.log('🔍 TEC v5.0.8 FIELD DISCOVERY');
 | ||
|     console.log('=' .repeat(70));
 | ||
|     console.log('Discovering actual field selectors in The Events Calendar v5.0.8');
 | ||
|     console.log('=' .repeat(70));
 | ||
|     
 | ||
|     const browser = await chromium.launch({ 
 | ||
|         headless: false,
 | ||
|         args: ['--no-sandbox', '--disable-setuid-sandbox']
 | ||
|     });
 | ||
|     
 | ||
|     const page = await browser.newPage();
 | ||
|     
 | ||
|     const discoveredFields = {
 | ||
|         basic: {},
 | ||
|         datetime: {},
 | ||
|         cost: {},
 | ||
|         venue: {},
 | ||
|         organizer: {},
 | ||
|         other: {}
 | ||
|     };
 | ||
|     
 | ||
|     try {
 | ||
|         // 1. LOGIN
 | ||
|         console.log('\n📝 STEP 1: Login');
 | ||
|         await page.goto(`${CONFIG.baseUrl}/wp-login.php`);
 | ||
|         await page.fill('#user_login', CONFIG.credentials.username);
 | ||
|         await page.fill('#user_pass', CONFIG.credentials.password);
 | ||
|         await page.click('#wp-submit');
 | ||
|         await page.waitForURL('**/wp-admin/**', { timeout: 15000 });
 | ||
|         console.log('✅ Logged in');
 | ||
|         
 | ||
|         // 2. OPEN EVENT EDIT FORM
 | ||
|         console.log('\n📝 STEP 2: Opening Event Edit Form');
 | ||
|         
 | ||
|         // First, get to the events list
 | ||
|         await page.goto(`${CONFIG.baseUrl}/wp-admin/edit.php?post_type=tribe_events`);
 | ||
|         await page.waitForLoadState('networkidle');
 | ||
|         
 | ||
|         // Click edit on first event
 | ||
|         const editLink = await page.$('tbody#the-list tr:first-child .row-actions .edit a');
 | ||
|         if (!editLink) {
 | ||
|             console.log('❌ No events found');
 | ||
|             await browser.close();
 | ||
|             return;
 | ||
|         }
 | ||
|         
 | ||
|         await editLink.click();
 | ||
|         await page.waitForLoadState('networkidle');
 | ||
|         console.log('✅ Edit form opened');
 | ||
|         await screenshot(page, 'edit-form');
 | ||
|         
 | ||
|         // 3. DISCOVER FIELDS
 | ||
|         console.log('\n📝 STEP 3: Discovering Fields');
 | ||
|         console.log('-' .repeat(70));
 | ||
|         
 | ||
|         // Strategy 1: Look for all input fields with relevant IDs/names
 | ||
|         console.log('\n🔍 Strategy 1: Input fields by ID/name patterns');
 | ||
|         
 | ||
|         const inputSelectors = [
 | ||
|             // Title and content (standard WP)
 | ||
|             '#title',
 | ||
|             '#content',
 | ||
|             'textarea[name="content"]',
 | ||
|             '#excerpt',
 | ||
|             
 | ||
|             // Event-specific patterns
 | ||
|             'input[id*="Event"]',
 | ||
|             'input[name*="Event"]',
 | ||
|             'select[id*="Event"]',
 | ||
|             'select[name*="Event"]',
 | ||
|             'input[id*="event"]',
 | ||
|             'input[name*="event"]',
 | ||
|             
 | ||
|             // Venue patterns
 | ||
|             'input[id*="Venue"]',
 | ||
|             'input[name*="venue"]',
 | ||
|             'select[id*="Venue"]',
 | ||
|             'select[name*="venue"]',
 | ||
|             
 | ||
|             // Organizer patterns
 | ||
|             'input[id*="Organizer"]',
 | ||
|             'input[name*="organizer"]',
 | ||
|             'select[id*="Organizer"]',
 | ||
|             'select[name*="organizer"]',
 | ||
|             
 | ||
|             // TEC specific patterns
 | ||
|             'input[name*="tribe"]',
 | ||
|             'select[name*="tribe"]',
 | ||
|             '.tribe-events-admin input',
 | ||
|             '.tribe-events-admin select'
 | ||
|         ];
 | ||
|         
 | ||
|         for (const selector of inputSelectors) {
 | ||
|             try {
 | ||
|                 const elements = await page.$$(selector);
 | ||
|                 if (elements.length > 0) {
 | ||
|                     console.log(`\n✅ Found ${elements.length} elements matching: ${selector}`);
 | ||
|                     
 | ||
|                     // Get details of each element
 | ||
|                     for (let i = 0; i < Math.min(5, elements.length); i++) {
 | ||
|                         const details = await elements[i].evaluate(el => ({
 | ||
|                             id: el.id,
 | ||
|                             name: el.name,
 | ||
|                             type: el.type || el.tagName.toLowerCase(),
 | ||
|                             value: el.value || '',
 | ||
|                             placeholder: el.placeholder || '',
 | ||
|                             className: el.className
 | ||
|                         }));
 | ||
|                         
 | ||
|                         if (details.id || details.name) {
 | ||
|                             console.log(`  - ${details.type}: id="${details.id}" name="${details.name}"`);
 | ||
|                             
 | ||
|                             // Categorize the field
 | ||
|                             const fieldKey = details.id || details.name;
 | ||
|                             if (fieldKey.match(/date|time/i)) {
 | ||
|                                 discoveredFields.datetime[fieldKey] = details;
 | ||
|                             } else if (fieldKey.match(/cost|price|currency/i)) {
 | ||
|                                 discoveredFields.cost[fieldKey] = details;
 | ||
|                             } else if (fieldKey.match(/venue/i)) {
 | ||
|                                 discoveredFields.venue[fieldKey] = details;
 | ||
|                             } else if (fieldKey.match(/organizer/i)) {
 | ||
|                                 discoveredFields.organizer[fieldKey] = details;
 | ||
|                             } else {
 | ||
|                                 discoveredFields.other[fieldKey] = details;
 | ||
|                             }
 | ||
|                         }
 | ||
|                     }
 | ||
|                 }
 | ||
|             } catch (e) {
 | ||
|                 // Skip selector if it causes an error
 | ||
|             }
 | ||
|         }
 | ||
|         
 | ||
|         // Strategy 2: Look for metabox containers
 | ||
|         console.log('\n🔍 Strategy 2: TEC Metaboxes');
 | ||
|         
 | ||
|         const metaboxSelectors = [
 | ||
|             '#tribe_events_event_options',
 | ||
|             '.tribe-events-admin',
 | ||
|             '#event_tribe_venue',
 | ||
|             '#event_tribe_organizer',
 | ||
|             '.tribe_events_event_section',
 | ||
|             '[id*="tribe-events"]',
 | ||
|             '[class*="tribe-events"]'
 | ||
|         ];
 | ||
|         
 | ||
|         for (const selector of metaboxSelectors) {
 | ||
|             const element = await page.$(selector);
 | ||
|             if (element) {
 | ||
|                 console.log(`\n✅ Found metabox: ${selector}`);
 | ||
|                 
 | ||
|                 // Find all inputs within this metabox
 | ||
|                 const inputs = await element.$$('input, select, textarea');
 | ||
|                 console.log(`  Contains ${inputs.length} form fields`);
 | ||
|                 
 | ||
|                 for (let i = 0; i < Math.min(10, inputs.length); i++) {
 | ||
|                     const details = await inputs[i].evaluate(el => ({
 | ||
|                         id: el.id,
 | ||
|                         name: el.name,
 | ||
|                         type: el.type || el.tagName.toLowerCase()
 | ||
|                     }));
 | ||
|                     
 | ||
|                     if (details.id || details.name) {
 | ||
|                         console.log(`    - ${details.type}: id="${details.id}" name="${details.name}"`);
 | ||
|                     }
 | ||
|                 }
 | ||
|             }
 | ||
|         }
 | ||
|         
 | ||
|         // Strategy 3: Check for Block Editor (Gutenberg) fields
 | ||
|         console.log('\n🔍 Strategy 3: Block Editor Fields');
 | ||
|         
 | ||
|         const blockEditorPresent = await page.$('.block-editor');
 | ||
|         if (blockEditorPresent) {
 | ||
|             console.log('✅ Block Editor detected');
 | ||
|             
 | ||
|             // Look for TEC blocks
 | ||
|             const tecBlocks = await page.$$('[data-type*="tribe"], [class*="tribe-editor"]');
 | ||
|             console.log(`  Found ${tecBlocks.length} TEC blocks`);
 | ||
|         } else {
 | ||
|             console.log('ℹ️ Classic Editor in use');
 | ||
|         }
 | ||
|         
 | ||
|         // 4. TEST SPECIFIC FIELD SELECTORS
 | ||
|         console.log('\n📝 STEP 4: Testing Specific Fields');
 | ||
|         console.log('-' .repeat(70));
 | ||
|         
 | ||
|         const fieldsToTest = {
 | ||
|             // Based on what worked in our previous test
 | ||
|             'Title': '#title',
 | ||
|             'Content': '#content',
 | ||
|             'Start Date': '#EventStartDate',
 | ||
|             'End Date': '#EventEndDate', 
 | ||
|             'Start Time': '#EventStartTime',
 | ||
|             'End Time': '#EventEndTime',
 | ||
|             'Timezone': 'select[name="EventTimezone"]',
 | ||
|             'Website URL': '#EventURL',
 | ||
|             
 | ||
|             // Additional fields to test
 | ||
|             'Cost': '#EventCost',
 | ||
|             'Currency': '#EventCurrencySymbol',
 | ||
|             'Show Map': '#EventShowMap',
 | ||
|             'Show Map Link': '#EventShowMapLink',
 | ||
|             
 | ||
|             // Venue fields (various possible selectors)
 | ||
|             'Venue Dropdown': 'select[name="venue[VenueID]"]',
 | ||
|             'Venue Name Input': 'input[name="venue[Venue]"]',
 | ||
|             'Venue Address': 'input[name="venue[Address]"]',
 | ||
|             'Venue City': 'input[name="venue[City]"]',
 | ||
|             'Venue State': 'input[name="venue[State]"]',
 | ||
|             'Venue Zip': 'input[name="venue[Zip]"]',
 | ||
|             'Venue Country': 'select[name="venue[Country]"]',
 | ||
|             
 | ||
|             // Organizer fields
 | ||
|             'Organizer Dropdown': 'select[name="organizer[OrganizerID]"]',
 | ||
|             'Organizer Name Input': 'input[name="organizer[Organizer]"]',
 | ||
|             'Organizer Email': 'input[name="organizer[Email]"]',
 | ||
|             'Organizer Phone': 'input[name="organizer[Phone]"]',
 | ||
|             'Organizer Website': 'input[name="organizer[Website]"]'
 | ||
|         };
 | ||
|         
 | ||
|         const workingSelectors = {};
 | ||
|         
 | ||
|         for (const [fieldName, selector] of Object.entries(fieldsToTest)) {
 | ||
|             const element = await page.$(selector);
 | ||
|             if (element) {
 | ||
|                 try {
 | ||
|                     const isVisible = await element.isVisible();
 | ||
|                     const value = await element.inputValue().catch(() => null) ||
 | ||
|                                  await element.textContent().catch(() => null);
 | ||
|                     
 | ||
|                     if (isVisible) {
 | ||
|                         console.log(`✅ ${fieldName}: FOUND (visible) - Selector: ${selector}`);
 | ||
|                         if (value) {
 | ||
|                             console.log(`   Value: ${String(value).substring(0, 50)}`);
 | ||
|                         }
 | ||
|                         workingSelectors[fieldName] = selector;
 | ||
|                     } else {
 | ||
|                         console.log(`⚠️ ${fieldName}: Found but hidden - Selector: ${selector}`);
 | ||
|                     }
 | ||
|                 } catch (e) {
 | ||
|                     console.log(`❌ ${fieldName}: Error - ${e.message}`);
 | ||
|                 }
 | ||
|             } else {
 | ||
|                 console.log(`❌ ${fieldName}: NOT FOUND - Selector: ${selector}`);
 | ||
|             }
 | ||
|         }
 | ||
|         
 | ||
|         // 5. GENERATE FIELD MAPPING
 | ||
|         console.log('\n📝 STEP 5: Generating Field Mapping');
 | ||
|         console.log('-' .repeat(70));
 | ||
|         
 | ||
|         const fieldMapping = {
 | ||
|             version: 'TEC v5.0.8',
 | ||
|             discovered: new Date().toISOString(),
 | ||
|             workingSelectors: workingSelectors,
 | ||
|             categories: discoveredFields
 | ||
|         };
 | ||
|         
 | ||
|         // Save to file
 | ||
|         await fs.writeFile(
 | ||
|             'tec-v5-field-mapping.json',
 | ||
|             JSON.stringify(fieldMapping, null, 2)
 | ||
|         );
 | ||
|         
 | ||
|         console.log('✅ Field mapping saved to tec-v5-field-mapping.json');
 | ||
|         
 | ||
|         return fieldMapping;
 | ||
|         
 | ||
|     } catch (error) {
 | ||
|         console.error('\n❌ Error:', error.message);
 | ||
|         await screenshot(page, 'error');
 | ||
|     } finally {
 | ||
|         console.log('\n⏱️ Keeping browser open for 5 seconds...');
 | ||
|         await page.waitForTimeout(5000);
 | ||
|         await browser.close();
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| // Run the discovery
 | ||
| console.log('Starting TEC v5.0.8 field discovery...\n');
 | ||
| discoverTECFields().then(mapping => {
 | ||
|     if (mapping) {
 | ||
|         console.log('\n' + '=' .repeat(70));
 | ||
|         console.log('📊 DISCOVERY COMPLETE');
 | ||
|         console.log('=' .repeat(70));
 | ||
|         
 | ||
|         console.log('\n✅ Working Selectors Found:');
 | ||
|         for (const [field, selector] of Object.entries(mapping.workingSelectors)) {
 | ||
|             console.log(`  • ${field}: ${selector}`);
 | ||
|         }
 | ||
|         
 | ||
|         console.log('\n📁 Full mapping saved to: tec-v5-field-mapping.json');
 | ||
|         console.log('=' .repeat(70));
 | ||
|     }
 | ||
| }).catch(error => {
 | ||
|     console.error('Fatal error:', error);
 | ||
|     process.exit(1);
 | ||
| }); |