import type { Page } from '@playwright/test'; export type Axis = 'y' | 'x'; const raf = () => new Promise((r) => requestAnimationFrame(() => r())); async function pos(page: Page, sel: string, axis: Axis) { return page.evaluate( ([s, a]) => { const el = document.querySelector(s as string) as HTMLElement | null; if (!el) return null; return a === 'y' ? { p: el.scrollTop, size: el.clientHeight, total: el.scrollHeight } : { p: el.scrollLeft, size: el.clientWidth, total: el.scrollWidth }; }, [sel, axis] as const, ); } /** * Прокрутка колесом мыши шагами stepPx: вниз (вправо) до конца и обратно. * После каждого шага ждём requestAnimationFrame. * complete: в конце прямого прохода scrollTop + clientHeight >= scrollHeight - 2 * (для x - scrollLeft/clientWidth/scrollWidth). maxSteps ограничивает прямой проход * (при его исчерпании обратный проход не выполняется, позиция сбрасывается без замера). * Если по оси нет переполнения (scrollHeight <= clientHeight), возвращается complete=false, steps=0, reason='no_overflow'. * steps - число шагов колеса (вниз + обратно). */ export async function scrollThrough( page: Page, containerSelector: string, axis: Axis, stepPx = 200, maxSteps = 5000, ): Promise<{ complete: boolean; steps: number; reason?: 'no_overflow' }> { const box = await page.locator(containerSelector).first().boundingBox(); if (!box) throw new Error(`scroll container not found: ${containerSelector}`); await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2); const wheel = (d: number) => (axis === 'y' ? page.mouse.wheel(0, d) : page.mouse.wheel(d, 0)); const tick = () => page.evaluate(raf); const first = await pos(page, containerSelector, axis); // нет переполнения по оси - скроллить нечего, замер пустой: не засчитываем как complete if (first && first.total <= first.size) return { complete: false, steps: 0, reason: 'no_overflow' }; let steps = 0; let complete = false; while (steps < maxSteps) { const s = await pos(page, containerSelector, axis); if (!s) break; if (s.p + s.size >= s.total - 2) { complete = true; break; } await wheel(stepPx); await tick(); steps++; } // финальная проверка (последний шаг мог дойти до конца) if (!complete) { const s = await pos(page, containerSelector, axis); complete = !!s && s.p + s.size >= s.total - 2; } if (!complete) { await page.evaluate(([s, a]) => { const el = document.querySelector(s as string) as HTMLElement; if (a === 'y') el.scrollTop = 0; else el.scrollLeft = 0; }, [containerSelector, axis] as const); return { complete, steps }; } // обратно while (steps < 2 * maxSteps) { const s = await pos(page, containerSelector, axis); if (!s || s.p <= 2) break; await wheel(-stepPx); await tick(); steps++; } return { complete, steps }; }