- 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);
|
||
}); |