Tested all four variants in browser — only active layer body visible while tabs stay staggered; scroll covers previous layers correctly. Co-authored-by: Cursor <cursoragent@cursor.com>
92 lines
3.6 KiB
JavaScript
92 lines
3.6 KiB
JavaScript
import { chromium } from 'playwright';
|
|
import { writeFileSync, mkdirSync } from 'fs';
|
|
import { join } from 'path';
|
|
|
|
const BASE = process.env.BASE_URL || 'http://localhost:5176';
|
|
const OUT = '/tmp/levkin-stack-test';
|
|
mkdirSync(OUT, { recursive: true });
|
|
|
|
const pages = [
|
|
{ name: 'stack', path: '/stack/' },
|
|
{ name: 'stack-folder', path: '/stack-folder/' },
|
|
{ name: 'stack-trace', path: '/stack-trace/' },
|
|
{ name: 'stack-rack', path: '/stack-rack/' },
|
|
];
|
|
|
|
const scrollY = [0, 400, 800, 1200, 1800, 2400];
|
|
|
|
async function analyze(page, label) {
|
|
return page.evaluate(() => {
|
|
const sections = [...document.querySelectorAll('.scroll-section')];
|
|
const bodies = [...document.querySelectorAll('.body, .layer-inner, .frame-body, .unit-body')];
|
|
const tabs = [...document.querySelectorAll('.tab, .frame-line, .unit-head')];
|
|
const sticky = [...document.querySelectorAll('.tab, .body, .layer-inner, .frame-line, .frame-body, .unit-head, .unit-body')];
|
|
const rects = (els) => els.map((el, i) => {
|
|
const r = el.getBoundingClientRect();
|
|
const cs = getComputedStyle(el);
|
|
return {
|
|
i,
|
|
tag: el.className?.slice?.(0, 40) || el.tagName,
|
|
top: Math.round(r.top),
|
|
bottom: Math.round(r.bottom),
|
|
height: Math.round(r.height),
|
|
position: cs.position,
|
|
zIndex: cs.zIndex,
|
|
visible: r.height > 0 && r.bottom > 0 && r.top < innerHeight,
|
|
};
|
|
});
|
|
const visibleBodies = rects(bodies).filter((b) => b.visible && b.height > 80);
|
|
const mount = document.querySelector('.mount, .stack-mount');
|
|
const mountH = mount ? Math.round(mount.getBoundingClientRect().height) : 0;
|
|
return {
|
|
scrollY: Math.round(window.scrollY),
|
|
pageH: Math.round(document.documentElement.scrollHeight),
|
|
viewport: innerHeight,
|
|
sections: sections.length,
|
|
visibleBodyCount: visibleBodies.length,
|
|
visibleBodies,
|
|
mountDocHeight: mountH,
|
|
firstSectionTop: sections[0] ? Math.round(sections[0].getBoundingClientRect().top) : null,
|
|
lastSectionBottom: sections.at(-1) ? Math.round(sections.at(-1).getBoundingClientRect().bottom) : null,
|
|
stickyPositions: rects(sticky.filter((el) => getComputedStyle(el).position === 'sticky')).slice(0, 14),
|
|
};
|
|
});
|
|
}
|
|
|
|
const browser = await chromium.launch({ headless: true });
|
|
const report = [];
|
|
|
|
for (const { name, path } of pages) {
|
|
const page = await browser.newPage({ viewport: { width: 1280, height: 800 } });
|
|
const url = BASE + path;
|
|
await page.goto(url, { waitUntil: 'networkidle' });
|
|
const shots = [];
|
|
for (const y of scrollY) {
|
|
await page.evaluate((sy) => window.scrollTo(0, sy), y);
|
|
await page.waitForTimeout(200);
|
|
const data = await analyze(page, name);
|
|
const file = join(OUT, `${name}-scroll-${y}.png`);
|
|
await page.screenshot({ path: file, fullPage: false });
|
|
shots.push({ y, file, ...data });
|
|
}
|
|
report.push({ name, url, shots });
|
|
await page.close();
|
|
}
|
|
|
|
await browser.close();
|
|
|
|
const summary = report.map((r) => {
|
|
const problems = r.shots.map((s) => {
|
|
const issues = [];
|
|
if (s.visibleBodyCount > 2) issues.push(`${s.visibleBodyCount} bodies visible (want ≤2)`);
|
|
if (s.scrollY === 0 && s.visibleBodyCount > 1) issues.push('top: multiple full bodies');
|
|
if (s.pageH > s.viewport * 8) issues.push(`page very tall: ${s.pageH}px`);
|
|
return { y: s.y, issues, visibleBodyCount: s.visibleBodyCount, pageH: s.pageH };
|
|
});
|
|
return { variant: r.name, url: r.url, scrollChecks: problems };
|
|
});
|
|
|
|
writeFileSync(join(OUT, 'report.json'), JSON.stringify({ report, summary }, null, 2));
|
|
console.log(JSON.stringify(summary, null, 2));
|
|
console.log('\nScreenshots:', OUT);
|