// The off-screen-layout bug only showed at the default width. Prove the fix // holds across viewport sizes: the graph must stay framed at every size, and // zooming/panning must keep the whole graph reachable. const { chromium } = require('/tmp/gltest/node_modules/playwright-core'); const BASE = process.env.PANEL_URL || 'https://panel.attackdefense.imrnes.team'; const SIZES = [ { w: 1920, h: 1080, label: 'desktop-wide' }, { w: 1400, h: 900, label: 'laptop' }, { w: 1100, h: 800, label: 'narrow' }, { w: 820, h: 900, label: 'below-canvas-min' }, ]; (async () => { const browser = await chromium.launch({ args: ['--no-sandbox', '--use-gl=swiftshader', '--enable-unsafe-swiftshader'], }); const creds = require('fs').readFileSync('/opt/gemastik18-final/panel/.env', 'utf8') .split('\n').reduce((a, l) => { const m = l.match(/^PANEL_ADMIN_(USER|PASS)=(.*)$/); if (m) a[m[1]] = m[2]; return a; }, {}); let failures = 0; for (const s of SIZES) { const page = await browser.newPage({ viewport: { width: s.w, height: s.h } }); const errs = []; page.on('pageerror', e => errs.push(e.message)); await page.goto(BASE + '/login', { waitUntil: 'domcontentloaded' }); await page.fill('#u', creds.USER); await page.fill('#p', creds.PASS); await page.click('#f button[type=submit]'); await page.waitForSelector('#view-challs.active'); await page.click('.tab[data-view=topo]'); await page.waitForSelector('#topoHost canvas'); await page.waitForTimeout(2200); const r = await page.evaluate(() => { const g = window.__topoProbe(); const rd = g.app.renderer; rd.render(g.app.stage); const W = rd.width, H = rd.height; const buf = new Uint8Array(W * H * 4); rd.gl.readPixels(0, 0, W, H, rd.gl.RGBA, rd.gl.UNSIGNED_BYTE, buf); let nonBg = 0, minX = W, minY = H, maxX = -1, maxY = -1; for (let y = 0; y < H; y++) for (let x = 0; x < W; x++) { const i = (y * W + x) * 4; if (Math.max(Math.abs(buf[i]-10), Math.abs(buf[i+1]-15), Math.abs(buf[i+2]-28)) > 3) { nonBg++; if (x < minX) minX = x; if (x > maxX) maxX = x; if (y < minY) minY = y; if (y > maxY) maxY = y; } } // A viewport narrower than the CSS min-width scrolls; the visible part of // the canvas is what matters, so measure the canvas buffer, not the screen. return { W, H, pct: +(100 * nonBg / (W * H)).toFixed(2), bbox: maxX < 0 ? null : [minX, minY, maxX, maxY], inFrame: maxX < 0 ? false : (minX >= 0 && minY >= 0 && maxX <= W && maxY <= H), scale: +g.scale.toFixed(3), nodes: g.nodeViews.size, glErr: rd.gl.getError(), }; }); const ok = r.pct > 1 && r.inFrame && r.glErr === 0 && r.nodes > 0 && errs.length === 0; if (!ok) failures++; console.log(` ${ok ? 'PASS' : 'FAIL'} ${s.label.padEnd(20)} canvas ${r.W}x${r.H} drawn ${String(r.pct).padStart(5)}% scale ${r.scale} bbox ${JSON.stringify(r.bbox)}${errs.length ? ' ERR ' + errs[0] : ''}`); await page.close(); } await browser.close(); console.log(`\n ${failures === 0 ? 'PASS' : 'FAIL'} — ${SIZES.length - failures}/${SIZES.length} viewports framed correctly`); process.exit(failures === 0 ? 0 : 1); })();