const {chromium} = require('playwright'); const assert = require('node:assert/strict'); const fs = require('node:fs'); (async () => { const base = process.argv[2] || 'http://127.0.0.1:4173'; const output = `.cache/cycle-${process.argv[3] || 'local'}`; fs.mkdirSync(output, {recursive:true}); const browser = await chromium.launch({headless:true, executablePath:process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE, args:['--no-sandbox']}); const results = []; try { for (const width of [1440, 390, 320]) { const context = await browser.newContext({viewport:{width,height:1000}, colorScheme:'light'}); const page = await context.newPage(); await page.goto(`${base}/eldorado-v1/documentation/`, {waitUntil:'networkidle'}); if (width < 700) await page.getByRole('button', {name:'Toggle navigation bar'}).click(); const nav = page.getByRole('link', {name:'Strategy', exact:true}).filter({visible:true}); assert((await nav.getAttribute('href')).endsWith('/strategy/target-operating-model/')); await nav.click(); await page.waitForURL('**/strategy/target-operating-model/'); for (const route of ['target-operating-model/', '']) { await page.goto(`${base}/eldorado-v1/documentation/strategy/${route}`, {waitUntil:'networkidle'}); await page.waitForURL('**/strategy/target-operating-model/'); if (width < 700) await page.getByRole('button', {name:'Toggle navigation bar'}).click(); const menu = page.locator('.theme-doc-sidebar-menu .menu__link').filter({visible:true}); await menu.first().waitFor({state:'visible'}); assert.deepEqual(await menu.allTextContents(), ['Six-month strategy', 'Roadmap', 'One bounded experiment', 'Beyond six months']); assert((await menu.nth(0).getAttribute('href')).endsWith('/strategy/target-operating-model/')); assert((await menu.nth(1).getAttribute('href')).endsWith('/strategy/roadmap/')); assert.equal(await menu.filter({hasText:/^Six-month strategy$/}).count(), 1); await page.screenshot({path:`${output}/${width}-${route ? 'target' : 'landing'}-navigation.png`,fullPage:true,animations:'disabled'}); await menu.nth(1).click(); await page.waitForURL('**/strategy/roadmap/'); await page.getByRole('heading', {level:1,name:'Roadmap',exact:true}).waitFor(); await page.goBack({waitUntil:'domcontentloaded'}); await page.waitForURL('**/strategy/target-operating-model/'); await page.getByText('Conditional long-term Target operating model — not the six-month promise',{exact:true}).focus(); await page.keyboard.press('Enter'); const iframe = page.locator(`[data-diagram="strategy-target"] > .story-${width > 700 ? 'desktop' : 'mobile'}`); await iframe.scrollIntoViewIfNeeded(); const frame = await (await iframe.elementHandle()).contentFrame(); await frame.locator('svg[role="img"]').waitFor(); const metrics = await frame.evaluate(() => { const svg = document.querySelector('svg[role="img"]'); const labels = [...svg.querySelectorAll('text[data-node-label]')].map(n => ({text:n.textContent,px:parseFloat(getComputedStyle(n).fontSize)*n.getScreenCTM().a})); const edges = [...svg.querySelectorAll('path[data-edge-id^="stage-"]')].map(n => [n.dataset.edgeFrom,n.dataset.edgeTo,n.getAttribute('marker-end')]); const overflow = [...svg.querySelectorAll('[data-node-id]')].flatMap(n => { const box = n.querySelector('rect').getBoundingClientRect(); return [...n.querySelectorAll('text')].filter(t => {const b=t.getBoundingClientRect();return b.leftbox.right||b.topbox.bottom;}).map(t=>t.textContent); }); const relationships = [...svg.querySelectorAll('path[data-edge-id]')].map(n => ({id:n.dataset.edgeId,from:n.dataset.edgeFrom,to:n.dataset.edgeTo,dash:getComputedStyle(n).strokeDasharray,arrow:n.getAttribute('marker-end')})); const nodes = [...svg.querySelectorAll('[data-node-id]')].map(n => {const r=n.querySelector('rect').getBoundingClientRect();return {id:n.dataset.nodeId,x:r.x+r.width/2,y:r.y+r.height/2};}); return {labels,edges,overflow,relationships,nodes}; }); const ids = ['scope','architecture','ui-ux','planning','delivery','uat','security','releases']; assert.deepEqual(metrics.edges.map(e=>e.slice(0,2)), ids.map((id,i)=>[id,ids[(i+1)%ids.length]])); assert(metrics.edges.every(e=>e[2]?.startsWith('url(')), 'all stages must have arrowheads'); assert.equal(metrics.nodes.length, 17); assert.equal(metrics.labels.filter(n => n.text === 'AI Agent').length, 8); assert(!metrics.labels.some(n => /code[ -]agent/i.test(n.text))); assert((await page.locator('.strategy-content').innerText()).includes('human-controlled AI Agent')); assert(!metrics.nodes.some(n=>n.id==='humans')); assert.equal(await frame.locator('[data-node-id="agent"] text').allTextContents().then(texts=>texts.includes('Human Controlled')), true); assert.equal(metrics.relationships.length, 24); const governs = metrics.relationships.filter(e=>e.id.startsWith('govern-')); const contributions = metrics.relationships.filter(e=>e.id.startsWith('contribute-')); assert.deepEqual(governs.map(e=>[e.from,e.to]), ids.map(id=>['agent',id])); assert.deepEqual(contributions.map(e=>[e.from,e.to]), ids.map(id=>[`code-${id}`,id])); assert(governs.every(e=>e.dash==='3px, 9px' && e.arrow?.startsWith('url('))); assert(contributions.every(e=>e.dash==='12px, 8px' && e.arrow?.startsWith('url('))); assert(!metrics.relationships.some(e=>e.id==='human-direction' || e.from==='humans' || e.to==='humans')); if (width > 700) { const agent = metrics.nodes.find(n=>n.id==='agent'); const phases = metrics.nodes.filter(n=>ids.includes(n.id)); for (const axis of ['x','y']) assert(Math.abs(agent[axis]-(Math.min(...phases.map(n=>n[axis]))+Math.max(...phases.map(n=>n[axis])))/2)<1); for (const id of ids) { const phase=phases.find(n=>n.id===id), code=metrics.nodes.find(n=>n.id===`code-${id}`); assert(Math.hypot(code.x-agent.x,code.y-agent.y)>Math.hypot(phase.x-agent.x,phase.y-agent.y)); } } assert(metrics.labels.every(n=>n.px>=14), JSON.stringify(metrics.labels)); assert.deepEqual(metrics.overflow, []); await frame.locator('#btn-export').focus(); await page.keyboard.press('Enter'); await frame.locator('[data-format="svg"]').focus(); const [download] = await Promise.all([page.waitForEvent('download'), page.keyboard.press('Enter')]); assert.equal(await download.failure(), null); const exportPath = `${output}/${width}-${route ? 'target' : 'landing'}.svg`; await download.saveAs(exportPath); const exported = fs.readFileSync(exportPath, 'utf8'); assert.equal((exported.match(/>AI Agent<\/text>/g) || []).length, 8); assert(!/code[ -]agent/i.test(exported)); assert(exported.includes('Autonomous agent') && exported.includes('Human Controlled')); assert(!await page.evaluate(()=>document.documentElement.scrollWidth>innerWidth)); await page.evaluate(()=>scrollTo({top:0,behavior:'instant'})); await page.waitForFunction(()=>scrollY===0); await page.evaluate(()=>new Promise(resolve=>requestAnimationFrame(()=>requestAnimationFrame(resolve)))); await page.screenshot({path:`${output}/${width}-${route ? 'target' : 'landing'}.png`,fullPage:true}); results.push({width,route,...metrics}); fs.writeFileSync(`${output}/results.json`,JSON.stringify(results,null,2)); } await context.close(); } assert.equal(results.length,6); console.log('PASS 6 cycle routes/viewports: navigation, sidebar order, exact directed loop, arrowheads, >=14px labels, text bounds and no overflow'); } finally {await browser.close();} })().catch(error=>{console.error(error);process.exitCode=1;});