107 lines
4.6 KiB
JavaScript
107 lines
4.6 KiB
JavaScript
// The topology "disappears" for the user but passed my tests. Two suspects in
|
|||
|
|
// index.html:
|
||
|
|
// 1. The Topology tab button calls `showView('topo'); loadTopo()`, and
|
||
|
|
// showView ITSELF calls loadTopo() when v==='topo' -> two concurrent calls.
|
||
|
|
// 2. ensureTopoGraph() assigns `topoGraph = new TopoGraph(host)` BEFORE awaiting
|
||
|
|
// init(), but the guard is `if (topoGraph && topoGraph.app)` — and app is
|
||
|
|
// null until init() finishes. A second concurrent call therefore builds a
|
||
|
|
// SECOND renderer. Both run host.innerHTML = '' and append their own canvas,
|
||
|
|
// so whichever init() finishes LAST wins the DOM while `topoGraph` still
|
||
|
|
// points at the other one -> a canvas that is running but detached from the
|
||
|
|
// page, i.e. nothing visible.
|
||
|
|
// Hammer the tab exactly like a user clicking it, and check how many canvases
|
||
|
|
// exist and which one the bridge holds.
|
||
|
|
const { chromium } = require('/tmp/gltest/node_modules/playwright-core');
|
||
|
|
|
||
|
|
const BASE = process.env.PANEL_URL || 'https://panel.attackdefense.imrnes.team';
|
||
|
|
|
||
|
|
(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 errs = [];
|
||
|
|
page.on('pageerror', e => errs.push('PAGEERROR: ' + e.message));
|
||
|
|
page.on('console', m => { if (m.type() === 'error') errs.push('CONSOLE: ' + m.text()); });
|
||
|
|
|
||
|
|
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(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');
|
||
|
|
|
||
|
|
// Count how many times the module actually initialises a renderer.
|
||
|
|
await page.evaluate(() => {
|
||
|
|
window.__inits = 0;
|
||
|
|
const host = document.getElementById('topoHost');
|
||
|
|
const mo = new MutationObserver(() => { /* canvas churn visible below */ });
|
||
|
|
mo.observe(host, { childList: true });
|
||
|
|
window.__mo = mo;
|
||
|
|
});
|
||
|
|
|
||
|
|
console.log('=== single tab click (what my tests did) ===');
|
||
|
|
await page.click('.tab[data-view=topo]');
|
||
|
|
await page.waitForTimeout(2500);
|
||
|
|
let s = await page.evaluate(() => {
|
||
|
|
const host = document.getElementById('topoHost');
|
||
|
|
const g = window.__topoProbe ? window.__topoProbe() : null;
|
||
|
|
return {
|
||
|
|
canvases: host.querySelectorAll('canvas').length,
|
||
|
|
hostChildren: host.children.length,
|
||
|
|
bridgeHasApp: !!(g && g.app),
|
||
|
|
bridgeCanvasAttached: !!(g && g.app && g.app.canvas && g.app.canvas.isConnected),
|
||
|
|
nodes: g ? g.nodeViews.size : -1,
|
||
|
|
};
|
||
|
|
});
|
||
|
|
console.log(' ', JSON.stringify(s));
|
||
|
|
|
||
|
|
console.log('\n=== rapid re-entry (leave tab, come back, x3) ===');
|
||
|
|
for (let i = 0; i < 3; i++) {
|
||
|
|
await page.click('.tab[data-view=challs]');
|
||
|
|
await page.waitForTimeout(120);
|
||
|
|
await page.click('.tab[data-view=topo]');
|
||
|
|
await page.waitForTimeout(900);
|
||
|
|
const t = await page.evaluate(() => {
|
||
|
|
const host = document.getElementById('topoHost');
|
||
|
|
const g = window.__topoProbe ? window.__topoProbe() : null;
|
||
|
|
return {
|
||
|
|
canvases: host.querySelectorAll('canvas').length,
|
||
|
|
hostChildren: host.children.length,
|
||
|
|
bridgeCanvasAttached: !!(g && g.app && g.app.canvas && g.app.canvas.isConnected),
|
||
|
|
nodes: g ? g.nodeViews.size : -1,
|
||
|
|
};
|
||
|
|
});
|
||
|
|
console.log(` round ${i + 1}:`, JSON.stringify(t));
|
||
|
|
}
|
||
|
|
|
||
|
|
console.log('\n=== final pixel check on the VISIBLE canvas ===');
|
||
|
|
const r = await page.evaluate(() => {
|
||
|
|
const host = document.getElementById('topoHost');
|
||
|
|
const cv = host.querySelector('canvas');
|
||
|
|
if (!cv) return { error: 'no canvas in host at all' };
|
||
|
|
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;
|
||
|
|
for (let i = 0; i < buf.length; i += 4)
|
||
|
|
if (Math.max(Math.abs(buf[i]-10), Math.abs(buf[i+1]-15), Math.abs(buf[i+2]-28)) > 3) nonBg++;
|
||
|
|
return {
|
||
|
|
bridgeCanvasIsTheVisibleOne: cv === g.app.canvas,
|
||
|
|
pct: +(100 * nonBg / (W * H)).toFixed(2),
|
||
|
|
visibleCanvasW: cv.width, bridgeCanvasW: rd.width,
|
||
|
|
};
|
||
|
|
});
|
||
|
|
console.log(' ', JSON.stringify(r));
|
||
|
|
if (errs.length) { console.log('\n errors:'); errs.slice(0, 6).forEach(e => console.log(' ' + e)); }
|
||
|
|
await browser.close();
|
||
|
|
})();
|