import { test, expect } from '@playwright/test'; // All 10 pages to test const ALL_PAGES = [ { name: 'Home', url: '/', titleFragment: 'Transforming Ideas' }, { name: 'About', url: '/about', titleFragment: 'About Us' }, { name: 'Services', url: '/services', titleFragment: 'Our Services' }, { name: 'Portfolio', url: '/portfolio', titleFragment: 'Our Portfolio' }, { name: 'Blog', url: '/blog', titleFragment: 'Blog' }, { name: 'Contact', url: '/contact', titleFragment: 'Contact Us' }, { name: 'Privacy', url: '/privacy', titleFragment: 'Privacy Policy' }, { name: 'Terms', url: '/terms', titleFragment: 'Terms of Service' }, { name: 'Sitemap', url: '/sitemap', titleFragment: 'Sitemap' }, { name: '404', url: '/nonexistent-page-xyz', titleFragment: '' }, ]; // Viewport sizes for responsive testing const VIEWPORTS = [ { name: 'Mobile', width: 375, height: 667 }, { name: 'Tablet', width: 768, height: 1024 }, { name: 'Desktop', width: 1280, height: 800 }, { name: 'Wide', width: 1920, height: 1080 }, ]; // ============================================================ // 1. Cross-Browser Page Load Testing (runs on all configured browsers) // ============================================================ test.describe('Cross-Browser: All Pages Load', () => { for (const p of ALL_PAGES) { test(`${p.name} page loads successfully`, async ({ page }) => { const consoleErrors: string[] = []; page.on('console', (msg) => { if (msg.type() === 'error') consoleErrors.push(msg.text()); }); page.on('pageerror', (err) => consoleErrors.push(err.message)); const response = await page.goto(p.url, { waitUntil: 'domcontentloaded' }); if (p.url === '/nonexistent-page-xyz') { expect([404, 200]).toContain(response?.status()); // Some frameworks serve 200 with error page } else { expect(response?.status()).toBe(200); } // Verify no critical JS errors (filter out known non-critical browser noise) const critical = consoleErrors.filter( (e) => !e.includes('favicon') && !e.includes('livereload') && !e.includes('DevTools') && !e.includes('manifest') && !e.includes('net::ERR_ABORTED') // image lazy-load aborts on fast navigation ); if (critical.length > 0) { console.warn(`[${p.name}] Console errors:`, critical); } expect(critical, `Unexpected console errors on ${p.name}`).toHaveLength(0); }); } }); // ============================================================ // 2. Cross-Browser: Critical Layout Elements Present // ============================================================ test.describe('Cross-Browser: Layout Elements', () => { const CONTENT_PAGES = ALL_PAGES.filter((p) => p.url !== '/nonexistent-page-xyz'); for (const p of CONTENT_PAGES) { test(`${p.name} has header, main content, and footer`, async ({ page }) => { await page.goto(p.url, { waitUntil: 'domcontentloaded' }); // Fixed header is always present await expect(page.locator('#main-header')).toBeAttached(); // Footer is always present await expect(page.locator('footer')).toBeAttached(); // Page has at least one
or
with content const mainContent = page.locator('main, section').first(); await expect(mainContent).toBeAttached(); // No invisible page (must have visible body text) const bodyText = await page.locator('body').innerText(); expect(bodyText.trim().length).toBeGreaterThan(50); }); } }); // ============================================================ // 3. Responsive Design Testing // ============================================================ test.describe('Responsive Design', () => { test.describe('Home page responsive layout', () => { for (const vp of VIEWPORTS) { test(`renders correctly at ${vp.name} (${vp.width}x${vp.height})`, async ({ page }) => { await page.setViewportSize({ width: vp.width, height: vp.height }); await page.goto('/', { waitUntil: 'domcontentloaded' }); // Header is always visible await expect(page.locator('#main-header')).toBeVisible(); // Hero section (first section) is visible await expect(page.locator('section').first()).toBeVisible(); // Footer visible await expect(page.locator('footer')).toBeVisible(); // On mobile/tablet: desktop nav is hidden, hamburger is visible if (vp.width < 1024) { await expect(page.locator('#mobile-menu-toggle')).toBeVisible(); await expect(page.locator('ul[role="menubar"]')).toBeHidden(); } else { // On desktop: hamburger is hidden, nav is visible await expect(page.locator('#mobile-menu-toggle')).toBeHidden(); await expect(page.locator('ul[role="menubar"]')).toBeVisible(); } // Screenshot for visual reference await page.screenshot({ path: `tests/screenshots/${vp.name.toLowerCase()}-home.png`, fullPage: false, }); }); } }); test.describe('Services page responsive layout', () => { for (const vp of [VIEWPORTS[0], VIEWPORTS[2]]) { // Mobile + Desktop test(`renders at ${vp.name}`, async ({ page }) => { await page.setViewportSize({ width: vp.width, height: vp.height }); await page.goto('/services', { waitUntil: 'domcontentloaded' }); await expect(page.locator('#main-header')).toBeVisible(); await expect(page.locator('h1')).toBeVisible(); const content = await page.locator('body').innerText(); expect(content).toContain('Services'); }); } }); test.describe('Portfolio page responsive layout', () => { for (const vp of [VIEWPORTS[0], VIEWPORTS[1], VIEWPORTS[2]]) { test(`renders at ${vp.name}`, async ({ page }) => { await page.setViewportSize({ width: vp.width, height: vp.height }); await page.goto('/portfolio', { waitUntil: 'domcontentloaded' }); // Filter tabs and project grid must be visible await expect(page.locator('[role="tablist"]')).toBeVisible(); await expect(page.locator('#projects-grid')).toBeVisible(); // At least one project card must be visible const cards = page.locator('.project-card'); await expect(cards.first()).toBeVisible(); }); } }); test.describe('Contact page responsive layout', () => { for (const vp of [VIEWPORTS[0], VIEWPORTS[2]]) { test(`form is usable at ${vp.name}`, async ({ page }) => { await page.setViewportSize({ width: vp.width, height: vp.height }); await page.goto('/contact', { waitUntil: 'domcontentloaded' }); await expect(page.locator('#contact-form')).toBeVisible(); await expect(page.locator('#name')).toBeVisible(); await expect(page.locator('#email')).toBeVisible(); await expect(page.locator('#message')).toBeVisible(); // Submit button is visible and clickable const submitBtn = page.locator('#contact-form button[type="submit"]'); await expect(submitBtn).toBeVisible(); await expect(submitBtn).toBeEnabled(); }); } }); test.describe('Blog page responsive layout', () => { for (const vp of [VIEWPORTS[0], VIEWPORTS[2]]) { test(`renders at ${vp.name}`, async ({ page }) => { await page.setViewportSize({ width: vp.width, height: vp.height }); await page.goto('/blog', { waitUntil: 'domcontentloaded' }); await expect(page.locator('h1')).toBeVisible(); await expect(page.locator('footer')).toBeVisible(); }); } }); }); // ============================================================ // 4. Navigation Testing // ============================================================ test.describe('Navigation', () => { test('Desktop nav links navigate to correct pages', async ({ page }) => { await page.setViewportSize({ width: 1280, height: 800 }); await page.goto('/', { waitUntil: 'domcontentloaded' }); const navItems = [ { label: 'About', path: '/about' }, { label: 'Services', path: '/services' }, { label: 'Portfolio', path: '/portfolio' }, { label: 'Blog', path: '/blog' }, { label: 'Contact', path: '/contact' }, ]; for (const item of navItems) { // Navigate back to home first await page.goto('/', { waitUntil: 'domcontentloaded' }); // Click the nav link in the desktop menubar const navLink = page.locator(`ul[role="menubar"] a[href="${item.path}"]`); await expect(navLink).toBeVisible(); await navLink.click(); await page.waitForURL(`**${item.path}**`, { timeout: 5000 }); expect(page.url()).toContain(item.path); } }); test('Logo navigates to home page', async ({ page }) => { await page.goto('/about', { waitUntil: 'domcontentloaded' }); const logo = page.locator('#main-header a[aria-label*="WorkRoot"]').first(); await expect(logo).toBeVisible(); await logo.click(); await page.waitForURL('**/', { timeout: 5000 }); expect(page.url()).toMatch(/\/$|\/$/); }); test('"Get Started" CTA in header navigates to contact', async ({ page }) => { await page.setViewportSize({ width: 1280, height: 800 }); await page.goto('/', { waitUntil: 'domcontentloaded' }); const ctaBtn = page.locator('#main-header a[href="/contact"]').first(); await expect(ctaBtn).toBeVisible(); await ctaBtn.click(); await page.waitForURL('**/contact', { timeout: 5000 }); expect(page.url()).toContain('/contact'); }); test('Footer privacy link navigates correctly', async ({ page }) => { await page.goto('/', { waitUntil: 'domcontentloaded' }); await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); const privacyLink = page.locator('footer a[href="/privacy"]').first(); await expect(privacyLink).toBeVisible(); await privacyLink.click(); await page.waitForURL('**/privacy', { timeout: 5000 }); expect(page.url()).toContain('/privacy'); }); test('Footer terms link navigates correctly', async ({ page }) => { await page.goto('/', { waitUntil: 'domcontentloaded' }); await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); const termsLink = page.locator('footer a[href="/terms"]').first(); await expect(termsLink).toBeVisible(); await termsLink.click(); await page.waitForURL('**/terms', { timeout: 5000 }); expect(page.url()).toContain('/terms'); }); }); // ============================================================ // 5. Mobile Navigation (hamburger menu) // ============================================================ test.describe('Mobile Navigation Menu', () => { test.beforeEach(async ({ page }) => { await page.setViewportSize({ width: 375, height: 667 }); await page.goto('/', { waitUntil: 'domcontentloaded' }); }); test('Hamburger button is visible on mobile', async ({ page }) => { await expect(page.locator('#mobile-menu-toggle')).toBeVisible(); }); test('Desktop nav is hidden on mobile', async ({ page }) => { await expect(page.locator('ul[role="menubar"]')).toBeHidden(); }); test('Mobile menu panel opens when hamburger is clicked', async ({ page }) => { const toggle = page.locator('#mobile-menu-toggle'); const menu = page.locator('#mobile-menu'); // Menu starts closed (translated off-screen) await expect(menu).toHaveClass(/translate-x-full/); await toggle.click(); await page.waitForTimeout(400); // CSS transition // Menu slides in await expect(menu).not.toHaveClass(/translate-x-full/); await expect(toggle).toHaveAttribute('aria-expanded', 'true'); }); test('Mobile menu closes when close button is clicked', async ({ page }) => { await page.locator('#mobile-menu-toggle').click(); await page.waitForTimeout(400); const closeBtn = page.locator('#mobile-menu-close'); await expect(closeBtn).toBeVisible(); await closeBtn.click(); await page.waitForTimeout(400); const menu = page.locator('#mobile-menu'); await expect(menu).toHaveClass(/translate-x-full/); await expect(page.locator('#mobile-menu-toggle')).toHaveAttribute('aria-expanded', 'false'); }); test('Mobile menu closes when overlay is clicked', async ({ page }) => { await page.locator('#mobile-menu-toggle').click(); await page.waitForTimeout(400); const overlay = page.locator('#mobile-menu-overlay'); await overlay.click({ force: true }); await page.waitForTimeout(400); await expect(page.locator('#mobile-menu')).toHaveClass(/translate-x-full/); }); test('Mobile menu closes on Escape key', async ({ page }) => { await page.locator('#mobile-menu-toggle').click(); await page.waitForTimeout(400); await page.keyboard.press('Escape'); await page.waitForTimeout(400); await expect(page.locator('#mobile-menu')).toHaveClass(/translate-x-full/); await expect(page.locator('#mobile-menu-toggle')).toHaveAttribute('aria-expanded', 'false'); }); test('Mobile menu links navigate correctly', async ({ page }) => { await page.locator('#mobile-menu-toggle').click(); await page.waitForTimeout(400); // Click "Services" in mobile nav const servicesLink = page.locator('#mobile-menu a[href="/services"]'); await expect(servicesLink).toBeVisible(); await servicesLink.click(); await page.waitForURL('**/services', { timeout: 5000 }); expect(page.url()).toContain('/services'); }); test('Body scroll is locked while menu is open', async ({ page }) => { await page.locator('#mobile-menu-toggle').click(); await page.waitForTimeout(400); const overflow = await page.evaluate(() => document.body.style.overflow); expect(overflow).toBe('hidden'); // Scroll re-enabled on close await page.locator('#mobile-menu-close').click(); await page.waitForTimeout(400); const overflowAfter = await page.evaluate(() => document.body.style.overflow); expect(overflowAfter).toBe(''); }); test('Menu closes automatically on resize to desktop', async ({ page }) => { await page.locator('#mobile-menu-toggle').click(); await page.waitForTimeout(400); // Resize to desktop await page.setViewportSize({ width: 1280, height: 800 }); await page.waitForTimeout(400); await expect(page.locator('#mobile-menu')).toHaveClass(/translate-x-full/); }); }); // ============================================================ // 6. Contact Form Testing // ============================================================ test.describe('Contact Form', () => { test.beforeEach(async ({ page }) => { await page.goto('/contact', { waitUntil: 'domcontentloaded' }); }); test('Form elements are all present', async ({ page }) => { await expect(page.locator('#contact-form')).toBeVisible(); await expect(page.locator('#name')).toBeVisible(); await expect(page.locator('#email')).toBeVisible(); await expect(page.locator('#phone')).toBeVisible(); await expect(page.locator('#subject')).toBeVisible(); await expect(page.locator('#message')).toBeVisible(); // Honeypot field is present but visually hidden await expect(page.locator('#website')).toBeAttached(); await expect(page.locator('#website')).not.toBeVisible(); }); test('Subject dropdown has all expected options', async ({ page }) => { const options = await page.locator('#subject option').allTextContents(); expect(options).toContain('Web Development'); expect(options).toContain('Mobile Development'); expect(options).toContain('Cloud Services'); expect(options).toContain('AI & Machine Learning'); expect(options).toContain('IT Consulting'); expect(options).toContain('Technical Support'); expect(options).toContain('Other'); }); test('Error messages are hidden initially', async ({ page }) => { await expect(page.locator('[data-error="name"]')).toBeHidden(); await expect(page.locator('[data-error="email"]')).toBeHidden(); await expect(page.locator('[data-error="subject"]')).toBeHidden(); await expect(page.locator('[data-error="message"]')).toBeHidden(); }); test('Submit button is visible and enabled', async ({ page }) => { const btn = page.locator('#contact-form button[type="submit"]'); await expect(btn).toBeVisible(); await expect(btn).toBeEnabled(); await expect(btn).toContainText('Send Message'); }); test('Name field accepts valid input', async ({ page }) => { const nameInput = page.locator('#name'); await nameInput.fill('John Doe'); expect(await nameInput.inputValue()).toBe('John Doe'); }); test('Email field accepts valid email format', async ({ page }) => { const emailInput = page.locator('#email'); await emailInput.fill('john@example.com'); expect(await emailInput.inputValue()).toBe('john@example.com'); }); test('Phone field is optional and accepts phone number', async ({ page }) => { const phoneInput = page.locator('#phone'); await phoneInput.fill('+1 (555) 123-4567'); expect(await phoneInput.inputValue()).toBe('+1 (555) 123-4567'); }); test('Message textarea accepts multi-line text', async ({ page }) => { const messageInput = page.locator('#message'); const testMessage = 'This is a test message.\nWith multiple lines.\nFor our project.'; await messageInput.fill(testMessage); expect(await messageInput.inputValue()).toBe(testMessage); }); test('Full form can be filled out completely', async ({ page }) => { await page.fill('#name', 'Test User'); await page.fill('#email', 'test@example.com'); await page.fill('#phone', '+1234567890'); await page.selectOption('#subject', 'web-development'); await page.fill('#message', 'This is a complete test message for automated testing purposes.'); // All fields should have their values expect(await page.locator('#name').inputValue()).toBe('Test User'); expect(await page.locator('#email').inputValue()).toBe('test@example.com'); expect(await page.locator('#subject').inputValue()).toBe('web-development'); expect((await page.locator('#message').inputValue()).length).toBeGreaterThan(10); }); test('Honeypot field is not visible to users', async ({ page }) => { // Real users can't see or interact with the honeypot field const honeypot = page.locator('#website'); await expect(honeypot).toBeAttached(); // It should not be focusable via tab (tabindex="-1") const tabIndex = await honeypot.getAttribute('tabindex'); expect(tabIndex).toBe('-1'); }); test('Form is accessible on mobile viewport', async ({ page }) => { await page.setViewportSize({ width: 375, height: 667 }); await page.goto('/contact', { waitUntil: 'domcontentloaded' }); // All required fields are visible and tappable on mobile await expect(page.locator('#name')).toBeVisible(); await expect(page.locator('#email')).toBeVisible(); await expect(page.locator('#subject')).toBeVisible(); await expect(page.locator('#message')).toBeVisible(); await expect(page.locator('#contact-form button[type="submit"]')).toBeVisible(); }); }); // ============================================================ // 7. Portfolio Filter Functionality // ============================================================ test.describe('Portfolio Filters', () => { test.beforeEach(async ({ page }) => { await page.goto('/portfolio', { waitUntil: 'domcontentloaded' }); // Wait for project cards to be rendered await expect(page.locator('.project-card').first()).toBeVisible(); }); test('All filter buttons are present', async ({ page }) => { await expect(page.locator('button[data-filter="all"]')).toBeVisible(); await expect(page.locator('button[data-filter="web"]')).toBeVisible(); await expect(page.locator('button[data-filter="mobile"]')).toBeVisible(); await expect(page.locator('button[data-filter="ai"]')).toBeVisible(); }); test('"All Projects" filter is active by default', async ({ page }) => { const allBtn = page.locator('button[data-filter="all"]'); await expect(allBtn).toHaveClass(/active/); await expect(allBtn).toHaveAttribute('aria-selected', 'true'); // All 8 project cards should be visible const cards = page.locator('.project-card'); await expect(cards).toHaveCount(8); }); test('"Web Development" filter shows only web projects', async ({ page }) => { const webBtn = page.locator('button[data-filter="web"]'); await webBtn.click(); await page.waitForTimeout(500); // CSS animation await expect(webBtn).toHaveAttribute('aria-selected', 'true'); const visibleCards = page.locator('.project-card:not(.hidden)'); const count = await visibleCards.count(); expect(count).toBeGreaterThan(0); // All visible cards should be web category const categories = await visibleCards.evaluateAll((cards) => cards.map((c) => (c as HTMLElement).dataset.category) ); expect(categories.every((cat) => cat === 'web')).toBe(true); }); test('"Mobile Apps" filter shows only mobile projects', async ({ page }) => { await page.locator('button[data-filter="mobile"]').click(); await page.waitForTimeout(500); const visibleCards = page.locator('.project-card:not(.hidden)'); const count = await visibleCards.count(); expect(count).toBeGreaterThan(0); const categories = await visibleCards.evaluateAll((cards) => cards.map((c) => (c as HTMLElement).dataset.category) ); expect(categories.every((cat) => cat === 'mobile')).toBe(true); }); test('"AI & ML" filter shows only AI projects', async ({ page }) => { await page.locator('button[data-filter="ai"]').click(); await page.waitForTimeout(500); const visibleCards = page.locator('.project-card:not(.hidden)'); const count = await visibleCards.count(); expect(count).toBeGreaterThan(0); const categories = await visibleCards.evaluateAll((cards) => cards.map((c) => (c as HTMLElement).dataset.category) ); expect(categories.every((cat) => cat === 'ai')).toBe(true); }); test('Switching back to "All" restores all projects', async ({ page }) => { // Filter to web first await page.locator('button[data-filter="web"]').click(); await page.waitForTimeout(500); // Switch back to all await page.locator('button[data-filter="all"]').click(); await page.waitForTimeout(500); const visibleCards = page.locator('.project-card:not(.hidden)'); await expect(visibleCards).toHaveCount(8); }); test('Only one filter button is active at a time', async ({ page }) => { await page.locator('button[data-filter="web"]').click(); await page.waitForTimeout(300); const activeButtons = page.locator('button[data-filter][aria-selected="true"]'); await expect(activeButtons).toHaveCount(1); await expect(activeButtons.first()).toHaveAttribute('data-filter', 'web'); }); test('Case study modal opens when "View Case Study" is clicked', async ({ page }) => { const firstDetailsBtn = page.locator('.view-details-btn').first(); await firstDetailsBtn.click(); await page.waitForTimeout(400); const modal = page.locator('#case-study-modal'); await expect(modal).not.toHaveClass(/hidden/); await expect(modal).toBeVisible(); // Modal has a title await expect(page.locator('#modal-title')).toBeVisible(); }); test('Case study modal closes when close button is clicked', async ({ page }) => { await page.locator('.view-details-btn').first().click(); await page.waitForTimeout(400); await page.locator('#close-modal-btn').click(); await page.waitForTimeout(400); await expect(page.locator('#case-study-modal')).toHaveClass(/hidden/); }); test('Case study modal closes on Escape key', async ({ page }) => { await page.locator('.view-details-btn').first().click(); await page.waitForTimeout(400); await page.keyboard.press('Escape'); await page.waitForTimeout(400); await expect(page.locator('#case-study-modal')).toHaveClass(/hidden/); }); test('Portfolio filters work on mobile viewport', async ({ page }) => { await page.setViewportSize({ width: 375, height: 667 }); await page.goto('/portfolio', { waitUntil: 'domcontentloaded' }); await expect(page.locator('.project-card').first()).toBeVisible(); // Filter tabs should be visible (flex-wrap layout) await expect(page.locator('[role="tablist"]')).toBeVisible(); await page.locator('button[data-filter="web"]').click(); await page.waitForTimeout(500); const visibleCards = page.locator('.project-card:not(.hidden)'); const count = await visibleCards.count(); expect(count).toBeGreaterThan(0); }); }); // ============================================================ // 8. Blog Markdown Rendering // ============================================================ test.describe('Blog Rendering', () => { test('Blog listing page displays articles', async ({ page }) => { await page.goto('/blog', { waitUntil: 'domcontentloaded' }); await expect(page.locator('h1')).toBeVisible(); // Blog listing should have links to posts or a "no posts" message const postLinks = page.locator('a[href^="/blog/"]'); const articleCards = page.locator('article'); const totalContent = (await postLinks.count()) + (await articleCards.count()); // Either posts exist or page loads cleanly const bodyText = await page.locator('body').innerText(); expect(bodyText.trim().length).toBeGreaterThan(50); }); test('Navigating to a blog post renders markdown content', async ({ page }) => { await page.goto('/blog', { waitUntil: 'domcontentloaded' }); const firstPostLink = page.locator('a[href^="/blog/"]').first(); const hasPost = (await firstPostLink.count()) > 0; if (!hasPost) { test.skip(); // No blog posts in content collection - skip gracefully return; } await firstPostLink.click(); await page.waitForLoadState('domcontentloaded'); // Page must have an h1 title await expect(page.locator('h1')).toBeVisible(); // Page must have substantial content (markdown rendered to paragraphs) const paragraphs = page.locator('p'); const pCount = await paragraphs.count(); expect(pCount).toBeGreaterThan(0); // Should have prose content area const content = await page.locator('body').innerText(); expect(content.trim().length).toBeGreaterThan(100); }); test('Blog post page has proper heading hierarchy', async ({ page }) => { await page.goto('/blog', { waitUntil: 'domcontentloaded' }); const firstPostLink = page.locator('a[href^="/blog/"]').first(); if ((await firstPostLink.count()) === 0) { test.skip(); return; } await firstPostLink.click(); await page.waitForLoadState('domcontentloaded'); // Must have exactly one h1 const h1Count = await page.locator('h1').count(); expect(h1Count).toBeGreaterThanOrEqual(1); }); test('Blog post code blocks render with pre/code tags', async ({ page }) => { await page.goto('/blog', { waitUntil: 'domcontentloaded' }); const firstPostLink = page.locator('a[href^="/blog/"]').first(); if ((await firstPostLink.count()) === 0) { test.skip(); return; } await firstPostLink.click(); await page.waitForLoadState('domcontentloaded'); const htmlContent = await page.content(); // Code blocks use
 tags in markdown rendering - acceptable if absent (post may have no code)
    const hasPreTag = htmlContent.includes(' {
    await page.setViewportSize({ width: 375, height: 667 });
    await page.goto('/blog', { waitUntil: 'domcontentloaded' });

    await expect(page.locator('h1')).toBeVisible();
    await expect(page.locator('#main-header')).toBeVisible();
    await expect(page.locator('footer')).toBeVisible();
  });
});

// ============================================================
// 9. Console Error Monitoring (comprehensive - all pages, all known errors)
// ============================================================
test.describe('Console Error Monitoring', () => {
  test('No critical JavaScript errors across all pages', async ({ page }) => {
    const errorLog: { url: string; error: string }[] = [];

    page.on('console', (msg) => {
      if (msg.type() === 'error') {
        errorLog.push({ url: page.url(), error: msg.text() });
      }
    });

    page.on('pageerror', (err) => {
      errorLog.push({ url: page.url(), error: err.message });
    });

    const testPages = ALL_PAGES.filter((p) => p.url !== '/nonexistent-page-xyz');

    for (const p of testPages) {
      await page.goto(p.url, { waitUntil: 'domcontentloaded' });
      await page.waitForTimeout(500); // Allow async scripts to run
    }

    const critical = errorLog.filter(
      (e) =>
        !e.error.includes('favicon') &&
        !e.error.includes('livereload') &&
        !e.error.includes('DevTools') &&
        !e.error.includes('manifest') &&
        !e.error.includes('net::ERR_ABORTED')
    );

    if (critical.length > 0) {
      console.error('CRITICAL CONSOLE ERRORS FOUND:');
      critical.forEach((e) => console.error(`  [${e.url}] ${e.error}`));
    }

    expect(critical, `Found ${critical.length} critical console error(s)`).toHaveLength(0);
  });

  test('No failed network requests (4xx/5xx) for critical assets', async ({ page }) => {
    const failedRequests: { url: string; status: number }[] = [];

    page.on('response', (response) => {
      const status = response.status();
      const url = response.url();

      // Only track failures for page-relative assets (JS, CSS, fonts, images from same origin)
      if (
        status >= 400 &&
        (url.includes('.js') ||
          url.includes('.css') ||
          url.includes('.woff') ||
          url.includes('.woff2'))
      ) {
        failedRequests.push({ url, status });
      }
    });

    await page.goto('/', { waitUntil: 'networkidle' });

    if (failedRequests.length > 0) {
      console.warn('Failed asset requests:', failedRequests);
    }

    expect(failedRequests).toHaveLength(0);
  });
});

// ============================================================
// 10. Header Scroll Behavior
// ============================================================
test.describe('Header Scroll Behavior', () => {
  test('Header gets shadow class after scrolling down', async ({ page }) => {
    await page.goto('/', { waitUntil: 'domcontentloaded' });

    const header = page.locator('#main-header');
    await expect(header).not.toHaveClass(/header-scrolled/);

    // Scroll down
    await page.evaluate(() => window.scrollTo(0, 100));
    await page.waitForTimeout(200);

    await expect(header).toHaveClass(/header-scrolled/);
  });

  test('Header shadow is removed when scrolled back to top', async ({ page }) => {
    await page.goto('/', { waitUntil: 'domcontentloaded' });

    await page.evaluate(() => window.scrollTo(0, 200));
    await page.waitForTimeout(200);

    await page.evaluate(() => window.scrollTo(0, 0));
    await page.waitForTimeout(200);

    await expect(page.locator('#main-header')).not.toHaveClass(/header-scrolled/);
  });
});