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/strategy-navigation-${process.argv[3] || 'local'}`; fs.mkdirSync(output, {recursive:true}); const prefix = '/eldorado-v1/documentation/strategy/'; const first = '6 Months Roadmap'; const second = 'Beyond 6 Months'; const topics = ['Skills lifecycle reference','Measurement','Baseline','Governance','Quality','Adoption','Maintenance']; const metrics = JSON.parse(fs.readFileSync('research/ai-adoption/measurement-migration.json','utf8')).metrics; const childIds = [...metrics.map(m => m.docId),'measurement/collection-and-safeguards','measurement/synthetic-example']; const childTitles = [...metrics.map(m => m.title),'Collection and safeguards','Run the synthetic measurement example']; const ids = ['target-operating-model','roadmap','six-months-target','six-months-roadmap','stage-01-skills-adoption','measurement',...childIds,'baseline','skills-governance','skills-quality','skills-adoption','skills-maintenance','stage-02-autonomous-agent']; const titles = ['Target','Roadmap','6 Months Target',first,'Skills lifecycle reference','Measuring real output',...childTitles,'Baseline','Skills governance','Skills quality','Skills adoption','Skills maintenance',second]; const browser = await chromium.launch({headless:true, executablePath:process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE, args:['--no-sandbox']}); const results = []; try { for (const width of [1440,1024,390,320]) { const context = await browser.newContext({viewport:{width,height:900}, colorScheme:'light'}); try { const page = await context.newPage(); const errors = []; page.on('pageerror', error => errors.push(error.message)); const openMenu = async () => { if (width < 997) await page.getByRole('button', {name:'Toggle navigation bar'}).click(); const menu = page.locator('.theme-doc-sidebar-menu').filter({visible:true}); await menu.locator('.menu__link').first().waitFor({state:'visible'}); return menu; }; const closeMenu = async () => { if (width < 997) await page.getByRole('button', {name:'Close navigation bar'}).click(); }; assert.equal((await page.goto(`${base}${prefix}roadmap/`, {waitUntil:'networkidle'})).status(), 200); let menu = await openMenu(); const visibleLabels = () => menu.locator('.menu__link').filter({visible:true}).allTextContents(); assert.deepEqual(await visibleLabels(), ['Target','Roadmap','6 Months Target',first,second]); const category = menu.locator('.theme-doc-sidebar-item-category').filter({has:page.getByRole('link', {name:first, exact:true})}); const caret = category.locator(':scope > .menu__list-item-collapsible > button.menu__caret'); await caret.click(); await category.getByRole('link', {name:'Governance', exact:true}).waitFor({state:'visible'}); assert.deepEqual(await visibleLabels(), ['Target','Roadmap','6 Months Target',first,...topics,second]); assert.deepEqual(await category.locator('ul .menu__link').filter({visible:true}).allTextContents(), topics); const measurement = category.locator('.theme-doc-sidebar-item-category').filter({has:page.getByRole('link', {name:'Measurement', exact:true})}); const reference = measurement.locator(':scope > .menu__list-item-collapsible > button.menu__caret'); await reference.focus(); await page.keyboard.press('Enter'); await category.getByRole('link', {name:'Validated task success', exact:true}).waitFor({state:'visible'}); assert.deepEqual(await visibleLabels(), ['Target','Roadmap','6 Months Target',first,...topics.slice(0,2),...childTitles,...topics.slice(2),second]); await reference.click(); await page.screenshot({path:`${output}/${width}-expanded.png`, animations:'disabled'}); await caret.click(); await category.getByRole('link', {name:'Governance', exact:true}).waitFor({state:'hidden'}); assert.deepEqual(await visibleLabels(), ['Target','Roadmap','6 Months Target',first,second]); await menu.getByRole('link', {name:first, exact:true}).click(); await page.waitForURL(`${base}${prefix}six-months-roadmap/`); await page.getByRole('heading', {level:1, name:first, exact:true}).waitFor(); assert.equal(await page.locator('h1').innerText(), first); assert((await page.locator('article').innerText()).includes('Delivery first')); assert(!await page.evaluate(() => document.documentElement.scrollWidth > innerWidth)); await page.screenshot({path:`${output}/${width}-stage-01.png`, fullPage:true, animations:'disabled'}); for (const [index, id] of ids.entries()) { assert.equal((await page.goto(`${base}${prefix}${id}/`, {waitUntil:'networkidle'})).status(), 200); assert.equal(new URL(page.url()).pathname, `${prefix}${id}/`); // Target has a longer canonical page heading but keeps its short navigation label. assert.equal(await page.locator('h1').innerText(), titles[index]); const prev = page.locator('.pagination-nav__link--prev'); const next = page.locator('.pagination-nav__link--next'); if (index) assert.equal(await prev.getAttribute('href'), `${prefix}${id === 'baseline' ? 'measurement' : ids[index-1]}/`); else assert.equal(await prev.count(), 0); if (index < ids.length-1) assert.equal(await next.getAttribute('href'), `${prefix}${id === 'measurement' ? 'baseline' : ids[index+1]}/`); else assert.equal(await next.count(), 0); if (index >= 4 && index < ids.length - 1) { assert.equal(await page.locator('.breadcrumbs a').filter({hasText:first}).getAttribute('href'), `${prefix}six-months-roadmap/`); menu = await openMenu(); const expectedTopics = id === 'measurement' || id.startsWith('measurement/') ? [...topics.slice(0,2),...childTitles,...topics.slice(2)] : topics; assert.deepEqual(await visibleLabels(), ['Target','Roadmap','6 Months Target',first,...expectedTopics,second]); if (id.startsWith('measurement/')) assert.equal(await page.locator('.breadcrumbs a').filter({hasText:'Measurement'}).getAttribute('href'), `${prefix}measurement/`); await closeMenu(); } assert(!await page.evaluate(() => document.documentElement.scrollWidth > innerWidth), `${width}: overflow on ${id}`); if(width===1440 || width===390) { // Full-page capture does not trigger below-fold lazy iframes. for(const iframe of await page.locator('.archify-panel--story > iframe').filter({visible:true}).all()) { await iframe.scrollIntoViewIfNeeded(); const frame=await (await iframe.elementHandle()).contentFrame(); await frame.locator('.diagram-container > svg').waitFor(); } await page.evaluate(()=>scrollTo({top:0,behavior:'instant'})); await page.screenshot({path:`${output}/${width}-${id.replaceAll('/','-')}.png`,fullPage:true,animations:'disabled'}); } } assert((await page.locator('article').innerText()).includes('not deployed autonomy')); await page.screenshot({path:`${output}/${width}-stage-02.png`, fullPage:true, animations:'disabled'}); assert.deepEqual(errors, []); results.push({width, routes:ids.length, linkedCategory:true, expandCollapse:true, nesting:true, breadcrumbs:true, pagination:true, overflow:false, errors}); fs.writeFileSync(`${output}/results.json`, JSON.stringify(results,null,2)); } finally {await context.close();} } assert.equal(results.length, 4); console.log(`PASS: 4 desktop/mobile viewports; all ${ids.length} routes; linked Measurement category, keyboard expansion, breadcrumbs, next/previous and no overflow/errors`); } finally {await browser.close();} })().catch(error => {console.error(error); process.exitCode = 1;});