// Bytecode on-demand loading tests // Verifies .sxbc modules are only downloaded when needed, not all at boot. const { test, expect } = require('playwright/test'); const { BASE_URL, waitForSxReady } = require('./helpers'); test.describe('Bytecode Loading', () => { test('manifest is fetched at boot', async ({ page }) => { const manifestReqs = []; page.on('request', req => { if (req.url().includes('module-manifest.json')) manifestReqs.push(req.url()); }); await page.goto(BASE_URL + '/sx/', { waitUntil: 'domcontentloaded' }); await waitForSxReady(page); expect(manifestReqs.length).toBe(1); }); test('boot loads only manifest deps, not all modules', async ({ page }) => { const sxbcReqs = []; page.on('request', req => { const url = req.url(); if (url.includes('.sxbc') && !url.includes('manifest')) { const name = url.split('/').pop().split('?')[0].replace('.sxbc', ''); sxbcReqs.push(name); } }); await page.goto(BASE_URL + '/sx/', { waitUntil: 'domcontentloaded' }); await waitForSxReady(page); // Boot loads manifest deps for hydration — NOT all 26 // 13 boot deps, 13 deferred (compiler, vm, harness, etc.) expect(sxbcReqs.length).toBeLessThanOrEqual(14); expect(sxbcReqs.length).toBeGreaterThan(5); // Boot-critical modules present expect(sxbcReqs).toContain('boot'); expect(sxbcReqs).toContain('dom'); // Dev/test modules NOT loaded on homepage const nonBootModules = ['compiler', 'vm', 'harness', 'harness-web', 'harness-reactive']; for (const mod of nonBootModules) { expect(sxbcReqs).not.toContain(mod); } }); test('no .sx fallback requests when bytecode exists', async ({ page }) => { const sxFallbackReqs = []; page.on('request', req => { const url = req.url(); // .sx source fallback (not .sxbc) in the wasm directory if (url.match(/\/static\/wasm\/sx\/[^/]+\.sx(\?|$)/) && !url.includes('.sxbc')) { sxFallbackReqs.push(url); } }); await page.goto(BASE_URL + '/sx/', { waitUntil: 'domcontentloaded' }); await waitForSxReady(page); expect(sxFallbackReqs).toEqual([]); }); test('all .sxbc requests use cache-busting hash', async ({ page }) => { const sxbcUrls = []; page.on('request', req => { if (req.url().includes('.sxbc')) sxbcUrls.push(req.url()); }); await page.goto(BASE_URL + '/sx/', { waitUntil: 'domcontentloaded' }); await waitForSxReady(page); // Every .sxbc request should have a ?v= cache-buster for (const url of sxbcUrls) { expect(url).toMatch(/[?&]v=/); } // All should share the same hash (combined hash from data-sxbc-hash) const hashes = sxbcUrls.map(u => new URL(u).searchParams.get('v')).filter(Boolean); const unique = [...new Set(hashes)]; expect(unique.length).toBe(1); }); test('SPA navigation does not re-fetch boot modules', async ({ page }) => { await page.goto(BASE_URL + '/sx/', { waitUntil: 'domcontentloaded' }); await waitForSxReady(page); // Start tracking requests AFTER boot const postBootSxbc = []; page.on('request', req => { if (req.url().includes('.sxbc')) { const name = req.url().split('/').pop().split('?')[0].replace('.sxbc', ''); postBootSxbc.push(name); } }); // Navigate to a different page via SPA await page.click('a[href*="geography"]'); await page.waitForTimeout(2000); // Boot modules should NOT be re-fetched const bootModules = ['dom', 'signals', 'boot', 'engine', 'orchestration']; for (const mod of bootModules) { expect(postBootSxbc).not.toContain(mod); } }); });