97 lines
4.8 KiB
JavaScript
97 lines
4.8 KiB
JavaScript
// 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);
|
||
|
|
})();
|