// 2026-10-20 SW Storybook simplification: sidebar order, hidden verification Stories, removed IDs, direct URL render of // every SW Story and the moved Property Panel (SW/Popup, ID sw-header--property-panel-compare). // Usage: build Storybook first, then node packages/sw/tests/storybook-simplification.browser.mjs [evidence-dir] import { readFile, writeFile, mkdir } from 'node:fs/promises' import path from 'node:path' import http from 'node:http' import { runtimeRequire } from './playwright-runtime.mjs' const require = runtimeRequire() const { chromium } = require('playwright') const out = path.resolve(process.argv[2] ?? 'docs/sw/storybook-simplification-qa'); await mkdir(out, { recursive: true }) const root = path.resolve('storybook-static'), mime = { '.html': 'text/html', '.js': 'text/javascript', '.css': 'text/css', '.json': 'application/json', '.svg': 'image/svg+xml', '.png': 'image/png', '.ttf': 'font/ttf', '.woff2': 'font/woff2' } const server = http.createServer(async (req, res) => { try { const p = new URL(req.url, 'http://localhost').pathname, file = path.resolve(root, '.' + decodeURIComponent(p === '/' ? '/index.html' : p)); res.setHeader('Content-Type', mime[path.extname(file)] ?? 'application/octet-stream'); res.end(await readFile(file)) } catch { res.writeHead(404).end() } }) await new Promise(r => server.listen(0, '127.0.0.1', r)); const base = `http://127.0.0.1:${server.address().port}` const browser = await chromium.launch({ headless: true, executablePath: process.env.SW_CHROME_PATH ?? 'C:/Program Files/Google/Chrome/Application/chrome.exe' }) const checks = []; const check = (n, ok, d) => { checks.push({ n, ok, d }); console.log(ok ? 'PASS' : 'FAIL', n, ok ? '' : JSON.stringify(d)) } const index = JSON.parse(await readFile(path.join(root, 'index.json'), 'utf8')) const sw = Object.values(index.entries).filter(e => e.type === 'story' && e.title.startsWith('SW/')) // 1. every SW story renders by direct URL without page errors const page = await browser.newPage({ viewport: { width: 1920, height: 1000 } }) let errors = []; page.on('pageerror', e => errors.push(e.message)); page.on('console', m => { if (m.type() === 'error') errors.push(m.text()) }) const bad = [] for (const e of sw) { errors = [] await page.goto(`${base}/iframe.html?id=${e.id}&viewMode=story`) try { await page.locator('#storybook-root > *').first().waitFor({ timeout: 15000 }) } catch { bad.push([e.id, 'no root']) ; continue } await page.waitForTimeout(150) const err = await page.locator('.sb-show-errordisplay').count() if (err || errors.length) bad.push([e.id, errors.slice(0, 2)]) } check(`direct URL render: ${sw.length} SW Stories, no error display / page error`, bad.length === 0, bad) // 2. removed IDs are gone const removed = ['figma', 'capture-active', 'capture-outline', 'capture-recent', 'variants', 'recent-files'].map(id => `sw-header--home-menu-${id}`) check('removed Home Menu IDs absent from index', removed.every(id => !index.entries[id])) // 2026-10-20 Footer cleanup: retired Footer IDs are gone, the two main Footer Stories stay. check('removed Footer IDs absent, full-footer / figma-variants kept', ['sw-footer--default', 'sw-footer--states'].every(id => !index.entries[id]) && ['sw-footer--full-footer', 'sw-footer--figma-variants'].every(id => index.entries[id])) // 3. Property panel in Popup: four states await page.goto(`${base}/iframe.html?id=sw-header--property-panel-compare&viewMode=story`) await page.locator('[data-compare]').first().waitFor() await page.waitForTimeout(300) const cells = await page.locator('[data-compare]').evaluateAll(list => list.map(c => { const r = c.querySelector('.hm-sw-property').getBoundingClientRect(); return [c.dataset.compare, Math.round(r.width), Math.round(r.height)] })) check('Popup › Property Panel: four states none/all/circle/expanded at 314 × 256/361/658/754', JSON.stringify(cells) === JSON.stringify([['none', 314, 256], ['all', 314, 361], ['circle', 314, 658], ['expanded', 314, 754]]), cells) await page.screenshot({ path: path.join(out, 'popup-property-panel-compare.png'), fullPage: true }) // 4. manager sidebar const m = await browser.newPage({ viewport: { width: 1600, height: 2400 } }) await m.goto(`${base}/?path=/story/sw-header--overview`) await m.locator('[data-item-id="sw-header--overview"]').waitFor() for (const c of ['sw-footer', 'sw-popup', 'sw-viewer', 'sw-theme']) { const b = m.locator(`[data-item-id="${c}"] > button[aria-expanded="false"]`); if (await b.count()) await b.click() } await m.waitForTimeout(500) const rows = await m.locator('#storybook-explorer-tree [data-item-id^="sw-"]').evaluateAll(list => list.map(e => [e.dataset.itemId, e.dataset.nodetype, e.textContent.replace('Skip to content', '').trim()])) await writeFile(path.join(out, 'sidebar.json'), JSON.stringify(rows, null, 1)) const comps = rows.filter(r => r[1] === 'component').map(r => r[2]) // 2026-10-21 (user request): SW/Theme left the sidebar (verification tag); its Story keeps its ID and direct URL. check('sidebar SW order Header → Footer → Popup → Viewer (Theme hidden)', JSON.stringify(comps) === JSON.stringify(['Header', 'Footer', 'Popup', 'Viewer']), comps) const hidden = ['sw-header--window-move-motion', 'sw-header--k-guide', 'sw-footer--figma-variants', 'sw-header--detail-buttons', 'sw-header--home-menu-capture-default'] check('verification Stories not in sidebar', rows.every(r => !hidden.includes(r[0])), rows.filter(r => hidden.includes(r[0]))) const popupRows = await m.locator('#storybook-explorer-tree [data-item-id]').evaluateAll(list => list.map(e => e.dataset.itemId)) const pi = popupRows.indexOf('sw-popup') // 2026-10-22 (user request): SW/Popup shows only Property Panel; Custom Content / Multiple Popups are verification. check('Popup children: Property Panel only', JSON.stringify(popupRows.slice(pi + 1, pi + 2)) === JSON.stringify(['sw-header--property-panel-compare']) && !popupRows.some(id => id.startsWith('sw-popup--')), popupRows.slice(pi + 1, pi + 4)) const headerIds = rows.filter(r => r[0].startsWith('sw-header--')).map(r => r[0]) check('Property Panel listed once (under Popup only)', headerIds.filter(id => id === 'sw-header--property-panel-compare').length === 1) await m.locator('[data-item-id="sw-header--property-panel-compare"]').click(); await m.waitForTimeout(800) check('sidebar link opens moved Story with same ID', m.url().includes('sw-header--property-panel-compare'), m.url()) await m.screenshot({ path: path.join(out, 'sidebar.png'), clip: { x: 0, y: 0, width: 320, height: 2400 } }) await writeFile(path.join(out, 'result.json'), JSON.stringify(checks, null, 1)) console.log(checks.filter(c => c.ok).length + '/' + checks.length + ' PASS') await browser.close(); server.close()