import { expect, test } from '../helpers/serve-dist'; interface DocumentSnapshot { text: string; sectionIds: string[]; sectionHeadings: string[]; h1: string; role: string; landmarks: { headers: number; mains: number; navigations: number; footers: number; }; } const captureDocument = async ( page: import('@playwright/test').Page, ): Promise => page.evaluate(() => ({ text: (document.querySelector('.portfolio-shell')?.textContent ?? '') .replace(/\s+/gu, ' ') .trim(), sectionIds: [ ...document.querySelectorAll('main > section'), ].map((section) => section.id), sectionHeadings: [ ...document.querySelectorAll('main > section > h2'), ].map((heading) => heading.innerText.trim()), h1: document.querySelector('h1')?.textContent?.trim() ?? '', role: document.querySelector('.site-header > p')?.textContent?.trim() ?? '', landmarks: { headers: document.querySelectorAll('header').length, mains: document.querySelectorAll('main').length, navigations: document.querySelectorAll('nav').length, footers: document.querySelectorAll('footer').length, }, })); test('hydration preserves the built subject matter and landmark structure without errors or external fetches', async ({ browser, page, distUrl, }, testInfo) => { test.skip( testInfo.project.name === 'mobile-chromium', 'Hydration identity is covered once; semantic and no-script behavior run on mobile.', ); const beforePage = await browser.newPage({ javaScriptEnabled: false, viewport: { width: 1440, height: 900 }, }); await beforePage.goto(distUrl, { waitUntil: 'load' }); const before = await captureDocument(beforePage); await beforePage.close(); const pageErrors: string[] = []; const consoleErrors: string[] = []; const failedRequests: string[] = []; const requests: string[] = []; page.on('pageerror', (error) => pageErrors.push(error.message)); page.on('console', (message) => { if (message.type() === 'error') consoleErrors.push(message.text()); }); page.on('request', (request) => requests.push(request.url())); page.on('requestfailed', (request) => failedRequests.push( `${request.url()}: ${request.failure()?.errorText ?? 'failed'}`, ), ); await page.addInitScript(() => { const unhandled: string[] = []; Reflect.set(globalThis, '__portfolioUnhandledRejections', unhandled); addEventListener('unhandledrejection', (event) => unhandled.push(String(event.reason)), ); }); await page.goto(distUrl, { waitUntil: 'domcontentloaded' }); await page.waitForFunction(() => { const app = document.querySelector('#app'); return app !== null && Reflect.has(app, '__vue_app__'); }); await page.waitForFunction(() => document.readyState === 'complete'); await page.evaluate( () => new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve())), ), ); const after = await captureDocument(page); const unhandledRejections = await page.evaluate( () => (Reflect.get(globalThis, '__portfolioUnhandledRejections') as string[] | undefined) ?? [], ); expect(after).toEqual(before); expect(after.h1).toBe('Luca Pîslaru'); expect(after.role).toBe('System Administrator & Full-Stack Developer'); expect(after.sectionIds).toEqual([ 'start', 'profile', 'experience', 'infrastructure', 'selected-builds', 'additional-work', 'toolkit', 'contact', ]); expect(after.landmarks).toEqual({ headers: 1, mains: 1, navigations: 1, footers: 1, }); expect(pageErrors).toEqual([]); expect(consoleErrors).toEqual([]); expect(unhandledRejections).toEqual([]); expect(failedRequests).toEqual([]); expect(requests.length).toBeGreaterThan(1); expect( requests.every((requestUrl) => { const url = new URL(requestUrl); return ( url.origin === distUrl && (url.pathname === '/' || url.pathname.startsWith('/assets/')) ); }), ).toBe(true); });