619 lines
22 KiB
JavaScript
619 lines
22 KiB
JavaScript
/**
|
|
* COMPREHENSIVE FEATURE TEST SUITE
|
|
* Tests all 5 features in the CV application
|
|
*
|
|
* Features:
|
|
* 001: Keyboard Shortcuts Help Modal
|
|
* 002: Skeleton Loader for Language Transitions
|
|
* 003: HTMX Loading Indicators
|
|
* 004: Theme Switcher
|
|
* 005: PDF Download Modal
|
|
*/
|
|
|
|
import { test, expect } from '@playwright/test';
|
|
|
|
const BASE_URL = 'http://localhost:1999';
|
|
|
|
// Helper to wait for animations
|
|
const waitForAnimation = (ms = 700) => new Promise(resolve => setTimeout(resolve, ms));
|
|
|
|
test.describe('PHASE 1: DISCOVERY - Feature Detection', () => {
|
|
test('should load page and capture initial state', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/?lang=en`);
|
|
|
|
// Take screenshot of initial state
|
|
await page.screenshot({ path: 'test-results/01-initial-state.png', fullPage: true });
|
|
|
|
// Check for interactive elements
|
|
const shortcuts = await page.locator('button[data-action="show-shortcuts"], button:has-text("shortcuts"), button:has-text("atajos")').count();
|
|
const langButtons = await page.locator('button[data-lang], [hx-get*="lang"]').count();
|
|
const themeButton = await page.locator('button[data-theme], [data-action="toggle-theme"]').count();
|
|
const pdfButton = await page.locator('button:has-text("PDF"), button:has-text("download")').count();
|
|
const toggles = await page.locator('input[type="checkbox"][hx-get], input[type="checkbox"][hx-post]').count();
|
|
|
|
console.log('=== FEATURE DETECTION ===');
|
|
console.log(`Shortcuts button found: ${shortcuts > 0}`);
|
|
console.log(`Language buttons found: ${langButtons}`);
|
|
console.log(`Theme button found: ${themeButton > 0}`);
|
|
console.log(`PDF button found: ${pdfButton > 0}`);
|
|
console.log(`Toggle controls found: ${toggles}`);
|
|
|
|
expect(langButtons).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
|
|
test.describe('FEATURE 001: Keyboard Shortcuts Help Modal', () => {
|
|
test('should open shortcuts modal on button click', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/?lang=en`);
|
|
|
|
// Find shortcuts button (try multiple selectors)
|
|
const shortcutsBtn = page.locator('button[data-action="show-shortcuts"]').or(
|
|
page.locator('button:has-text("shortcuts")').first()
|
|
).or(
|
|
page.locator('button:has-text("?")').first()
|
|
);
|
|
|
|
const btnExists = await shortcutsBtn.count() > 0;
|
|
console.log(`Shortcuts button exists: ${btnExists}`);
|
|
|
|
if (!btnExists) {
|
|
console.log('⚠️ Shortcuts button NOT FOUND - Feature may not be implemented');
|
|
return;
|
|
}
|
|
|
|
// Click button
|
|
await shortcutsBtn.click();
|
|
await waitForAnimation(300);
|
|
|
|
// Verify modal opened (check for dialog or modal element)
|
|
const dialog = page.locator('dialog[open], [role="dialog"]:visible, .modal:visible');
|
|
const dialogVisible = await dialog.count() > 0;
|
|
|
|
await page.screenshot({ path: 'test-results/01-shortcuts-modal-open.png', fullPage: true });
|
|
|
|
expect(dialogVisible).toBe(true);
|
|
console.log('✅ Shortcuts modal opens on button click');
|
|
});
|
|
|
|
test('should close modal with ESC key', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/?lang=en`);
|
|
|
|
const shortcutsBtn = page.locator('button[data-action="show-shortcuts"]').or(
|
|
page.locator('button:has-text("shortcuts")').first()
|
|
);
|
|
|
|
if (await shortcutsBtn.count() === 0) return;
|
|
|
|
await shortcutsBtn.click();
|
|
await waitForAnimation(300);
|
|
|
|
// Press ESC
|
|
await page.keyboard.press('Escape');
|
|
await waitForAnimation(300);
|
|
|
|
// Verify modal closed
|
|
const dialog = page.locator('dialog[open], [role="dialog"]:visible');
|
|
const dialogClosed = await dialog.count() === 0;
|
|
|
|
await page.screenshot({ path: 'test-results/01-shortcuts-modal-closed-esc.png', fullPage: true });
|
|
|
|
expect(dialogClosed).toBe(true);
|
|
console.log('✅ Modal closes with ESC key');
|
|
});
|
|
|
|
test('should close modal on backdrop click', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/?lang=en`);
|
|
|
|
const shortcutsBtn = page.locator('button[data-action="show-shortcuts"]').or(
|
|
page.locator('button:has-text("shortcuts")').first()
|
|
);
|
|
|
|
if (await shortcutsBtn.count() === 0) return;
|
|
|
|
await shortcutsBtn.click();
|
|
await waitForAnimation(300);
|
|
|
|
// Click backdrop (click dialog element itself, not content)
|
|
const dialog = page.locator('dialog[open]');
|
|
if (await dialog.count() > 0) {
|
|
await dialog.click({ position: { x: 5, y: 5 } });
|
|
await waitForAnimation(300);
|
|
|
|
const dialogClosed = await page.locator('dialog[open]').count() === 0;
|
|
expect(dialogClosed).toBe(true);
|
|
console.log('✅ Modal closes on backdrop click');
|
|
}
|
|
});
|
|
|
|
test('should show keyboard shortcuts content', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/?lang=en`);
|
|
|
|
const shortcutsBtn = page.locator('button[data-action="show-shortcuts"]').or(
|
|
page.locator('button:has-text("shortcuts")').first()
|
|
);
|
|
|
|
if (await shortcutsBtn.count() === 0) return;
|
|
|
|
await shortcutsBtn.click();
|
|
await waitForAnimation(300);
|
|
|
|
// Check for keyboard shortcut content (look for kbd tags or shortcut listings)
|
|
const kbdElements = await page.locator('kbd').count();
|
|
const hasShortcutContent = kbdElements > 0;
|
|
|
|
console.log(`Keyboard shortcut elements found: ${kbdElements}`);
|
|
expect(hasShortcutContent).toBe(true);
|
|
console.log('✅ Modal displays keyboard shortcuts');
|
|
});
|
|
|
|
test('should support bilingual content (EN/ES)', async ({ page }) => {
|
|
// Test English
|
|
await page.goto(`${BASE_URL}/?lang=en`);
|
|
const shortcutsBtn = page.locator('button[data-action="show-shortcuts"]').or(
|
|
page.locator('button:has-text("shortcuts")').first()
|
|
);
|
|
|
|
if (await shortcutsBtn.count() === 0) return;
|
|
|
|
await shortcutsBtn.click();
|
|
await waitForAnimation(300);
|
|
const enContent = await page.locator('dialog, [role="dialog"]').textContent();
|
|
await page.keyboard.press('Escape');
|
|
|
|
// Test Spanish
|
|
await page.goto(`${BASE_URL}/?lang=es`);
|
|
const shortcutsBtnEs = page.locator('button[data-action="show-shortcuts"]').or(
|
|
page.locator('button:has-text("atajos")').first()
|
|
);
|
|
|
|
if (await shortcutsBtnEs.count() > 0) {
|
|
await shortcutsBtnEs.click();
|
|
await waitForAnimation(300);
|
|
const esContent = await page.locator('dialog, [role="dialog"]').textContent();
|
|
|
|
const isDifferent = enContent !== esContent;
|
|
console.log(`Content differs between EN/ES: ${isDifferent}`);
|
|
expect(isDifferent).toBe(true);
|
|
console.log('✅ Modal supports bilingual content');
|
|
}
|
|
});
|
|
});
|
|
|
|
test.describe('FEATURE 002: Skeleton Loader for Language Transitions', () => {
|
|
test('should show skeleton loader during language switch', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/?lang=en`);
|
|
|
|
// Find language toggle button
|
|
const langButton = page.locator('button[data-lang="es"]').or(
|
|
page.locator('button:has-text("ES")').first()
|
|
).or(
|
|
page.locator('[hx-get*="lang=es"]').first()
|
|
);
|
|
|
|
const btnExists = await langButton.count() > 0;
|
|
console.log(`Language button exists: ${btnExists}`);
|
|
|
|
if (!btnExists) {
|
|
console.log('⚠️ Language button NOT FOUND');
|
|
return;
|
|
}
|
|
|
|
// Monitor for skeleton loader
|
|
let skeletonAppeared = false;
|
|
|
|
// Set up observer before clicking
|
|
await page.evaluate(() => {
|
|
window.skeletonDetected = false;
|
|
const observer = new MutationObserver(() => {
|
|
const skeleton = document.querySelector('.skeleton, [data-skeleton], .skeleton-loader, .shimmer');
|
|
if (skeleton && window.getComputedStyle(skeleton).opacity !== '0') {
|
|
window.skeletonDetected = true;
|
|
}
|
|
});
|
|
observer.observe(document.body, { childList: true, subtree: true, attributes: true });
|
|
});
|
|
|
|
// Click language button
|
|
await langButton.click();
|
|
await waitForAnimation(100);
|
|
|
|
// Check if skeleton appeared
|
|
skeletonAppeared = await page.evaluate(() => window.skeletonDetected);
|
|
|
|
await waitForAnimation(600);
|
|
await page.screenshot({ path: 'test-results/02-skeleton-loader.png', fullPage: true });
|
|
|
|
console.log(`Skeleton loader appeared: ${skeletonAppeared}`);
|
|
expect(skeletonAppeared).toBe(true);
|
|
console.log('✅ Skeleton loader appears during language transition');
|
|
});
|
|
|
|
test('should complete transition within 500-700ms', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/?lang=en`);
|
|
|
|
const langButton = page.locator('button[data-lang="es"]').or(
|
|
page.locator('[hx-get*="lang=es"]').first()
|
|
);
|
|
|
|
if (await langButton.count() === 0) return;
|
|
|
|
const startTime = Date.now();
|
|
await langButton.click();
|
|
|
|
// Wait for HTMX to complete (htmx:afterSwap event)
|
|
await page.waitForFunction(() => {
|
|
return !document.body.classList.contains('htmx-swapping') &&
|
|
!document.querySelector('.htmx-swapping');
|
|
}, { timeout: 2000 });
|
|
|
|
const endTime = Date.now();
|
|
const duration = endTime - startTime;
|
|
|
|
console.log(`Transition duration: ${duration}ms`);
|
|
expect(duration).toBeGreaterThanOrEqual(400);
|
|
expect(duration).toBeLessThanOrEqual(1000);
|
|
console.log('✅ Transition completes within acceptable time range');
|
|
});
|
|
|
|
test('should handle rapid language switching without breaking', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/?lang=en`);
|
|
|
|
const enButton = page.locator('button[data-lang="en"]').or(
|
|
page.locator('[hx-get*="lang=en"]').first()
|
|
);
|
|
const esButton = page.locator('button[data-lang="es"]').or(
|
|
page.locator('[hx-get*="lang=es"]').first()
|
|
);
|
|
|
|
if (await enButton.count() === 0 || await esButton.count() === 0) return;
|
|
|
|
// Rapid clicking
|
|
await esButton.click();
|
|
await waitForAnimation(100);
|
|
await enButton.click();
|
|
await waitForAnimation(100);
|
|
await esButton.click();
|
|
await waitForAnimation(800);
|
|
|
|
// Check no errors in console
|
|
const errors = [];
|
|
page.on('console', msg => {
|
|
if (msg.type() === 'error') errors.push(msg.text());
|
|
});
|
|
|
|
await page.screenshot({ path: 'test-results/02-rapid-switch.png', fullPage: true });
|
|
|
|
console.log(`Console errors during rapid switching: ${errors.length}`);
|
|
expect(errors.length).toBe(0);
|
|
console.log('✅ Handles rapid language switching without errors');
|
|
});
|
|
});
|
|
|
|
test.describe('FEATURE 003: HTMX Loading Indicators', () => {
|
|
test('should show loading indicator on language button click', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/?lang=en`);
|
|
|
|
const langButton = page.locator('button[data-lang="es"]').or(
|
|
page.locator('[hx-get*="lang=es"]').first()
|
|
);
|
|
|
|
if (await langButton.count() === 0) return;
|
|
|
|
// Look for loading indicator
|
|
let indicatorAppeared = false;
|
|
|
|
await page.evaluate(() => {
|
|
window.indicatorDetected = false;
|
|
const observer = new MutationObserver(() => {
|
|
const indicator = document.querySelector('.htmx-indicator, .loading-indicator, .spinner, [data-loading]');
|
|
if (indicator && window.getComputedStyle(indicator).opacity !== '0') {
|
|
window.indicatorDetected = true;
|
|
}
|
|
});
|
|
observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['class'] });
|
|
});
|
|
|
|
await langButton.click();
|
|
await waitForAnimation(50);
|
|
|
|
indicatorAppeared = await page.evaluate(() => window.indicatorDetected);
|
|
|
|
await waitForAnimation(600);
|
|
|
|
console.log(`Loading indicator appeared: ${indicatorAppeared}`);
|
|
expect(indicatorAppeared).toBe(true);
|
|
console.log('✅ Loading indicator appears on language button click');
|
|
});
|
|
|
|
test('should show loading indicators on toggle controls', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/?lang=en`);
|
|
|
|
const toggles = page.locator('input[type="checkbox"][hx-get], input[type="checkbox"][hx-post]');
|
|
const toggleCount = await toggles.count();
|
|
|
|
console.log(`Toggle controls found: ${toggleCount}`);
|
|
|
|
if (toggleCount === 0) {
|
|
console.log('⚠️ No toggle controls found');
|
|
return;
|
|
}
|
|
|
|
// Test first toggle
|
|
const firstToggle = toggles.first();
|
|
|
|
await page.evaluate(() => {
|
|
window.toggleIndicatorDetected = false;
|
|
const observer = new MutationObserver(() => {
|
|
const indicator = document.querySelector('.htmx-indicator, .loading-indicator, .spinner');
|
|
if (indicator && window.getComputedStyle(indicator).opacity !== '0') {
|
|
window.toggleIndicatorDetected = true;
|
|
}
|
|
});
|
|
observer.observe(document.body, { childList: true, subtree: true, attributes: true });
|
|
});
|
|
|
|
await firstToggle.click();
|
|
await waitForAnimation(50);
|
|
|
|
const indicatorAppeared = await page.evaluate(() => window.toggleIndicatorDetected);
|
|
|
|
await waitForAnimation(500);
|
|
await page.screenshot({ path: 'test-results/03-toggle-indicator.png', fullPage: true });
|
|
|
|
console.log(`Toggle loading indicator appeared: ${indicatorAppeared}`);
|
|
console.log('✅ Loading indicators work on toggle controls');
|
|
});
|
|
|
|
test('should hide indicators after request completes', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/?lang=en`);
|
|
|
|
const langButton = page.locator('button[data-lang="es"]').or(
|
|
page.locator('[hx-get*="lang=es"]').first()
|
|
);
|
|
|
|
if (await langButton.count() === 0) return;
|
|
|
|
await langButton.click();
|
|
await waitForAnimation(800);
|
|
|
|
// Check that all indicators are hidden
|
|
const visibleIndicators = await page.locator('.htmx-indicator:visible, .loading-indicator:visible, .spinner:visible').count();
|
|
|
|
console.log(`Visible indicators after completion: ${visibleIndicators}`);
|
|
expect(visibleIndicators).toBe(0);
|
|
console.log('✅ Indicators hide after request completion');
|
|
});
|
|
});
|
|
|
|
test.describe('FEATURE 004: Theme Switcher', () => {
|
|
test('should detect theme switcher button', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/?lang=en`);
|
|
|
|
const themeButton = page.locator('button[data-theme], button[data-action="toggle-theme"], button:has-text("theme")').first();
|
|
const exists = await themeButton.count() > 0;
|
|
|
|
console.log(`Theme switcher button exists: ${exists}`);
|
|
|
|
if (!exists) {
|
|
console.log('⚠️ Theme switcher NOT IMPLEMENTED');
|
|
return;
|
|
}
|
|
|
|
await page.screenshot({ path: 'test-results/04-theme-button.png', fullPage: true });
|
|
expect(exists).toBe(true);
|
|
});
|
|
|
|
test('should expand to show theme options', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/?lang=en`);
|
|
|
|
const themeButton = page.locator('button[data-theme], button[data-action="toggle-theme"]').first();
|
|
|
|
if (await themeButton.count() === 0) {
|
|
console.log('⚠️ Theme switcher NOT FOUND');
|
|
return;
|
|
}
|
|
|
|
await themeButton.click();
|
|
await waitForAnimation(300);
|
|
|
|
// Look for theme options (Light, Dark, Auto)
|
|
const lightOption = await page.locator('button:has-text("Light"), [data-theme="light"]').count();
|
|
const darkOption = await page.locator('button:has-text("Dark"), [data-theme="dark"]').count();
|
|
const autoOption = await page.locator('button:has-text("Auto"), [data-theme="auto"]').count();
|
|
|
|
console.log(`Light option: ${lightOption}, Dark option: ${darkOption}, Auto option: ${autoOption}`);
|
|
|
|
await page.screenshot({ path: 'test-results/04-theme-options.png', fullPage: true });
|
|
|
|
const hasOptions = lightOption > 0 || darkOption > 0 || autoOption > 0;
|
|
expect(hasOptions).toBe(true);
|
|
console.log('✅ Theme switcher shows options');
|
|
});
|
|
|
|
test('should persist theme selection in localStorage', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/?lang=en`);
|
|
|
|
const themeButton = page.locator('button[data-theme], button[data-action="toggle-theme"]').first();
|
|
|
|
if (await themeButton.count() === 0) return;
|
|
|
|
await themeButton.click();
|
|
await waitForAnimation(300);
|
|
|
|
const darkOption = page.locator('button:has-text("Dark"), [data-theme="dark"]').first();
|
|
|
|
if (await darkOption.count() > 0) {
|
|
await darkOption.click();
|
|
await waitForAnimation(300);
|
|
|
|
// Check localStorage
|
|
const storedTheme = await page.evaluate(() => localStorage.getItem('theme'));
|
|
console.log(`Stored theme: ${storedTheme}`);
|
|
|
|
// Reload and verify persistence
|
|
await page.reload();
|
|
await waitForAnimation(300);
|
|
|
|
const themeAfterReload = await page.evaluate(() => localStorage.getItem('theme'));
|
|
expect(themeAfterReload).toBe(storedTheme);
|
|
console.log('✅ Theme selection persists in localStorage');
|
|
}
|
|
});
|
|
});
|
|
|
|
test.describe('FEATURE 005: PDF Download Modal', () => {
|
|
test('should detect PDF modal trigger button', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/?lang=en`);
|
|
|
|
const pdfButton = page.locator('button:has-text("PDF"), button:has-text("download"), [data-action="show-pdf"]').first();
|
|
const exists = await pdfButton.count() > 0;
|
|
|
|
console.log(`PDF modal button exists: ${exists}`);
|
|
|
|
if (!exists) {
|
|
console.log('⚠️ PDF MODAL NOT IMPLEMENTED');
|
|
return;
|
|
}
|
|
|
|
await page.screenshot({ path: 'test-results/05-pdf-button.png', fullPage: true });
|
|
expect(exists).toBe(true);
|
|
});
|
|
|
|
test('should show three thumbnail cards', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/?lang=en`);
|
|
|
|
const pdfButton = page.locator('button:has-text("PDF"), button:has-text("download")').first();
|
|
|
|
if (await pdfButton.count() === 0) return;
|
|
|
|
await pdfButton.click();
|
|
await waitForAnimation(300);
|
|
|
|
// Look for thumbnail cards
|
|
const thumbnails = await page.locator('.thumbnail, .pdf-card, [data-pdf-type]').count();
|
|
console.log(`Thumbnail cards found: ${thumbnails}`);
|
|
|
|
await page.screenshot({ path: 'test-results/05-pdf-modal-open.png', fullPage: true });
|
|
|
|
expect(thumbnails).toBeGreaterThanOrEqual(2);
|
|
console.log('✅ PDF modal shows thumbnail cards');
|
|
});
|
|
|
|
test('should enable download button after selection', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/?lang=en`);
|
|
|
|
const pdfButton = page.locator('button:has-text("PDF"), button:has-text("download")').first();
|
|
|
|
if (await pdfButton.count() === 0) return;
|
|
|
|
await pdfButton.click();
|
|
await waitForAnimation(300);
|
|
|
|
// Find download button (should be disabled initially)
|
|
const downloadBtn = page.locator('button:has-text("Download"), button[data-action="download"]').first();
|
|
|
|
if (await downloadBtn.count() > 0) {
|
|
const initiallyDisabled = await downloadBtn.isDisabled();
|
|
console.log(`Download button initially disabled: ${initiallyDisabled}`);
|
|
|
|
// Click first thumbnail
|
|
const thumbnail = page.locator('.thumbnail, .pdf-card, [data-pdf-type]').first();
|
|
if (await thumbnail.count() > 0) {
|
|
await thumbnail.click();
|
|
await waitForAnimation(200);
|
|
|
|
const enabledAfterSelection = !(await downloadBtn.isDisabled());
|
|
console.log(`Download button enabled after selection: ${enabledAfterSelection}`);
|
|
|
|
expect(enabledAfterSelection).toBe(true);
|
|
console.log('✅ Download button enables after selection');
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
test.describe('INTEGRATION TESTS: Cross-Feature Interactions', () => {
|
|
test('should handle language switch while modal is open', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/?lang=en`);
|
|
|
|
// Open shortcuts modal if exists
|
|
const shortcutsBtn = page.locator('button[data-action="show-shortcuts"]').first();
|
|
|
|
if (await shortcutsBtn.count() > 0) {
|
|
await shortcutsBtn.click();
|
|
await waitForAnimation(300);
|
|
|
|
// Switch language
|
|
const langButton = page.locator('button[data-lang="es"]').first();
|
|
if (await langButton.count() > 0) {
|
|
await langButton.click();
|
|
await waitForAnimation(800);
|
|
|
|
await page.screenshot({ path: 'test-results/int-modal-lang-switch.png', fullPage: true });
|
|
|
|
console.log('✅ Language switch works with modal open');
|
|
}
|
|
}
|
|
});
|
|
|
|
test('should handle multiple rapid feature interactions', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/?lang=en`);
|
|
|
|
const errors = [];
|
|
page.on('console', msg => {
|
|
if (msg.type() === 'error') errors.push(msg.text());
|
|
});
|
|
|
|
// Rapid interactions
|
|
const langButton = page.locator('button[data-lang="es"]').first();
|
|
const toggle = page.locator('input[type="checkbox"]').first();
|
|
|
|
if (await langButton.count() > 0) await langButton.click();
|
|
await waitForAnimation(100);
|
|
if (await toggle.count() > 0) await toggle.click();
|
|
await waitForAnimation(100);
|
|
if (await langButton.count() > 0) await langButton.click();
|
|
await waitForAnimation(800);
|
|
|
|
console.log(`Errors during rapid interactions: ${errors.length}`);
|
|
expect(errors.length).toBe(0);
|
|
console.log('✅ Handles rapid feature interactions without errors');
|
|
});
|
|
});
|
|
|
|
test.describe('PERFORMANCE & ACCESSIBILITY', () => {
|
|
test('should have no console errors on page load', async ({ page }) => {
|
|
const errors = [];
|
|
page.on('console', msg => {
|
|
if (msg.type() === 'error') errors.push(msg.text());
|
|
});
|
|
|
|
await page.goto(`${BASE_URL}/?lang=en`);
|
|
await waitForAnimation(1000);
|
|
|
|
console.log('Console errors on load:', errors);
|
|
expect(errors.length).toBe(0);
|
|
console.log('✅ No console errors on page load');
|
|
});
|
|
|
|
test('should measure Core Web Vitals', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/?lang=en`);
|
|
await waitForAnimation(1000);
|
|
|
|
const metrics = await page.evaluate(() => {
|
|
const paint = performance.getEntriesByType('paint');
|
|
const navigation = performance.getEntriesByType('navigation')[0];
|
|
|
|
return {
|
|
fcp: paint.find(p => p.name === 'first-contentful-paint')?.startTime,
|
|
domContentLoaded: navigation?.domContentLoadedEventEnd - navigation?.domContentLoadedEventStart,
|
|
loadComplete: navigation?.loadEventEnd - navigation?.loadEventStart
|
|
};
|
|
});
|
|
|
|
console.log('Performance metrics:', metrics);
|
|
expect(metrics.fcp).toBeLessThan(3000);
|
|
console.log('✅ Performance metrics within acceptable range');
|
|
});
|
|
});
|