// The site (site/index.html) never scrolls sideways: at every viewport width from 320 to 1440 px (10 px steps), in the // light and the dark theme, document.documentElement.scrollWidth stays within innerWidth. On a failure it names the // elements whose right edge passes the viewport outside any scroll container (pseudo-elements show as their owner). // Uses the bundled playwright-core with its Chromium (or Google Chrome); skips when neither is installed (a launch that // times out on a busy machine fails instead). One launch, the context closed right after each test, capped closes // (tests/helpers/browser.ts). SLOW on purpose: 113 widths x 2 themes, each a resize and a layout read. import { afterAll, afterEach, describe, expect, test } from 'bun:test'; import { join } from 'node:path'; import { AFTER_ALL_MS, AFTER_EACH_MS, launchTestBrowser, testScope } from '../helpers/browser'; const page = 'file://' + join(import.meta.dir, '..', '..', 'site', 'index.html'); const shared = await launchTestBrowser('playwright-core'); const { browser, skipWhy } = shared; if (!browser) console.warn(`site overflow test skipped: no Chromium installed (${skipWhy}). Run \`bunx playwright install chromium\`.`); const scope = testScope(() => browser); // bounded: one context per test, each close capped (bun's 5 s default hook timeout was too tight for Chrome's slow shutdown) afterEach(() => scope.settle(), AFTER_EACH_MS); afterAll(() => shared.close(), AFTER_ALL_MS); describe.skipIf(!browser)('site: no horizontal overflow', () => { for (const scheme of ['light', 'dark'] as const) { test(`320-1440 px, ${scheme}`, async () => { const p = await scope.newPage({ colorScheme: scheme, viewport: { width: 1440, height: 900 } }); await p.goto(page, { waitUntil: 'load' }); const bad: string[] = []; for (let w = 320; w <= 1440; w += 10) { await p.setViewportSize({ width: w, height: 900 }); const r = await p.evaluate(() => { const iw = innerWidth, sw = document.documentElement.scrollWidth; if (sw <= iw) return null; const els: string[] = []; for (const el of document.querySelectorAll('body *')) { const rc = el.getBoundingClientRect(); if (rc.width === 0 || rc.right <= iw + 0.5) continue; let clipped = false; for (let a = el.parentElement; a && a !== document.body; a = a.parentElement) { if (getComputedStyle(a).overflowX !== 'visible') { clipped = true; break; } } if (!clipped) els.push(`${el.tagName.toLowerCase()}${el.id ? '#' + el.id : ''}${[...el.classList].map(c => '.' + c).join('')} right=${Math.round(rc.right)}`); } return `${iw}px: page ${sw - iw}px too wide${els.length ? ' (' + els.slice(0, 5).join(', ') + ')' : ' (a pseudo-element or transform)'}`; }); if (r) bad.push(r); } expect(bad).toEqual([]); }, 120_000); } });