Files
cv-site/tests/manual-inspection.spec.js
T
juanatsap 25e9ebafe7 bf fixes
2025-11-16 10:11:58 +00:00

409 lines
14 KiB
JavaScript

/**
* MANUAL INSPECTION - Deep Dive into Features
* Investigates specific issues found in comprehensive tests
*/
import { test, expect } from '@playwright/test';
const BASE_URL = 'http://localhost:1999';
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
test.describe('MANUAL INSPECTION: Feature Deep Dive', () => {
test('Inspect page structure and all interactive elements', async ({ page }) => {
await page.goto(`${BASE_URL}/?lang=en`);
await wait(1000);
console.log('\n=== PAGE STRUCTURE INSPECTION ===\n');
// Find all buttons
const allButtons = await page.$$('button');
console.log(`Total buttons: ${allButtons.length}`);
for (let i = 0; i < allButtons.length; i++) {
const btn = allButtons[i];
const text = await btn.textContent();
const id = await btn.getAttribute('id');
const dataAction = await btn.getAttribute('data-action');
const classes = await btn.getAttribute('class');
console.log(`Button ${i + 1}: text="${text?.trim()}" id="${id}" data-action="${dataAction}" class="${classes}"`);
}
// Find all toggles
console.log('\n=== TOGGLE CONTROLS ===\n');
const toggles = await page.$$('input[type="checkbox"]');
console.log(`Total checkboxes: ${toggles.length}`);
for (let i = 0; i < toggles.length; i++) {
const toggle = toggles[i];
const id = await toggle.getAttribute('id');
const hxGet = await toggle.getAttribute('hx-get');
const hxPost = await toggle.getAttribute('hx-post');
const hxIndicator = await toggle.getAttribute('hx-indicator');
console.log(`Toggle ${i + 1}: id="${id}" hx-get="${hxGet}" hx-post="${hxPost}" hx-indicator="${hxIndicator}"`);
}
// Find modals/dialogs
console.log('\n=== MODALS/DIALOGS ===\n');
const dialogs = await page.$$('dialog');
console.log(`Native dialogs: ${dialogs.length}`);
for (let i = 0; i < dialogs.length; i++) {
const dialog = dialogs[i];
const id = await dialog.getAttribute('id');
const classes = await dialog.getAttribute('class');
const textPreview = (await dialog.textContent())?.substring(0, 50);
console.log(`Dialog ${i + 1}: id="${id}" class="${classes}" preview="${textPreview}..."`);
}
// Find HTMX indicators
console.log('\n=== HTMX INDICATORS ===\n');
const indicators = await page.$$('.htmx-indicator, [class*="indicator"], [class*="loading"], [class*="spinner"]');
console.log(`Indicator elements: ${indicators.length}`);
for (let i = 0; i < indicators.length; i++) {
const indicator = indicators[i];
const classes = await indicator.getAttribute('class');
const id = await indicator.getAttribute('id');
console.log(`Indicator ${i + 1}: id="${id}" class="${classes}"`);
}
// Find skeleton loaders
console.log('\n=== SKELETON LOADERS ===\n');
const skeletons = await page.$$('[class*="skeleton"], [class*="shimmer"]');
console.log(`Skeleton elements: ${skeletons.length}`);
for (let i = 0; i < skeletons.length; i++) {
const skeleton = skeletons[i];
const classes = await skeleton.getAttribute('class');
const id = await skeleton.getAttribute('id');
console.log(`Skeleton ${i + 1}: id="${id}" class="${classes}"`);
}
await page.screenshot({ path: 'test-results/inspect-full-page.png', fullPage: true });
});
test('Test language switch with detailed timing', async ({ page }) => {
await page.goto(`${BASE_URL}/?lang=en`);
await wait(1000);
console.log('\n=== LANGUAGE SWITCH DETAILED TIMING ===\n');
// Find ES button
const esButton = await page.locator('button').filter({ hasText: 'ES' }).first();
// Monitor all DOM changes during switch
await page.evaluate(() => {
window.transitionLog = [];
window.startTime = Date.now();
// Monitor skeleton
const observer = new MutationObserver(() => {
const skeleton = document.querySelector('[class*="skeleton"]');
if (skeleton) {
const opacity = window.getComputedStyle(skeleton).opacity;
const display = window.getComputedStyle(skeleton).display;
window.transitionLog.push({
time: Date.now() - window.startTime,
event: 'skeleton',
opacity,
display
});
}
});
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['class', 'style']
});
// Monitor HTMX events
document.body.addEventListener('htmx:beforeSwap', () => {
window.transitionLog.push({ time: Date.now() - window.startTime, event: 'beforeSwap' });
});
document.body.addEventListener('htmx:afterSwap', () => {
window.transitionLog.push({ time: Date.now() - window.startTime, event: 'afterSwap' });
});
document.body.addEventListener('htmx:afterSettle', () => {
window.transitionLog.push({ time: Date.now() - window.startTime, event: 'afterSettle' });
});
});
// Click ES button
const clickTime = Date.now();
await esButton.click();
// Wait and capture screenshots at different stages
await wait(100);
await page.screenshot({ path: 'test-results/lang-switch-100ms.png', fullPage: true });
await wait(200);
await page.screenshot({ path: 'test-results/lang-switch-300ms.png', fullPage: true });
await wait(300);
await page.screenshot({ path: 'test-results/lang-switch-600ms.png', fullPage: true });
await wait(200);
const endTime = Date.now();
// Get transition log
const log = await page.evaluate(() => window.transitionLog);
console.log('Transition timeline:');
log.forEach(entry => {
console.log(` ${entry.time}ms: ${entry.event}${entry.opacity ? ` (opacity: ${entry.opacity})` : ''}`);
});
console.log(`\nTotal measured time: ${endTime - clickTime}ms`);
});
test('Inspect HTMX loading indicators in detail', async ({ page }) => {
await page.goto(`${BASE_URL}/?lang=en`);
await wait(1000);
console.log('\n=== HTMX INDICATOR INSPECTION ===\n');
// Find language button with hx attributes
const langButtons = await page.$$('button[hx-get], button[data-lang]');
console.log(`Buttons with HTMX attributes: ${langButtons.length}`);
for (let i = 0; i < langButtons.length; i++) {
const btn = langButtons[i];
const hxIndicator = await btn.getAttribute('hx-indicator');
const text = await btn.textContent();
console.log(`Button "${text?.trim()}": hx-indicator="${hxIndicator}"`);
if (hxIndicator) {
const indicatorExists = await page.locator(hxIndicator).count();
console.log(` → Indicator "${hxIndicator}" exists: ${indicatorExists > 0}`);
if (indicatorExists > 0) {
const classes = await page.locator(hxIndicator).getAttribute('class');
const styles = await page.locator(hxIndicator).evaluate(el => ({
display: window.getComputedStyle(el).display,
opacity: window.getComputedStyle(el).opacity,
visibility: window.getComputedStyle(el).visibility
}));
console.log(` → Classes: "${classes}"`);
console.log(` → Computed styles: ${JSON.stringify(styles)}`);
}
}
}
// Test clicking and monitoring
const esButton = page.locator('button').filter({ hasText: 'ES' }).first();
await page.evaluate(() => {
window.indicatorStates = [];
const observer = new MutationObserver(() => {
const indicators = document.querySelectorAll('.htmx-indicator, [class*="loading"]');
indicators.forEach((ind, idx) => {
const styles = window.getComputedStyle(ind);
window.indicatorStates.push({
time: Date.now(),
indicator: idx,
opacity: styles.opacity,
display: styles.display,
classes: ind.className
});
});
});
observer.observe(document.body, {
attributes: true,
childList: true,
subtree: true,
attributeFilter: ['class', 'style']
});
});
await esButton.click();
await wait(50);
await page.screenshot({ path: 'test-results/indicator-active-50ms.png', fullPage: true });
await wait(700);
const states = await page.evaluate(() => window.indicatorStates);
console.log('\nIndicator state changes:');
states.forEach(state => {
console.log(` ${state.time}: Indicator ${state.indicator} - opacity=${state.opacity}, display=${state.display}`);
});
});
test('Test PDF modal structure', async ({ page }) => {
await page.goto(`${BASE_URL}/?lang=en`);
await wait(1000);
console.log('\n=== PDF MODAL INSPECTION ===\n');
// Find PDF button
const pdfButtons = await page.$$('button');
let pdfButton = null;
for (const btn of pdfButtons) {
const text = (await btn.textContent())?.toLowerCase() || '';
if (text.includes('pdf') || text.includes('download')) {
pdfButton = btn;
console.log(`Found PDF button: "${await btn.textContent()}"`);
break;
}
}
if (!pdfButton) {
console.log('❌ No PDF button found');
return;
}
// Click to open modal
await pdfButton.click();
await wait(500);
await page.screenshot({ path: 'test-results/pdf-modal-detailed.png', fullPage: true });
// Inspect modal structure
const modalContent = await page.evaluate(() => {
const dialog = document.querySelector('dialog[open]');
if (!dialog) return { found: false };
const allElements = dialog.querySelectorAll('*');
const structure = {
found: true,
totalElements: allElements.length,
images: dialog.querySelectorAll('img').length,
cards: dialog.querySelectorAll('[class*="card"], [class*="thumbnail"], [data-pdf]').length,
buttons: dialog.querySelectorAll('button').length,
textContent: dialog.textContent?.substring(0, 200)
};
return structure;
});
console.log('Modal structure:', JSON.stringify(modalContent, null, 2));
// Look for specific PDF-related elements
const pdfElements = await page.$$('[data-pdf-type], [class*="pdf"], .thumbnail, .card');
console.log(`\nPDF-related elements found: ${pdfElements.length}`);
for (let i = 0; i < pdfElements.length; i++) {
const el = pdfElements[i];
const classes = await el.getAttribute('class');
const dataPdf = await el.getAttribute('data-pdf-type');
const tagName = await el.evaluate(node => node.tagName);
console.log(` Element ${i + 1}: <${tagName}> class="${classes}" data-pdf-type="${dataPdf}"`);
}
});
test('Search for shortcuts button systematically', async ({ page }) => {
await page.goto(`${BASE_URL}/?lang=en`);
await wait(1000);
console.log('\n=== SHORTCUTS BUTTON SEARCH ===\n');
// Try all possible button texts
const searchTerms = ['shortcuts', 'shortcut', 'keyboard', 'help', '?', 'atajos', 'ayuda'];
for (const term of searchTerms) {
const count = await page.locator(`button:has-text("${term}")`).count();
console.log(`Buttons containing "${term}": ${count}`);
}
// Try data attributes
const dataActions = await page.$$('[data-action]');
console.log(`\nElements with data-action: ${dataActions.length}`);
for (const el of dataActions) {
const action = await el.getAttribute('data-action');
const tagName = await el.evaluate(node => node.tagName);
const text = (await el.textContent())?.trim();
console.log(` <${tagName}> data-action="${action}" text="${text}"`);
}
// Look for info icon or help icon
const icons = await page.$$('[class*="icon"], i, svg');
console.log(`\nIcon elements: ${icons.length}`);
for (let i = 0; i < Math.min(icons.length, 20); i++) {
const icon = icons[i];
const classes = await icon.getAttribute('class');
const parent = await icon.evaluateHandle(node => node.parentElement);
const parentTag = await parent.evaluate(node => node?.tagName);
if (classes?.includes('info') || classes?.includes('help') || classes?.includes('question')) {
console.log(` Icon ${i + 1}: class="${classes}" parent=<${parentTag}>`);
}
}
});
test('Test theme switcher detection', async ({ page }) => {
await page.goto(`${BASE_URL}/?lang=en`);
await wait(1000);
console.log('\n=== THEME SWITCHER SEARCH ===\n');
// Search for theme-related elements
const themeElements = await page.$$('[data-theme], [class*="theme"], button:has-text("theme")');
console.log(`Theme-related elements: ${themeElements.length}`);
for (const el of themeElements) {
const tagName = await el.evaluate(node => node.tagName);
const classes = await el.getAttribute('class');
const dataTheme = await el.getAttribute('data-theme');
const text = (await el.textContent())?.substring(0, 30);
console.log(` <${tagName}> class="${classes}" data-theme="${dataTheme}" text="${text}"`);
}
// Check localStorage
const themeInStorage = await page.evaluate(() => localStorage.getItem('theme'));
console.log(`\nTheme in localStorage: "${themeInStorage}"`);
// Check for moon/sun icons (common theme toggle icons)
const moonSun = await page.$$('[class*="moon"], [class*="sun"], [class*="dark"], [class*="light"]');
console.log(`Moon/sun/dark/light elements: ${moonSun.length}`);
});
test('Console error monitoring', async ({ page }) => {
const errors = [];
const warnings = [];
page.on('console', msg => {
if (msg.type() === 'error') errors.push({ text: msg.text(), location: msg.location() });
if (msg.type() === 'warning') warnings.push(msg.text());
});
page.on('pageerror', error => {
errors.push({ text: error.message, stack: error.stack });
});
await page.goto(`${BASE_URL}/?lang=en`);
await wait(2000);
// Interact with features
const esButton = page.locator('button').filter({ hasText: 'ES' }).first();
if (await esButton.count() > 0) {
await esButton.click();
await wait(1000);
}
console.log('\n=== CONSOLE MONITORING ===\n');
console.log(`Errors: ${errors.length}`);
errors.forEach((err, i) => {
console.log(` Error ${i + 1}: ${err.text}`);
if (err.stack) console.log(` Stack: ${err.stack.substring(0, 100)}...`);
});
console.log(`\nWarnings: ${warnings.length}`);
warnings.forEach((warn, i) => {
console.log(` Warning ${i + 1}: ${warn}`);
});
});
});