409 lines
14 KiB
JavaScript
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}`);
|
|
});
|
|
});
|
|
});
|