Headless SPA verification: clean localStorage + fresh tab + raw page.screenshot
Context
Three real FestIn incidents were caught (or initially mis-diagnosed) by headless browser verification:
- Self-demotion lockout: the Users table allowed demoting your own admin → instant 403s. UI had to disable the role select for the current user.
- Stale app.js served from cache: a fixed bug appeared to persist across reloads;
fetch()withcache: 'no-store'proved the server served fresh code — the browser was the stale layer. - view-findings section missing from index.html: SPA completely inert, zero console errors.
Method
- Headless browser tab, viewport matching the bug report (or 390x844 + 1428x806 for responsive checks).
localStorage.clear()BEFORE testing login — residual tokens/roles make auth flows lie (a "fixed" login may just be an old token still working).- Login via element handles +
page.keyboard.type(raw Puppeteer); the higher-level tab helpers can silently skip form submit. - For screenshots into docs:
page.screenshot({type: 'png'})— the tab helpertab.screenshot()returns JPEG regardless of extension, which corrupts.pngfiles. - Console errors are NOT reliable: errors inside async handlers or late listeners never surface. When a route "does nothing", probe state directly:
// is init() actually bound? probe a delegated-click side effect
document.querySelector('[data-action="logout"]').click();
localStorage.getItem("festin_token") // null → init ran
- A test that toggles the hash and waits:
await page.reload({waitUntil: 'networkidle0'})+ 1-2s settle before asserting.
Contraste
Every UI change in the 0.3/0.4 releases was verified this way (desktop 1428-1508px + mobile 390px). The self-demotion bug and the inert-SPA bug were both caught only because the verification ran real flows against the real server, not unit tests of the JS.