const {chromium}=require('playwright'); const fs=require('node:fs'); const assert=require('node:assert/strict'); (async()=>{ const base=process.argv[2]||'http://127.0.0.1:4173'; const out=`.cache/skills-${process.argv[3]||'candidate'}`;fs.mkdirSync(out,{recursive:true}); const browser=await chromium.launch({executablePath:process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE,args:['--no-sandbox']}); const results=[]; try { for(const topic of ['measurement','baseline','governance','quality','adoption','maintenance','stage01','stage02']) for(const width of [1440,1024,390,320]) { console.log(`Checking ${topic} at ${width}px`); const page=await browser.newPage({viewport:{width,height:900},colorScheme:'light'});const errors=[];page.on('pageerror',e=>errors.push(e.message)); const route=topic==='stage01'?'stage-01-skills-adoption':topic==='stage02'?'stage-02-autonomous-agent':['measurement','baseline'].includes(topic)?topic:`skills-${topic}`; assert.equal((await page.goto(`${base}/eldorado-v1/documentation/strategy/${route}/`,{waitUntil:'networkidle'})).status(),200); const panel=page.locator('.archify-panel--skills');assert.equal(await panel.count(),1); const iframe=panel.locator(width>1100?'.story-desktop':'.story-mobile');await iframe.scrollIntoViewIfNeeded(); const frame=await(await iframe.elementHandle()).contentFrame();await frame.locator('svg[role="img"]').waitFor(); // Keep iframe controls below the fixed portal header. Frame-local click // actionability cannot detect an overlay in the parent document. const showControls=async()=>{ await panel.locator('dialog iframe').waitFor({state:'detached'}); await panel.evaluate(n=>{ scrollTo({top:n.getBoundingClientRect().top+scrollY-100,behavior:'instant'}); return new Promise(resolve=>requestAnimationFrame(()=>requestAnimationFrame(resolve))); }); }; const geometry=await frame.locator('svg[role="img"]').evaluate(svg=>{ const nodes=[...svg.querySelectorAll('g[data-node-id]')];const issues=[];let min=Infinity; for(const n of nodes){const rect=n.querySelector('rect').getBBox();for(const t of n.querySelectorAll('text')){ const b=t.getBBox();min=Math.min(min,parseFloat(getComputedStyle(t).fontSize)*t.getScreenCTM().a); if(b.xrect.x+rect.width||b.y+b.height>rect.y+rect.height)issues.push(n.id+':'+t.textContent); }} return {nodes:nodes.length,min,issues,width:innerWidth,svgWidth:svg.getBoundingClientRect().width}; }); const source=JSON.parse(fs.readFileSync(`diagrams/strategy-${topic}.architecture.json`)); assert.equal(geometry.nodes,source.components.length);assert.deepEqual(geometry.issues,[]);assert(geometry.min>=10,JSON.stringify(geometry)); for(const theme of ['light','dark']){ await showControls(); if(await frame.locator('html').getAttribute('data-theme')!==theme)await frame.locator('#btn-theme').click(); await page.waitForFunction(t=>document.documentElement.dataset.theme===t,theme).catch(error=>{throw new Error(`${topic}/${width}/${theme}: theme synchronization failed`,{cause:error});}); await panel.scrollIntoViewIfNeeded();await page.screenshot({path:`${out}/${topic}-${width}-${theme}-inline.png`}); const open=panel.getByRole('button',{name:/^Expand /});await open.click();const close=panel.getByRole('button',{name:'Close full-screen diagram',exact:true});await close.waitFor({state:'visible'}); const expanded=await(await panel.locator('dialog iframe').elementHandle()).contentFrame();await expanded.locator('svg[role="img"]').waitFor(); await expanded.waitForFunction(t=>document.documentElement.dataset.theme===t,theme); await panel.locator('dialog').evaluate(n=>n.scrollTop=0); assert((await expanded.url()).includes(width<=1100?`${topic}-mobile/`:`${topic}/`)); await page.screenshot({path:`${out}/${topic}-${width}-${theme}-fullscreen.png`}); if(width<=700){await page.setViewportSize({width,height:480});assert(await panel.locator('dialog').evaluate(n=>n.scrollHeight>n.clientHeight));await panel.locator('dialog').evaluate(n=>n.scrollTop=n.scrollHeight);await page.screenshot({path:`${out}/${topic}-${width}-${theme}-short-bottom.png`});await page.setViewportSize({width,height:900});} await close.focus();await page.keyboard.press('Escape');assert(await open.evaluate(n=>n===document.activeElement)); } await showControls(); await frame.locator('#btn-preset').click();await frame.locator('[data-preset-value="blueprint"]').click(); await frame.locator('#btn-preset').click();await frame.locator('[data-preset-value="classic"]').click(); await frame.locator('#btn-export').click();const [download]=await Promise.all([page.waitForEvent('download'),frame.locator('[data-format="svg"]').click()]);assert.equal(await download.failure(),null); await download.saveAs(`${out}/${topic}-${width}.svg`);assert(fs.readFileSync(`${out}/${topic}-${width}.svg`,'utf8').includes('eldorado-skills-typography')); assert(!await page.evaluate(()=>document.documentElement.scrollWidth>innerWidth));assert.deepEqual(errors,[]); results.push({topic,width,geometry,controls:true,export:true,themes:true,fullscreen:true});fs.writeFileSync(`${out}/results.json`,JSON.stringify(results,null,2));await page.close(); } console.log(`PASS ${results.length} Skills story contexts: geometry, themes, controls, exports, fullscreen and short-phone scrolling`); }finally{await browser.close();} })().catch(e=>{console.error(e);process.exitCode=1});