diff --git a/apps/web/e2e/a11y.spec.ts b/apps/web/e2e/a11y.spec.ts index 3e3223d..e51f718 100644 --- a/apps/web/e2e/a11y.spec.ts +++ b/apps/web/e2e/a11y.spec.ts @@ -141,20 +141,36 @@ async function expectNoHorizontalScroll(page: Page, label: string): Promise 0 && rect.right > limit + 1) { - past.push(`${describe(el)} right=${Math.round(rect.right)} w=${Math.round(rect.width)}`); + /** Content inside a scroll container may exceed the viewport — that is + * the remedy. But only when the CONTAINER fits: a scroller that is + * itself too wide still pushes the page. */ + const insideFittingScroller = (el: Element): boolean => { + for (let node = el.parentElement; node && node !== doc; node = node.parentElement) { + const ox = getComputedStyle(node).overflowX; + if (ox === 'auto' || ox === 'scroll' || ox === 'hidden') { + return node.getBoundingClientRect().right <= limit + 1; + } } - } + return false; + }; + + // Widest reach first, so a long tail of clipped children cannot bury the + // one box that actually pushes the page. + const past = Array.from(document.querySelectorAll('body *')) + .map((el) => ({ el, rect: el.getBoundingClientRect() })) + .filter(({ rect }) => rect.width > 0 && rect.right > limit + 1) + .sort((a, b) => b.rect.right - a.rect.right) + .map( + ({ el, rect }) => + `${describe(el)} right=${Math.round(rect.right)} w=${Math.round(rect.width)}` + + `${insideFittingScroller(el) ? ' [in fitting scroller]' : ' <-- pushes page'}`, + ); return { overflowBy: doc.scrollWidth - limit, viewport: `html client=${limit} scroll=${doc.scrollWidth} | body client=${document.body.clientWidth} scroll=${document.body.scrollWidth} rect=${Math.round(document.body.getBoundingClientRect().width)}`, overflowing: overflowing.slice(0, 15), - past: past.slice(0, 15), + past: past.slice(0, 40), }; }); const diagnosis = [