import { expect, test } from '@playwright/test'; import { readFile } from 'node:fs/promises'; test.use({ javaScriptEnabled: true, reducedMotion: 'reduce' }); async function probeAsset(name: string) { return readFile(new URL(`../../test-assets/wip-wasm/${name}`, import.meta.url)); } test('real WASM enforces manifest byte, node and depth bounds', async ({ page }, testInfo) => { const base = testInfo.project.use.baseURL!; await page.goto(new URL('worldspace.json', base).href); const result = await page.evaluate(async (base) => { const module = await import(new URL('wip-wasm/wip_browser.js', base).href); await module.default(); const root = { path: '/', name: '', displayPath: '/', title: 'Root', href: '/', html: '
Root
' }; const attempt = (json: string) => { try { new module.BrowserSession(json).free(); return { error: '', trap: false }; } catch (error) { return { error: String(error), trap: error instanceof WebAssembly.RuntimeError }; } }; // Both oversized manifests are otherwise valid; the controls prove that // removing a limit cannot leave this test green via unrelated validation. const byteLimit = 16 * 1024 * 1024; const empty = JSON.stringify({ nodes: [{ ...root, html: '' }] }); const atBytes = JSON.stringify({ nodes: [{ ...root, html: 'x'.repeat(byteLimit - empty.length) }] }); const bytesAccepted = attempt(atBytes); const bytes = attempt(atBytes.replace('"html":"', '"html":"x')); const wide = [root, ...Array.from({ length: 4096 }, (_, i) => ({ path: `/n${i}`, name: `n${i}`, displayPath: `/n${i}`, parent: '/', title: `Node ${i}`, href: `/n${i}/`, html: '', }))]; const nodesAccepted = attempt(JSON.stringify({ nodes: wide.slice(0, 4096) })); const nodes = attempt(JSON.stringify({ nodes: wide })); const deep: RecordNode
' }); parent = path; } const accepted = new module.BrowserSession(JSON.stringify({ nodes: deep })); const deepest = JSON.parse(accepted.read(parent)).path; accepted.free(); deep.push({ path: `${parent}/a`, name: 'a', displayPath: `${parent}/a`, parent, title: 'Too deep', href: `${parent}/a/`, html: '' }); const depth = attempt(JSON.stringify({ nodes: deep })); return { bytes, nodes, depth, deepest, bytesAccepted, nodesAccepted, probePublished: 'panic_probe' in module.BrowserSession.prototype }; }, base); expect(result.bytesAccepted).toEqual({ error: '', trap: false }); expect(result.nodesAccepted).toEqual({ error: '', trap: false }); expect(result.bytes).toEqual({ error: 'document JSON exceeds the input byte limit', trap: false }); expect(result.nodes).toEqual({ error: 'document node count must be between 1 and MAX_NODES', trap: false }); expect(result.depth).toEqual({ error: 'document hierarchy exceeds the depth limit', trap: false }); expect(result.deepest.split('/').length - 1).toBe(32); expect(result.probePublished).toBe(false); }); test('a controlled release Rust panic traps, not a Protocol failure; a fresh instance recovers', async ({ page }, testInfo) => { const base = testInfo.project.use.baseURL!; await page.route('**/wip-probe/wip_browser*', async (route) => { const wasm = new URL(route.request().url()).pathname.endsWith('.wasm'); await route.fulfill({ body: await probeAsset(wasm ? 'wip_browser_bg.wasm' : 'wip_browser.js'), contentType: wasm ? 'application/wasm' : 'text/javascript' }); }); await page.goto(new URL('worldspace.json', base).href); const result = await page.evaluate(async (base) => { const manifest = await (await fetch(new URL('worldspace.json', base))).text(); const module = await import(new URL('wip-probe/wip_browser.js', base).href); await module.default(); const session = new module.BrowserSession(manifest); const before = JSON.parse(session.read('/')).path; let trap = false, detail = ''; try { session.panic_probe(); } catch (error) { trap = error instanceof WebAssembly.RuntimeError; detail = String(error); } // Retain the aborted wrapper too: its generated GC finalizer enters WASM. (window as unknown as Record