import { spawn } from 'node:child_process' import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises' import os from 'node:os' import path from 'node:path' import { setTimeout as delay } from 'node:timers/promises' const CHROME = process.env.CHROME_BIN || '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome' const URL = process.env.URL || 'http://localhost:8080/modern/app/#/parties' const OUT = process.env.OUT || '/Users/qiu/Desktop/ERP/ofbiz-framework/plugins/modern-ui/verification/scroll-check.png' const profile = await mkdtemp(path.join(os.tmpdir(), 'scrollchk.')) const chrome = spawn(CHROME, ['--headless=new', '--disable-gpu', '--no-first-run', '--remote-debugging-port=0', '--window-size=1280,900', `--user-data-dir=${profile}`, 'about:blank'], { stdio: ['ignore', 'ignore', 'pipe'] }) let port = '' const deadline = Date.now() + 15000 while (Date.now() < deadline && !port) { try { const t = await readFile(path.join(profile, 'DevToolsActivePort'), 'utf8'); port = t.trim().split('\n')[0] } catch { await delay(150) } } const targets = await fetch(`http://127.0.0.1:${port}/json/list`).then((r) => r.json()) const ws = new WebSocket(targets.find((t) => t.type === 'page' && t.webSocketDebuggerUrl).webSocketDebuggerUrl) await new Promise((res) => ws.addEventListener('open', res, { once: true })) let id = 0 const pending = new Map() ws.addEventListener('message', (e) => { const m = JSON.parse(e.data); if (m.id && pending.has(m.id)) { pending.get(m.id)(m.result); pending.delete(m.id) } }) const cmd = (method, params = {}) => new Promise((res) => { const i = ++id; pending.set(i, res); ws.send(JSON.stringify({ id: i, method, params })) }) const evalJs = async (expr) => (await cmd('Runtime.evaluate', { expression: expr, returnByValue: true, awaitPromise: true })).result?.value await cmd('Page.enable'); await cmd('Network.enable') // Log in inside the page (same-origin fetch sets the session cookie), then load the target. await cmd('Page.navigate', { url: 'http://localhost:8080/modern/app/' }) await delay(1500) await evalJs(`fetch('/api/v1/login',{method:'POST',credentials:'include',headers:{'Content-Type':'application/json'},body:JSON.stringify({username:'admin',password:'ADMIN'})}).then(r=>r.status)`) await delay(800) await evalJs("window.location.hash = '#/parties'; true") await cmd('Page.reload', { ignoreCache: true }) // wait for shell + table const wd = Date.now() + 20000 while (Date.now() < wd) { const ok = await evalJs("Boolean(document.querySelector('.erp-shell__main') && document.querySelector('.erp-shell__aside') && document.body.innerText.includes('客户与组织管理台'))").catch(() => false) if (ok) break await delay(300) } await delay(1200) const before = await evalJs(`(() => { const aside = document.querySelector('.erp-shell__aside'); const main = document.querySelector('.erp-shell__main'); return { docScrollable: document.documentElement.scrollHeight > window.innerHeight + 2, mainScrollable: main.scrollHeight > main.clientHeight + 2, asideTop: Math.round(aside.getBoundingClientRect().top), asideBottom: Math.round(aside.getBoundingClientRect().bottom), viewportH: window.innerHeight, mainScrollTop: main.scrollTop }; })()`) // scroll the main area down await evalJs("document.querySelector('.erp-shell__main').scrollTop = 100000; true") await delay(500) const after = await evalJs(`(() => { const aside = document.querySelector('.erp-shell__aside'); const main = document.querySelector('.erp-shell__main'); return { docScrollTop: document.documentElement.scrollTop || document.body.scrollTop, mainScrollTop: Math.round(main.scrollTop), asideTop: Math.round(aside.getBoundingClientRect().top), asideBottom: Math.round(aside.getBoundingClientRect().bottom), brandVisible: Boolean(document.querySelector('.erp-brand')) }; })()`) const shot = await cmd('Page.captureScreenshot', { format: 'png' }) await writeFile(OUT, Buffer.from(shot.data, 'base64')) console.log(JSON.stringify({ before, after, screenshot: OUT }, null, 2)) // verdict const pass = !before.docScrollable && before.mainScrollable && after.mainScrollTop > 50 && after.asideTop === 0 && (after.docScrollTop || 0) === 0 && after.asideBottom >= before.viewportH - 2 console.log('SCROLL-FIX:', pass ? 'PASS (sidebar fixed, only main scrolls)' : 'CHECK') ws.close(); chrome.kill('SIGTERM'); await delay(300); await rm(profile, { recursive: true, force: true }) process.exit(0)