/** * 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}`); }); }); });