- 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>
562 lines
No EOL
19 KiB
JavaScript
Executable file
562 lines
No EOL
19 KiB
JavaScript
Executable file
#!/usr/bin/env node
|
||
|
||
/**
|
||
* TEC v5.0.8 VALIDATED FIELD TEST
|
||
* Uses the correct field selectors from our field mapping documentation
|
||
* Tests complete event edit workflow with proper TEC v5.0.8 selectors
|
||
*/
|
||
|
||
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: process.env.UPSKILL_STAGING_URL || 'https://upskill-staging.measurequick.com',
|
||
credentials: {
|
||
username: 'test_admin',
|
||
password: 'TestAdmin2025!'
|
||
}
|
||
};
|
||
|
||
/**
|
||
* TEC v5.0.8 FIELD MAPPING
|
||
* Based on our documented field mapping (docs/TEC-V5-FIELD-MAPPING.md)
|
||
*/
|
||
const TEC_FIELDS = {
|
||
// Core Event Fields
|
||
basic: {
|
||
'Event Title': {
|
||
selector: '#title',
|
||
metaKey: 'post_title',
|
||
type: 'text'
|
||
},
|
||
'Event Content': {
|
||
selector: '#content',
|
||
metaKey: 'post_content',
|
||
type: 'wysiwyg'
|
||
},
|
||
'Event Excerpt': {
|
||
selector: '#excerpt',
|
||
metaKey: 'post_excerpt',
|
||
type: 'textarea'
|
||
}
|
||
},
|
||
|
||
// Date & Time Fields
|
||
datetime: {
|
||
'Start Date': {
|
||
selector: '#EventStartDate',
|
||
metaKey: '_EventStartDate',
|
||
type: 'date',
|
||
format: 'YYYY-MM-DD'
|
||
},
|
||
'End Date': {
|
||
selector: '#EventEndDate',
|
||
metaKey: '_EventEndDate',
|
||
type: 'date',
|
||
format: 'YYYY-MM-DD'
|
||
},
|
||
'Start Time': {
|
||
selector: '#EventStartTime',
|
||
metaKey: null, // Combined with date
|
||
type: 'time',
|
||
format: 'HH:MM am/pm'
|
||
},
|
||
'End Time': {
|
||
selector: '#EventEndTime',
|
||
metaKey: null, // Combined with date
|
||
type: 'time',
|
||
format: 'HH:MM am/pm'
|
||
},
|
||
'All Day Event': {
|
||
selector: '#EventAllDay',
|
||
metaKey: '_EventAllDay',
|
||
type: 'checkbox'
|
||
},
|
||
'Timezone': {
|
||
selector: 'select[name="EventTimezone"]',
|
||
metaKey: '_EventTimezone',
|
||
type: 'select'
|
||
}
|
||
},
|
||
|
||
// Cost & Currency Fields
|
||
cost: {
|
||
'Event Cost': {
|
||
selector: '#EventCost',
|
||
metaKey: '_EventCost',
|
||
type: 'text'
|
||
},
|
||
'Currency Symbol': {
|
||
selector: '#EventCurrencySymbol',
|
||
metaKey: '_EventCurrencySymbol',
|
||
type: 'text',
|
||
default: '$'
|
||
},
|
||
'Currency Code': {
|
||
selector: '#EventCurrencyCode',
|
||
metaKey: '_EventCurrencyCode',
|
||
type: 'text',
|
||
default: 'USD'
|
||
},
|
||
'Currency Position': {
|
||
selector: 'select[name="EventCurrencyPosition"]',
|
||
metaKey: null,
|
||
type: 'select'
|
||
}
|
||
},
|
||
|
||
// Event Details
|
||
details: {
|
||
'Event URL': {
|
||
selector: '#EventURL',
|
||
metaKey: '_EventURL',
|
||
type: 'url'
|
||
},
|
||
'Show Map': {
|
||
selector: '#EventShowMap',
|
||
metaKey: '_EventShowMap',
|
||
type: 'checkbox'
|
||
},
|
||
'Show Map Link': {
|
||
selector: '#EventShowMapLink',
|
||
metaKey: '_EventShowMapLink',
|
||
type: 'checkbox'
|
||
}
|
||
},
|
||
|
||
// Venue Fields
|
||
venue: {
|
||
'Venue ID': {
|
||
selector: 'select[name="venue[VenueID]"]',
|
||
metaKey: '_EventVenueID',
|
||
type: 'select'
|
||
},
|
||
'Venue Name': {
|
||
selector: 'input[name="venue[Venue]"]',
|
||
metaKey: '_VenueVenue',
|
||
type: 'text'
|
||
},
|
||
'Venue Address': {
|
||
selector: 'input[name="venue[Address]"]',
|
||
metaKey: '_VenueAddress',
|
||
type: 'text'
|
||
},
|
||
'Venue City': {
|
||
selector: 'input[name="venue[City]"]',
|
||
metaKey: '_VenueCity',
|
||
type: 'text'
|
||
},
|
||
'Venue State': {
|
||
selector: 'input[name="venue[State]"]',
|
||
metaKey: '_VenueStateProvince',
|
||
type: 'text'
|
||
},
|
||
'Venue Zip': {
|
||
selector: 'input[name="venue[Zip]"]',
|
||
metaKey: '_VenueZip',
|
||
type: 'text'
|
||
},
|
||
'Venue Country': {
|
||
selector: 'select[name="venue[Country]"]',
|
||
metaKey: '_VenueCountry',
|
||
type: 'select'
|
||
},
|
||
'Venue Phone': {
|
||
selector: 'input[name="venue[Phone]"]',
|
||
metaKey: '_VenuePhone',
|
||
type: 'tel'
|
||
},
|
||
'Venue Website': {
|
||
selector: 'input[name="venue[URL]"]',
|
||
metaKey: '_VenueURL',
|
||
type: 'url'
|
||
}
|
||
},
|
||
|
||
// Organizer Fields
|
||
organizer: {
|
||
'Organizer ID': {
|
||
selector: 'select[name="organizer[OrganizerID]"]',
|
||
metaKey: '_EventOrganizerID',
|
||
type: 'select'
|
||
},
|
||
'Organizer Name': {
|
||
selector: 'input[name="organizer[Organizer]"]',
|
||
metaKey: '_OrganizerOrganizer',
|
||
type: 'text'
|
||
},
|
||
'Organizer Email': {
|
||
selector: 'input[name="organizer[Email]"]',
|
||
metaKey: '_OrganizerEmail',
|
||
type: 'email'
|
||
},
|
||
'Organizer Phone': {
|
||
selector: 'input[name="organizer[Phone]"]',
|
||
metaKey: '_OrganizerPhone',
|
||
type: 'tel'
|
||
},
|
||
'Organizer Website': {
|
||
selector: 'input[name="organizer[Website]"]',
|
||
metaKey: '_OrganizerWebsite',
|
||
type: 'url'
|
||
}
|
||
}
|
||
};
|
||
|
||
async function screenshot(page, name) {
|
||
await fs.mkdir('screenshots/tec-v5-validated', { recursive: true });
|
||
const path = `screenshots/tec-v5-validated/${name}-${Date.now()}.png`;
|
||
await page.screenshot({ path, fullPage: true });
|
||
console.log(`📸 Screenshot: ${name}`);
|
||
return path;
|
||
}
|
||
|
||
async function getFieldValue(page, fieldDef) {
|
||
try {
|
||
const element = await page.$(fieldDef.selector);
|
||
if (!element) return null;
|
||
|
||
switch (fieldDef.type) {
|
||
case 'checkbox':
|
||
return await element.isChecked();
|
||
case 'select':
|
||
return await element.inputValue();
|
||
case 'wysiwyg':
|
||
case 'textarea':
|
||
// Try to get from the actual textarea or content div
|
||
const textValue = await element.inputValue().catch(() => null);
|
||
if (textValue) return textValue;
|
||
return await element.textContent();
|
||
default:
|
||
return await element.inputValue();
|
||
}
|
||
} catch (e) {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
async function setFieldValue(page, fieldDef, value) {
|
||
try {
|
||
const element = await page.$(fieldDef.selector);
|
||
if (!element) return false;
|
||
|
||
switch (fieldDef.type) {
|
||
case 'checkbox':
|
||
if (value) {
|
||
await element.check();
|
||
} else {
|
||
await element.uncheck();
|
||
}
|
||
return true;
|
||
case 'select':
|
||
await element.selectOption(value);
|
||
return true;
|
||
case 'wysiwyg':
|
||
case 'textarea':
|
||
case 'text':
|
||
case 'date':
|
||
case 'time':
|
||
case 'url':
|
||
case 'email':
|
||
case 'tel':
|
||
default:
|
||
await element.fill(value.toString());
|
||
return true;
|
||
}
|
||
} catch (e) {
|
||
console.log(` Error setting field: ${e.message}`);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
async function runTECv5ValidatedTest() {
|
||
console.log('🎯 TEC v5.0.8 VALIDATED FIELD TEST');
|
||
console.log('=' .repeat(70));
|
||
console.log('Using correct field selectors from TEC v5.0.8 documentation');
|
||
console.log('=' .repeat(70));
|
||
|
||
const browser = await chromium.launch({
|
||
headless: false,
|
||
args: ['--no-sandbox', '--disable-setuid-sandbox']
|
||
});
|
||
|
||
const page = await browser.newPage();
|
||
|
||
const results = {
|
||
loginSuccess: false,
|
||
eventsFound: 0,
|
||
fieldsFound: {},
|
||
fieldsPopulated: {},
|
||
fieldsTested: 0,
|
||
fieldsWorking: 0,
|
||
editTestResults: {}
|
||
};
|
||
|
||
try {
|
||
// 1. LOGIN
|
||
console.log('\n📝 STEP 1: Login');
|
||
console.log('-' .repeat(50));
|
||
await page.goto(`${CONFIG.baseUrl}/training-login`);
|
||
await page.waitForLoadState('networkidle');
|
||
|
||
// Use the training login form selectors
|
||
await page.fill('input[name="log"]', CONFIG.credentials.username);
|
||
await page.fill('input[name="pwd"]', CONFIG.credentials.password);
|
||
await page.click('input[type="submit"]');
|
||
|
||
// Wait for redirect to dashboard
|
||
await page.waitForURL('**/dashboard/**', { timeout: 15000 });
|
||
results.loginSuccess = true;
|
||
console.log('✅ Logged in successfully');
|
||
|
||
// 2. ACCESS EVENT EDIT FORM
|
||
console.log('\n📝 STEP 2: Opening Event Edit Form');
|
||
console.log('-' .repeat(50));
|
||
|
||
// From dashboard, go to manage events page
|
||
await page.goto(`${CONFIG.baseUrl}/trainer/event/manage/`);
|
||
await page.waitForLoadState('networkidle');
|
||
await screenshot(page, 'manage-events-page');
|
||
|
||
// Look for event list on the manage page
|
||
const eventLinks = await page.$$('.hvac-events-list a[href*="edit"], .event-actions a:has-text("Edit")');
|
||
results.eventsFound = eventLinks.length;
|
||
console.log(`✅ Found ${results.eventsFound} events on manage page`);
|
||
|
||
if (results.eventsFound === 0) {
|
||
// Try alternate approach - go directly to wp-admin
|
||
console.log('⚠️ No events on manage page, trying wp-admin...');
|
||
await page.goto(`${CONFIG.baseUrl}/wp-admin/edit.php?post_type=tribe_events`);
|
||
await page.waitForLoadState('networkidle');
|
||
|
||
const eventRows = await page.$$('tbody#the-list tr');
|
||
results.eventsFound = eventRows.length;
|
||
console.log(` Found ${results.eventsFound} events in wp-admin`);
|
||
|
||
if (results.eventsFound === 0) {
|
||
console.log('❌ No events found - running seed script...');
|
||
execSync('bash bin/create-test-admin-and-seed.sh', { stdio: 'inherit' });
|
||
await page.reload();
|
||
await page.waitForLoadState('networkidle');
|
||
}
|
||
|
||
// Click edit on first event from wp-admin
|
||
const editLink = await page.$('tbody#the-list tr:first-child .row-actions .edit a');
|
||
if (editLink) {
|
||
await editLink.click();
|
||
}
|
||
} else {
|
||
// Click first edit link from manage page
|
||
await eventLinks[0].click();
|
||
}
|
||
|
||
await page.waitForLoadState('networkidle');
|
||
console.log('✅ Edit form opened');
|
||
await screenshot(page, 'edit-form');
|
||
|
||
// 3. TEST FIELD DISCOVERY WITH CORRECT SELECTORS
|
||
console.log('\n📝 STEP 3: Testing TEC v5.0.8 Field Selectors');
|
||
console.log('-' .repeat(50));
|
||
|
||
for (const [category, fields] of Object.entries(TEC_FIELDS)) {
|
||
console.log(`\n🔍 Testing ${category.toUpperCase()} fields:`);
|
||
|
||
for (const [fieldName, fieldDef] of Object.entries(fields)) {
|
||
results.fieldsTested++;
|
||
|
||
const element = await page.$(fieldDef.selector);
|
||
if (element) {
|
||
const isVisible = await element.isVisible().catch(() => false);
|
||
const value = await getFieldValue(page, fieldDef);
|
||
|
||
results.fieldsFound[fieldName] = {
|
||
found: true,
|
||
visible: isVisible,
|
||
value: value,
|
||
selector: fieldDef.selector,
|
||
metaKey: fieldDef.metaKey
|
||
};
|
||
|
||
if (isVisible) {
|
||
results.fieldsWorking++;
|
||
if (value !== null && value !== '' && value !== false) {
|
||
results.fieldsPopulated[fieldName] = value;
|
||
console.log(` ✅ ${fieldName}: POPULATED - "${String(value).substring(0, 50)}"`);
|
||
} else {
|
||
console.log(` ✅ ${fieldName}: FOUND (empty)`);
|
||
}
|
||
} else {
|
||
console.log(` ⚠️ ${fieldName}: Hidden`);
|
||
}
|
||
} else {
|
||
results.fieldsFound[fieldName] = {
|
||
found: false,
|
||
selector: fieldDef.selector
|
||
};
|
||
console.log(` ❌ ${fieldName}: NOT FOUND`);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 4. EDIT TEST WITH CORRECT SELECTORS
|
||
console.log('\n📝 STEP 4: Testing Field Updates');
|
||
console.log('-' .repeat(50));
|
||
|
||
const testEdits = {
|
||
'Event Title': 'TEC v5.0.8 Test Event - EDITED',
|
||
'Start Date': '2025-12-25',
|
||
'End Date': '2025-12-26',
|
||
'Event Cost': '999',
|
||
'Event URL': 'https://tec-v5-test.example.com'
|
||
};
|
||
|
||
for (const [fieldName, newValue] of Object.entries(testEdits)) {
|
||
// Find the field definition
|
||
let fieldDef = null;
|
||
for (const category of Object.values(TEC_FIELDS)) {
|
||
if (category[fieldName]) {
|
||
fieldDef = category[fieldName];
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (fieldDef) {
|
||
const success = await setFieldValue(page, fieldDef, newValue);
|
||
if (success) {
|
||
console.log(` ✅ ${fieldName} updated to: ${newValue}`);
|
||
results.editTestResults[fieldName] = 'updated';
|
||
} else {
|
||
console.log(` ❌ ${fieldName} update failed`);
|
||
results.editTestResults[fieldName] = 'failed';
|
||
}
|
||
}
|
||
}
|
||
|
||
await screenshot(page, 'after-edits');
|
||
|
||
// 5. SAVE AND VERIFY
|
||
console.log('\n📝 STEP 5: Saving and Verifying');
|
||
console.log('-' .repeat(50));
|
||
|
||
const publishButton = await page.$('#publish');
|
||
if (publishButton) {
|
||
await publishButton.click();
|
||
console.log(' Saving changes...');
|
||
|
||
try {
|
||
await page.waitForSelector('.notice-success, #message', { timeout: 10000 });
|
||
console.log('✅ Changes saved');
|
||
} catch (e) {
|
||
console.log('⚠️ Save confirmation not detected');
|
||
}
|
||
|
||
// Reload and verify
|
||
await page.reload();
|
||
await page.waitForLoadState('networkidle');
|
||
|
||
console.log('\n Verifying persistence:');
|
||
for (const [fieldName, expectedValue] of Object.entries(testEdits)) {
|
||
let fieldDef = null;
|
||
for (const category of Object.values(TEC_FIELDS)) {
|
||
if (category[fieldName]) {
|
||
fieldDef = category[fieldName];
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (fieldDef) {
|
||
const currentValue = await getFieldValue(page, fieldDef);
|
||
if (currentValue === expectedValue ||
|
||
(currentValue && currentValue.includes && currentValue.includes(expectedValue))) {
|
||
console.log(` ✅ ${fieldName}: PERSISTED`);
|
||
results.editTestResults[fieldName] = 'persisted';
|
||
} else {
|
||
console.log(` ❌ ${fieldName}: NOT persisted (current: ${currentValue})`);
|
||
results.editTestResults[fieldName] = 'not_persisted';
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
} 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();
|
||
}
|
||
|
||
return results;
|
||
}
|
||
|
||
// Run the test
|
||
console.log('Starting TEC v5.0.8 validated field test...\n');
|
||
runTECv5ValidatedTest().then(results => {
|
||
console.log('\n' + '=' .repeat(70));
|
||
console.log('📊 TEC v5.0.8 VALIDATION REPORT');
|
||
console.log('=' .repeat(70));
|
||
|
||
console.log('\n✅ Field Discovery Results:');
|
||
console.log(` • Total fields tested: ${results.fieldsTested}`);
|
||
console.log(` • Fields found & visible: ${results.fieldsWorking}`);
|
||
console.log(` • Fields populated: ${Object.keys(results.fieldsPopulated).length}`);
|
||
|
||
console.log('\n✅ Working Field Selectors:');
|
||
for (const [field, data] of Object.entries(results.fieldsFound)) {
|
||
if (data.found && data.visible) {
|
||
console.log(` • ${field}: ${data.selector}`);
|
||
}
|
||
}
|
||
|
||
console.log('\n⚠️ Missing or Hidden Fields:');
|
||
for (const [field, data] of Object.entries(results.fieldsFound)) {
|
||
if (!data.found || !data.visible) {
|
||
console.log(` • ${field}: ${data.found ? 'Hidden' : 'Not Found'} (${data.selector})`);
|
||
}
|
||
}
|
||
|
||
console.log('\n✅ Edit Test Results:');
|
||
const persistedCount = Object.values(results.editTestResults)
|
||
.filter(r => r === 'persisted').length;
|
||
console.log(` • Changes persisted: ${persistedCount}/${Object.keys(results.editTestResults).length}`);
|
||
|
||
for (const [field, result] of Object.entries(results.editTestResults)) {
|
||
const icon = result === 'persisted' ? '✅' : '❌';
|
||
console.log(` ${icon} ${field}: ${result}`);
|
||
}
|
||
|
||
console.log('\n' + '=' .repeat(70));
|
||
console.log('📌 SUMMARY');
|
||
console.log('-' .repeat(70));
|
||
|
||
if (results.fieldsWorking >= 20 && persistedCount >= 3) {
|
||
console.log('\n✅✅✅ TEC v5.0.8 VALIDATION SUCCESSFUL! ✅✅✅');
|
||
console.log('Field selectors are correct and working!');
|
||
console.log('Event editing with TEC v5.0.8 is fully functional!');
|
||
} else {
|
||
console.log('\n⚠️ PARTIAL SUCCESS');
|
||
console.log('Some field selectors may need adjustment.');
|
||
console.log('Review the missing/hidden fields above.');
|
||
}
|
||
|
||
console.log('\n📁 Screenshots saved to: screenshots/tec-v5-validated/');
|
||
console.log('📄 Field mapping documented in: docs/TEC-V5-FIELD-MAPPING.md');
|
||
console.log('=' .repeat(70));
|
||
|
||
}).catch(error => {
|
||
console.error('Fatal error:', error);
|
||
process.exit(1);
|
||
}); |