dom-selector-diagnostic.js•20 kB
/**
* @document DOM Selector Diagnostic Tool - Authenticated Version
* @version 1.1.1
* @status active
* @author Claude
* @created 2025-06-29
* @last_updated 2025-06-29
*/
import { chromium } from 'playwright';
import { writeFile } from 'fs/promises';
/**
* Authenticated DOM Selector Diagnostic Tool
* Uses the same authentication flow as the existing MCP server
* to analyze the EuConquisto Composer interface correctly.
*/
class AuthenticatedDOMDiagnostic {
constructor() {
this.browser = null;
this.page = null;
this.baseURL = "https://composer.euconquisto.com/#/embed";
this.orgId = "36c92686-c494-ec11-a22a-dc984041c95d";
this.jwtToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImFkbWluLmRlc2Vudm9sdmltZW50b0BldWNvbnF1aXN0by5jb20iLCJuYW1lIjoiQWRtaW4gRGV2Iiwib2lkIjoiNWZiM2RlYzYtYzQ5NC1lYzExLWEyMmEtZGM5ODQwNDFjOTVkIiwiZGlyZWN0b3J5IjoiYjBmZWY4NjAtYzQ5NC1lYzExLWEyMmEtZGM5ODQwNDFjOTVkIiwiYXBpbSI6IkVFQzUzQTI0LUVDMEUtNDFCOS05NDA1LTg2QTE3NTAwREIzNCIsImRpcm4iOiJEZXNlbnZvbHZpbWVudG8iLCJyb2xlIjpbIlJldmlld0NvbnRyaWJ1dG9yIiwiQ29udHJpYnV0b3JHbG9iYWwiLCJBZG1pbmlzdHJhdG9yR2xvYmFsIl0sImRjbiI6WyJINHNJQUFBQUFBQUFBMkptWUdBd1pRQ0JaQXVqRkdNREM0dFYyMXRTRS1vZHA5NElpMnVVYTNqMXNxRXl6YWhrN3ZxWlN5MG5sLV94c3cyb0xEN05jT2ppRThWelhyZDBEamF3XzQyVW1fZmxrT1loZzlVN0ZKOGx5N1dWQVFBQUFQX18iLCJINHNJQUFBQUFBQUFBMkptWUdBd1pRQ0JaQXVqRkdNREM0c2xCWS0zTjB5WkU1S1NNSFhKTzhjcm0wNS1remgtYVBzTXhtMnZvOTBxYzhXTjktX08wRzRMbmo5dDRqb2R4bnZNdVkycHJEd1NrYlkyamJOMnpuNXJvemJmR3dBQUFQX18iLCJINHNJQUFBQUFBQUFBMkptWUdBd1pRQ0JaQXVqRkdNREM0dnpkMWU1SDFGTzVhenRyR1RuZXZ6emx0U0djM3VFT1IteHExWXZNVHF5NEstaThXR0gwd292cC1ka3FjNm82d2xSZkM0d3k5MVcxSHFqOFU4aEMtRWZMa0pSQUFBQUFQX18iLCJINHNJQUFBQUFBQUFBMkptWUdBd1pRQ0JaQXVqRkdNREM0dU5XMnUyS3A5dXZhUGhkOFNpOE9KNV8yTmRmZnNTSC0temZHTXVmX084Wm5GbXFXRE5qeE1ubnBWelRkVTBPVF9mUldtNVVhTkxlV2oyTjY5Y3JRS0x1SmJQQUFBQUFQX18iLCJINHNJQUFBQUFBQUFBMkptWUdBd1pRQ0JaQXVqRkdNREM0czY2NE5odVhwck52S3FiV1RuUEtGNTU2UzRycXdXNDJ1SnkzbjNGVkxFVjZ4VlljdU9kTnVrZXliMjdKbjRlOS1GaGNNdW1iOTZ1cUx0aEptSDJrX0h0U29MQUFBQUFQX18iLCJINHNJQUFBQUFBQUFBMkptWUdBd1pRQ0JaQXVqRkdNREN3dWpCWjhyQW81RUxEaHRvQ2ZUdmVQcTVET2hpd1dtM3A2eFktTDdIYkpTb2RFU2I2SUVWQ3BQcXpETTJ2WlR6VzlsMXBrWlBFb3Zkbl8tV3R2LWY0bkt3dmEtU0FBQUFBRF9fdyJdLCJzY3AiOlsiSDRzSUFBQUFBQUFBQTJKbVlHRHdZQUFCMDZRMDA5VFU1RlNKTmZzV1NKNDRWWGZBeHZYeDVEc3lzaFhPNmllVGk0cWVYbTZaVVN2QzZ2djNST0hNRDBkVHBDVHUzekFfVUMtWVVIOHIyMzNlS29mQ0hZR3UwWG83aWswWGROWVVxdmotV0hfclhiU3VVYnJZM1VZQUFBQUFfXzgiLCJINHNJQUFBQUFBQUFBd0RTQWkzOUF3QUFBck1BQUFBQUFHRTVaalF6TkRsa2E0TWozbjdQejY0dmpPSWl5LVlNb3dhRDdlMWpqcDZJakE2SVFZMi14TFNOTWFSZWNrTTdzWEJLamR2SkpoeUF5TnZJVHQ5aUFFWHcybVVzM3lIbGc2UHNtSld1Mm1EU2UwbXlrYnQ0OGl1NTlkYTIxMVBNdkxyRzdxOV9teDlhYWI0SjdZbWlNODg2OTVUclk3WFY1Nmlsb3UwdnQ4VmdBMWptbkdYd0paa0ZDTXhYaUlhcGl5S01TVGlfa3BnREdQdlRlYUNNdE5GSXlIU0gwUTZ3bTFwRkJBYzRMQ1dqRThMeWtnc1ZtbEJxRG9aek1oZTVnY21sZHl0VW1FVFltakIyakVaUHdvd0M2NFRZb1JudDJOWkpmRDdDZXg1T3FqY1ptVU5tNjMtVlRYOFh0UEtza1BpSnpIb3kzcHF5dXVycnVrTndZenducHpwak51OEdKZXQ4V2RoMl85ckNoVnpPUnR4N1N5UXEyb2prUmJfWEphenFtTnEwMEVTcTNSZUVSX0pQZmhGOGg4bGlxcGVWeE1FSG5RUGFmMXp2N3FVQ3JzTWtrVjd3NU5sOURWYU9DMnNLV2Q1TDdBNDYxUlFRSjVUNU5MMGQ2UGZqVWV4dkZTR0RQdW5jcHAxeDA0TlBOM2ttTmdMSlF3eDZLZjRxLVBZbDVXWmFQUTlXU0NLNHBmeHY3ZTJRZEw0X1IwQ1c3NUdQRHh1bEZQZG1tcmRrc0k2RVhqUTFkQTdkZlNFUnJoZHdvOUhCN0xGMzRYSENnMWg3Z2I4UWVKdmhMTXRjUmtlUzVpQkctOGNfNkdCQU9YUDdQR2ptQ1NUZzV2WGVyX0Rud3FkektCOFdtY1p0OXFjMXZpU0ROV1JHZVN0bHc3d250UzFSSXlBN205cS01YU9FLUx0ODB3bE1HTkxOTi1TVFN3OWVDN2dxVmQtSUZZWlhYQ2NZcWNfcWc1dDhodFdSLVJ2cUJsX3Fjbkt2N2xhaHk5elRTaHJfX1BTRWJqdTg0SkhDV3I5SXhGd2lzYVd1eVZiMUp0ZUQzcDk0WnpWZDhnTEJQRGFRVUJMZHc1SVEzWkJIc3daTXExRjJqVGdEcF92RDJLQzZzVnd1dVhaa3RzYWlrc3pMZW5NcTFUVjRUMmF5QUdyVHB3UlUzMFMwc3hfQ3pCRVlLREV0b2s4eDI5TGlnUnlHMnVfZDVpOHRyVEJ1WTNjVFdzSXVIdzRibXNVV1lkTXNwblRMMXpwNE1LWUFBQURfX3ciXSwibmJmIjoxNzQ4ODc2OTU1LCJleHAiOjE3NTE0Njg5NTUsImlhdCI6MTc0ODg3Njk1NSwiaXNzIjoiaHR0cHM6Ly9hcGkuZGlnaXRhbHBhZ2VzLmNvbS5iciIsImF1ZCI6IkV1Q29ucXVpc3RvIn0.iTUfl6-mwLwFaxYYPf6PufRSYbSJlw3tKejmbc5G42g";
this.results = {
timestamp: new Date().toISOString(),
url: '',
authentication: {},
selectors: {},
errors: [],
recommendations: []
};
}
/**
* Initialize browser with same config as MCP server
*/
async initialize() {
try {
console.log('🚀 Initializing authenticated browser diagnostic...');
this.browser = await chromium.launch({
headless: false,
args: [
'--no-sandbox',
'--disable-dev-shm-usage',
'--enable-features=VizDisplayCompositor',
'--disable-background-timer-throttling',
'--disable-backgrounding-occluded-windows',
'--disable-renderer-backgrounding',
'--disable-features=TranslateUI',
'--disable-ipc-flooding-protection',
'--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
],
timeout: 60000
});
const context = await this.browser.newContext({
viewport: { width: 1280, height: 720 },
permissions: ['notifications'],
colorScheme: 'light',
timezoneId: 'America/New_York'
});
this.page = await context.newPage();
this.page.setDefaultTimeout(60000);
this.page.setDefaultNavigationTimeout(120000);
console.log('✅ Browser initialized with MCP server configuration');
return true;
} catch (error) {
this.results.errors.push(`Initialization failed: ${error.message}`);
console.error('❌ Initialization failed:', error.message);
return false;
}
}
/**
* Navigate using the same authentication flow as MCP server
*/
async navigateWithAuth() {
if (!this.page) {
throw new Error('Browser not initialized');
}
const embedURL = `${this.baseURL}/auth-with-token/pt_br/home/${this.orgId}/${this.jwtToken}`;
try {
console.log('🌐 Navigating with JWT authentication...');
console.log(`🔗 URL: ${embedURL.substring(0, 80)}...`);
const response = await this.page.goto(embedURL, {
waitUntil: 'domcontentloaded',
timeout: 120000
});
if (!response || !response.ok()) {
throw new Error(`Navigation failed with status: ${response?.status()}`);
}
this.results.url = this.page.url();
this.results.authentication = {
jwtProvided: true,
navigationStatus: response.status(),
finalURL: this.results.url
};
console.log(`📍 Successfully navigated to: ${this.results.url}`);
// Wait for SPA to initialize
console.log('⏳ Waiting for JavaScript application to initialize...');
let attempts = 0;
const maxAttempts = 12;
while (attempts < maxAttempts) {
attempts++;
console.log(` 🔄 Attempt ${attempts}/${maxAttempts}: Checking for app initialization...`);
await this.page.waitForTimeout(5000);
const hasInteractiveElements = await this.page.evaluate(() => {
const buttons = document.querySelectorAll('button, a, [role="button"], input[type="button"]').length;
const hasContent = document.body.textContent &&
document.body.textContent.length > 100 &&
!document.body.textContent.includes('You need to enable JavaScript');
return buttons > 0 || hasContent;
});
if (hasInteractiveElements) {
console.log('✅ JavaScript application initialized successfully');
break;
}
if (attempts === maxAttempts) {
console.warn('⚠️ App may not have fully initialized, but continuing...');
}
}
// Take debug screenshot
await this.page.screenshot({
path: `debug-authenticated-${Date.now()}.png`,
fullPage: true
});
return true;
} catch (error) {
this.results.errors.push(`Authentication navigation failed: ${error.message}`);
console.error('❌ Authentication navigation failed:', error);
if (this.page) {
await this.page.screenshot({
path: `error-auth-${Date.now()}.png`,
fullPage: true
});
}
return false;
}
}
/**
* Test Nova Composição selectors from MCP server code
*/
async testNovaComposicaoSelectors() {
console.log('🔍 Testing Nova Composição selectors from MCP server...');
// These are the exact selectors from the MCP server composition-lifecycle.ts
const mcpSelectors = [
'text=NOVA COMPOSIÇÃO',
'button:has-text("NOVA COMPOSIÇÃO")',
'[role="button"]:has-text("NOVA COMPOSIÇÃO")',
'text="Nova Composição"',
'button:has-text("Nova Composição")',
'text="Nova composição"',
'button:has-text("Nova composição")',
'button[class*="btn"]',
'.btn-primary',
'button[style*="background"]',
'button:text("NOVA")',
'button:text("Nova")',
'a:text("NOVA COMPOSIÇÃO")',
'button',
'[role="button"]'
];
const results = {};
for (const selector of mcpSelectors) {
try {
const elements = await this.page.$$(selector);
const elementDetails = [];
for (let i = 0; i < Math.min(elements.length, 3); i++) {
const element = elements[i];
const text = await element.textContent();
const isVisible = await element.isVisible();
const isEnabled = await element.isEnabled();
const boundingBox = await element.boundingBox();
elementDetails.push({
text: text?.trim(),
visible: isVisible,
enabled: isEnabled,
boundingBox: boundingBox
});
if (isVisible && isEnabled && text && text.includes('Nova')) {
console.log(`✅ Found viable Nova Composição selector: ${selector} - "${text.trim()}"`);
}
}
results[selector] = {
found: elements.length > 0,
count: elements.length,
elements: elementDetails
};
} catch (error) {
results[selector] = {
found: false,
error: error.message
};
}
}
this.results.selectors.novaComposicao = results;
return results;
}
/**
* Scan and catalog ALL page elements for analysis
*/
async scanAllPageElements() {
console.log('🔍 Scanning all page elements...');
try {
const pageData = await this.page.evaluate(() => {
const results = {
allButtons: [],
allLinks: [],
allInteractive: [],
pageText: document.body.textContent?.substring(0, 500) || 'No text content',
totalElements: 0
};
// Scan all buttons
document.querySelectorAll('button').forEach((el, index) => {
const rect = el.getBoundingClientRect();
if (rect.width > 0 && rect.height > 0) {
results.allButtons.push({
index,
text: el.textContent?.trim() || '',
className: el.className || '',
id: el.id || '',
visible: rect.width > 0 && rect.height > 0,
ariaLabel: el.getAttribute('aria-label') || '',
title: el.title || '',
boundingBox: {
x: Math.round(rect.x),
y: Math.round(rect.y),
width: Math.round(rect.width),
height: Math.round(rect.height)
}
});
}
});
// Scan all links
document.querySelectorAll('a').forEach((el, index) => {
const rect = el.getBoundingClientRect();
if (rect.width > 0 && rect.height > 0) {
results.allLinks.push({
index,
text: el.textContent?.trim() || '',
href: el.href || '',
className: el.className || '',
id: el.id || ''
});
}
});
// Scan all interactive elements
document.querySelectorAll('[onclick], [role="button"], input, select, textarea').forEach((el, index) => {
const rect = el.getBoundingClientRect();
if (rect.width > 0 && rect.height > 0) {
results.allInteractive.push({
index,
tagName: el.tagName.toLowerCase(),
text: el.textContent?.trim().substring(0, 50) || '',
className: el.className || '',
id: el.id || '',
type: el.type || '',
role: el.role || ''
});
}
});
results.totalElements = results.allButtons.length + results.allLinks.length + results.allInteractive.length;
return results;
});
this.results.pageElements = pageData;
console.log(`📊 Found ${pageData.totalElements} interactive elements:`);
console.log(` - ${pageData.allButtons.length} buttons`);
console.log(` - ${pageData.allLinks.length} links`);
console.log(` - ${pageData.allInteractive.length} other interactive elements`);
return pageData;
} catch (error) {
this.results.errors.push(`Page scan failed: ${error.message}`);
return null;
}
}
/**
* Generate targeted recommendations based on findings
*/
generateRecommendations() {
console.log('💡 Generating targeted recommendations...');
const recommendations = [];
// Check authentication
if (this.results.authentication.jwtProvided) {
recommendations.push({
type: 'success',
category: 'Authentication',
message: 'JWT authentication successful',
details: `Navigation status: ${this.results.authentication.navigationStatus}`,
action: 'Authentication working correctly'
});
}
// Check Nova Composição selectors
const novaResults = this.results.selectors.novaComposicao || {};
const workingSelectors = Object.entries(novaResults)
.filter(([_, result]) => result.found && result.elements?.some(el => el.visible && el.enabled))
.map(([selector, _]) => selector);
if (workingSelectors.length > 0) {
recommendations.push({
type: 'success',
category: 'Nova Composição Selectors',
message: `Found ${workingSelectors.length} working selectors`,
selectors: workingSelectors.slice(0, 5), // Top 5 working selectors
action: 'Update MCP server to use the most reliable selector'
});
} else {
recommendations.push({
type: 'warning',
category: 'Nova Composição Selectors',
message: 'No working selectors found with current strategy',
action: 'Review page elements to identify correct button selector'
});
}
// Check page elements
if (this.results.pageElements) {
const buttonTexts = this.results.pageElements.allButtons
.filter(btn => btn.text && btn.visible)
.map(btn => btn.text)
.slice(0, 10);
if (buttonTexts.length > 0) {
recommendations.push({
type: 'info',
category: 'Available Page Buttons',
message: `Found ${buttonTexts.length} visible buttons`,
buttons: buttonTexts,
action: 'Review button texts to identify composition creation button'
});
}
}
this.results.recommendations = recommendations;
return recommendations;
}
/**
* Run complete authenticated diagnostic
*/
async runDiagnostic() {
try {
const initialized = await this.initialize();
if (!initialized) {
throw new Error('Failed to initialize browser');
}
const authenticated = await this.navigateWithAuth();
if (!authenticated) {
throw new Error('Failed to authenticate and navigate');
}
// Wait for page to settle
await this.page.waitForTimeout(3000);
// Run diagnostic tests
await this.testNovaComposicaoSelectors();
await this.scanAllPageElements();
// Generate targeted recommendations
this.generateRecommendations();
// Create comprehensive report
await this.generateReport();
console.log('✅ Authenticated diagnostic complete!');
console.log('📄 Reports generated:');
console.log(' - authenticated-diagnostic-report.json (detailed data)');
console.log(' - authenticated-diagnostic-summary.md (human readable)');
} catch (error) {
console.error('❌ Diagnostic failed:', error.message);
this.results.errors.push(`Diagnostic failed: ${error.message}`);
} finally {
if (this.browser) {
await this.browser.close();
}
}
}
/**
* Generate comprehensive report
*/
async generateReport() {
const report = {
...this.results,
summary: {
totalSelectors: Object.keys(this.results.selectors).length,
totalErrors: this.results.errors.length,
totalRecommendations: this.results.recommendations.length,
authenticationWorking: !!this.results.authentication.jwtProvided,
completedAt: new Date().toISOString()
}
};
await writeFile(
'./authenticated-diagnostic-report.json',
JSON.stringify(report, null, 2)
);
const summary = this.generateHumanReadableSummary(report);
await writeFile('./authenticated-diagnostic-summary.md', summary);
}
/**
* Generate human-readable summary
*/
generateHumanReadableSummary(report) {
return `# Authenticated DOM Selector Diagnostic Report
**Generated:** ${report.summary.completedAt}
**URL:** ${report.url}
**Authentication:** ${report.summary.authenticationWorking ? '✅ Working' : '❌ Failed'}
## Executive Summary
- Authentication: ${report.summary.authenticationWorking ? 'SUCCESS' : 'FAILED'}
- Selectors Tested: ${report.summary.totalSelectors}
- Errors: ${report.summary.totalErrors}
- Recommendations: ${report.summary.totalRecommendations}
## Key Findings
### Authentication Status
${report.authentication.jwtProvided ?
`✅ JWT authentication successful (Status: ${report.authentication.navigationStatus})` :
'❌ JWT authentication failed'
}
### Nova Composição Selector Analysis
${Object.entries(report.selectors.novaComposicao || {})
.filter(([_, result]) => result.found && result.elements?.some(el => el.visible && el.enabled))
.map(([selector, result]) => `✅ **${selector}** - Found ${result.count} elements`)
.join('\n') || '❌ No working selectors identified'}
### Available Page Buttons
${report.pageElements?.allButtons
?.filter(btn => btn.visible && btn.text)
?.slice(0, 10)
?.map(btn => `- "${btn.text}" (${btn.className || 'no class'})`)
?.join('\n') || 'No buttons detected'}
## Recommendations
${report.recommendations.map(rec => `
### ${rec.category}
**Type:** ${rec.type}
**Message:** ${rec.message}
**Action:** ${rec.action}
${rec.selectors ? `**Working Selectors:** ${rec.selectors.join(', ')}` : ''}
${rec.buttons ? `**Available Buttons:** ${rec.buttons.join(', ')}` : ''}
`).join('\n')}
## Next Steps
1. Review working selectors and update MCP server code
2. Test with identified button selectors
3. Verify composition creation workflow
4. Update browser automation error handling
---
*This report provides the data needed to fix MCP server browser automation*
`;
}
}
// Main execution
async function main() {
const diagnostic = new AuthenticatedDOMDiagnostic();
await diagnostic.runDiagnostic();
}
// Export for use in other modules
export { AuthenticatedDOMDiagnostic };
// Run if called directly
if (import.meta.url === `file://${process.argv[1]}`) {
main().catch(console.error);
}