// Real-browser test of the PixiJS topology: boot the panel, log in, open the // Topology tab, and assert a live WebGL canvas actually rendered the graph. // A JS-parse check cannot catch "init() rejected" or "canvas sized to 0". const { chromium } = require('/tmp/gltest/node_modules/playwright-core'); (async () => { const browser = await chromium.launch({ args: ['--no-sandbox', '--use-gl=swiftshader', '--enable-unsafe-swiftshader'], }); const page = await browser.newPage({ viewport: { width: 1400, height: 900 } }); const errors = [], logs = []; page.on('pageerror', e => errors.push('pageerror: ' + e.message)); page.on('console', m => { if (m.type() === 'error') errors.push('console: ' + m.text()); else logs.push(m.text()); }); page.on('requestfailed', r => errors.push(`requestfailed: ${r.url()} ${r.failure()?.errorText}`)); 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; }, {}); await page.goto('https://panel.attackdefense.imrnes.team/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', { timeout: 20000 }); // Open the topology tab. await page.click('.tab[data-view=topo]'); await page.waitForSelector('#topoHost canvas', { timeout: 30000 }); await page.waitForTimeout(4000); const res = await page.evaluate(async () => { // The inline script's top-level `let topoGraph` is not a window property, // so read it through the page's deliberate debug hook. const g = window.__topoProbe ? window.__topoProbe() : null; const host = document.getElementById('topoHost'); const cv = host && host.querySelector('canvas'); return { hasCanvas: !!cv, canvasW: cv ? cv.width : 0, canvasH: cv ? cv.height : 0, cssW: cv ? cv.getBoundingClientRect().width : 0, cssH: cv ? cv.getBoundingClientRect().height : 0, pixiLoaded: !!(g && g.PIXI), nodeViews: g ? g.nodeViews.size : 0, attackViews: g ? g.attackViews.size : 0, edgeChildren: g && g.edgeLayer ? g.edgeLayer.children.length : 0, nodeChildren: g && g.nodeLayer ? g.nodeLayer.children.length : 0, scale: g ? g.scale : null, engine: document.getElementById('topoEngine')?.textContent || '', // Are pixels actually drawn, or an empty background? zoomLabel: document.getElementById('topoZoomLabel')?.textContent, }; }); console.log('--- topology render state ---'); for (const [k, v] of Object.entries(res)) console.log(` ${k}: ${v}`); // Zoom controls must reach the Pixi world transform. await page.click('#view-topo button:has-text("Zoom In")'); await page.waitForTimeout(400); const zoomed = await page.evaluate(() => ({ scale: window.__topoProbe().scale, label: document.getElementById('topoZoomLabel').textContent })); console.log(' after zoom-in ->', JSON.stringify(zoomed)); await page.click('#view-topo button:has-text("\u27f3")'); await page.waitForTimeout(300); // Reset now means "frame the whole graph in the viewport", not "scale=1": with // 37 nodes the graph is wider than the canvas, so the fit scale is < 1. Assert // the real contract — that reset returns to the framed scale and the view // transform is centred — rather than a magic 1 that only held for small graphs. const before = await page.evaluate(() => window.__topoProbe().scale); await page.click('#view-topo button:has-text("\u27f3")'); await page.waitForTimeout(400); const after = await page.evaluate(() => ({ scale: window.__topoProbe().scale, panX: window.__topoProbe().panX, label: document.getElementById('topoZoomLabel').textContent, })); console.log(' after zoom -> scale', before.toFixed(3)); console.log(' after reset -> scale', after.scale.toFixed(3), 'label', after.label); if (Math.abs(after.scale - before) > 0.001) { console.log(` FAIL: reset did not restore the framed scale (${after.scale} vs ${before})`); process.exitCode = 1; } else { console.log(' PASS: reset restored the framed scale'); } await page.screenshot({ path: '/tmp/topo_pixi.png' }); console.log(' screenshot -> /tmp/topo_pixi.png'); if (errors.length) { console.log('\n--- ERRORS ---'); errors.slice(0, 12).forEach(e => console.log(' ' + e)); } else console.log('\n no page errors'); const ok = res.hasCanvas && res.canvasW > 0 && res.canvasH > 0 && res.nodeViews > 0 && res.nodeChildren > 0; console.log(`\n ${ok ? 'PASS' : 'FAIL'} — canvas ${res.canvasW}x${res.canvasH}, ${res.nodeViews} nodes, ${res.edgeChildren} edge children`); await browser.close(); process.exit(ok && errors.length === 0 ? 0 : 1); })();