import path from 'node:path'; import { CONTEXT_TIMEOUT_MS, PAGE_TIMEOUT_MS, cleanupBrowserTmp, openBrowser, safeCloseBrowser, safeCloseContext, safeClosePage, setupBrowserTmp, shouldRetryNavigation, withTimeout, } from './lib/browser.mjs'; import { discoverRoutes, requireDist, startDistServer } from './lib/dist.mjs'; const browserTmp = path.resolve('.astro', 'playwright-overflow-tmp'); const MAX_NAV_RETRIES = 4; // Common device widths: iPhone SE / Galaxy S / iPhone 14 / iPad portrait / // iPad landscape / common laptop / full HD desktop. const VIEWPORT_WIDTHS = [320, 390, 430, 768, 1024, 1440, 1920]; const MEASURE_TIMEOUT_MS = 25000; await requireDist(); await setupBrowserTmp(browserTmp); const routes = await discoverRoutes(); const { server, port } = await startDistServer(); const failures = []; async function newMeasurementContext(browser, width) { const context = await browser.newContext({ viewport: { width, height: 900 }, javaScriptEnabled: true, }); await context.route('**/*', (route) => { const type = route.request().resourceType(); if (type === 'media') { route.abort('blockedbyclient'); } else { route.continue(); } }); return context; } async function openMeasurementContext(browser, width) { return withTimeout( newMeasurementContext(browser, width), CONTEXT_TIMEOUT_MS, `Timed out while creating ${width}px Playwright context` ); } async function measureViewport(page) { await page.waitForLoadState('load', { timeout: 5000 }).catch(() => {}); return page.evaluate(() => ({ scrollWidth: document.documentElement.scrollWidth, clientWidth: document.documentElement.clientWidth, })); } async function measureRoute(context, route) { let page; try { page = await withTimeout( context.newPage(), PAGE_TIMEOUT_MS, `Timed out while creating page for ${route}` ); return await withTimeout( (async () => { await page.goto(`http://127.0.0.1:${port}${route}`, { waitUntil: 'domcontentloaded', timeout: 15000, }); return measureViewport(page); })(), MEASURE_TIMEOUT_MS, `Timed out while measuring ${route}` ); } finally { if (page) await safeClosePage(page); } } try { for (const width of VIEWPORT_WIDTHS) { let browser; let context; try { browser = await openBrowser(browserTmp); context = await openMeasurementContext(browser, width); for (const route of routes) { let result; for (let attempt = 0; attempt < MAX_NAV_RETRIES; attempt += 1) { try { result = await measureRoute(context, route); break; } catch (error) { if (!shouldRetryNavigation(error) || attempt === MAX_NAV_RETRIES - 1) { throw error; } await safeCloseContext(context); await safeCloseBrowser(browser); browser = await openBrowser(browserTmp); context = await openMeasurementContext(browser, width); } } if (result.scrollWidth > result.clientWidth + 1) { failures.push( `${route} overflows at ${width}px: ${result.scrollWidth}px > ${result.clientWidth}px` ); } } } finally { if (context) await safeCloseContext(context); if (browser) await safeCloseBrowser(browser); } } } finally { server.close(); await cleanupBrowserTmp(browserTmp); } if (failures.length > 0) { console.error(failures.join('\n')); process.exit(1); } console.log( `No horizontal overflow detected at ${VIEWPORT_WIDTHS.join(', ')}px across ${routes.length} routes.` );