schmelczer-dev/scripts/check-overflow.mjs
2026-06-11 21:35:45 +01:00

134 lines
3.6 KiB
JavaScript

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.`
);