Files
documentation/scripts/browser/roadmap.cjs
T
jarvis-at-skic 1a2d16bc91
Build and publish Eldorado documentation / build (pull_request) Successful in 1m30s
docs: [verified] answer the six-month AI Architect assignment
2026-10-08 16:59:36 +00:00

165 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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/roadmap-${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 {
// Intermediate widths must use the readable vertical Roadmap, not shrink the loop.
for (const width of [768, 1024]) {
const context = await browser.newContext({viewport:{width,height:1000}, colorScheme:'light'});
try {
const page = await context.newPage();
const errors = [];
page.on('pageerror', error => errors.push(error.message));
const response = await page.goto(`${base}/eldorado-v1/documentation/strategy/roadmap/`, {waitUntil:'networkidle'});
assert.equal(response.status(), 200);
const panel = page.locator('.archify-panel--story[data-diagram="strategy-roadmap"]');
const iframe = panel.locator('.story-mobile');
assert(await iframe.isVisible(), `${width}: vertical Roadmap iframe must be visible`);
assert(!await panel.locator('.story-desktop').isVisible(), `${width}: horizontal Roadmap must be hidden`);
const frame = await (await iframe.elementHandle()).contentFrame();
await frame.locator('svg[role="img"]').waitFor();
const geometry = await frame.locator('svg[role="img"]').evaluate(svg => ({
width:innerWidth, height:innerHeight,
overflow:document.documentElement.scrollWidth > innerWidth || document.documentElement.scrollHeight > innerHeight,
nodes:[...svg.querySelectorAll('[data-node-id]')].map(node => ({
id:node.dataset.nodeId, box:node.querySelector('rect').getBoundingClientRect().toJSON(),
text:[...node.querySelectorAll('text')].map(t => ({text:t.textContent, box:t.getBoundingClientRect().toJSON(), size:parseFloat(getComputedStyle(t).fontSize)*t.getScreenCTM().a}))
})).sort((a,b) => a.box.y-b.box.y || a.box.x-b.box.x)
}));
assert.deepEqual(geometry.nodes.map(n => n.id), ['skills','autonomy']);
for (const [index, card] of geometry.nodes.entries()) {
assert(card.text.length > 0, `${width}: missing text in ${card.id}`);
assert(card.box.left >= 0 && card.box.top >= 0 && card.box.right <= geometry.width + 1 && card.box.bottom <= geometry.height + 1, `${width}: card outside iframe: ${card.id}`);
for (const t of card.text) {
assert(t.box.left >= card.box.left && t.box.right <= card.box.right + 1 && t.box.top >= card.box.top && t.box.bottom <= card.box.bottom + 1, `${width}: clipped ${card.id} ${t.text}`);
assert(t.size >= 11, `${width}: small ${card.id} ${t.text}: ${t.size}`);
}
if (index) {
const previous = geometry.nodes[index - 1].box;
assert.equal(previous.x, card.box.x);
assert(previous.bottom < card.box.top, `${width}: stages must flow vertically`);
}
}
assert(!geometry.overflow, `${width}: iframe overflow`);
assert(!await page.evaluate(() => document.documentElement.scrollWidth > innerWidth), `${width}: page overflow`);
assert.deepEqual(errors, []);
results.push({width, geometry, responsiveReadability:true});
fs.writeFileSync(`${output}/results.json`, JSON.stringify(results,null,2));
} finally {await context.close();}
}
for (const width of [1440, 390, 320]) {
const context = await browser.newContext({viewport:{width,height:1000}, colorScheme:'light'});
const page = await context.newPage();
const errors = [];
page.on('pageerror', error => errors.push(error.message));
const response = await page.goto(`${base}/eldorado-v1/documentation/strategy/roadmap/`, {waitUntil:'networkidle'});
assert.equal(response.status(), 200);
assert.equal(await page.locator('h1').innerText(), 'Roadmap');
const headings = page.locator('.strategy-content > h2');
assert.deepEqual((await headings.allTextContents()).map(s => s.replace(/\u200b/g, '')), ['Six months, two investment decisions','Delivery first, replication only if earned','Keep one learning loop','Solo capacity and influence']);
const text = await page.locator('.strategy-content').innerText();
for (const marker of ['Humans direct, guide and review throughout', 'Delivery keeps improving', 'system-logic source', 'baseline', 'Target operating model']) assert(text.includes(marker), marker);
assert(!text.includes('Supporting gate evidence and hold conditions\n\nThese are'));
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()).slice(0,2), ['Six-month strategy','Roadmap']);
if (width < 700) await page.getByRole('button', {name:'Close navigation bar'}).click();
const panel = page.locator('.archify-panel--story').first();
const iframe = panel.locator(width > 700 ? '.story-desktop' : '.story-mobile');
await iframe.scrollIntoViewIfNeeded();
const frame = await (await iframe.elementHandle()).contentFrame();
await frame.locator('svg[role="img"]').waitFor();
const checkCards = async () => {
const cards = await frame.locator('svg[role="img"] [data-node-id]').evaluateAll(nodes => nodes.map(node => {
const box = node.querySelector('rect').getBoundingClientRect();
return {id:node.dataset.nodeId, box:box.toJSON(), text:[...node.querySelectorAll('text')].map(t => ({text:t.textContent, box:t.getBoundingClientRect().toJSON(), size:parseFloat(getComputedStyle(t).fontSize)*t.getScreenCTM().a}))};
}));
for (const card of cards) for (const t of card.text) {
assert(t.box.left >= card.box.left && t.box.right <= card.box.right + 1 && t.box.top >= card.box.top && t.box.bottom <= card.box.bottom + 1, `${width}: clipped ${card.id} ${t.text}`);
assert(t.size >= 11, `${width}: small ${card.id} ${t.text}: ${t.size}`);
}
return cards;
};
await checkCards();
const geometry = await frame.locator('svg[role="img"]').evaluate(svg => ({
nodes:[...svg.querySelectorAll('[data-node-id]')].map(n => ({id:n.dataset.nodeId,...n.getBoundingClientRect().toJSON()})).sort((a,b) => a.y-b.y || a.x-b.x),
width:innerWidth, height:innerHeight,
minLabel:Math.min(...[...svg.querySelectorAll('text[data-node-label]')].map(n => parseFloat(getComputedStyle(n).fontSize)*n.getScreenCTM().a))
}));
assert.deepEqual(geometry.nodes.map(n => n.id), ['skills','autonomy']);
for (const n of geometry.nodes) assert(n.x >= 0 && n.y >= 0 && n.right <= geometry.width + 1 && n.bottom <= geometry.height + 1);
assert(geometry.minLabel >= 10, `readable node labels: ${geometry.minLabel}`);
for (let i=1; i<geometry.nodes.length; i++) {
const a=geometry.nodes[i-1], b=geometry.nodes[i];
if(width > 700) {assert.equal(a.y,b.y); assert(a.right < b.x);}
else {assert.equal(a.x,b.x); assert(a.bottom < b.y);}
}
// Portal -> diagram and diagram -> portal are both part of the contract.
if (width <= 700) await page.getByRole('button', {name:'Toggle navigation bar'}).click();
for (let attempt=0; attempt<3; attempt++) {
await page.getByRole('button', {name:/Switch between dark and light mode/}).filter({visible:true}).click();
if (await page.locator('html').getAttribute('data-theme') === 'dark') break;
}
if (width <= 700) await page.getByRole('button', {name:'Close navigation bar'}).click();
await frame.waitForFunction(() => document.documentElement.dataset.theme === 'dark');
await frame.locator('#btn-theme').click();
await page.waitForFunction(() => document.documentElement.dataset.theme === 'light');
await frame.locator('#btn-theme').click();
await page.waitForFunction(() => document.documentElement.dataset.theme === 'dark');
await checkCards();
await frame.locator('#btn-theme').click();
await page.waitForFunction(() => document.documentElement.dataset.theme === 'light');
for (const preset of ['blueprint','classic']) {
await frame.locator('#btn-preset').click();
await frame.locator(`[data-preset-value="${preset}"]`).click();
assert.equal(await frame.locator('html').getAttribute('data-preset'), preset);
await checkCards();
}
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(`${output}/${width}.svg`);
assert(fs.readFileSync(`${output}/${width}.svg`, 'utf8').includes('Learn and test'));
assert(fs.readFileSync(`${output}/${width}.svg`, 'utf8').includes('eldorado-roadmap-typography'));
for (const id of ['skills','autonomy']) {
await frame.locator(`svg[role="img"] [data-node-id="${id}"]`).screenshot({path:`${output}/${width}-${id}-card.png`});
}
const opener = panel.getByRole('button', {name:/^Expand /});
await opener.click();
const close = panel.getByRole('button', {name:'Close full-screen diagram', exact:true});
await close.waitFor({state:'visible'}); assert.equal(await close.innerText(), '×');
await close.click(); assert(await opener.evaluate(n => n === document.activeElement));
await opener.click(); await close.waitFor({state:'visible'}); await page.keyboard.press('Escape');
await page.waitForFunction(() => !document.querySelector('.archify-panel--story dialog').open);
assert(await opener.evaluate(n => n === document.activeElement));
assert(!await page.evaluate(() => document.documentElement.scrollWidth > innerWidth));
await page.evaluate(() => scrollTo({top:0, behavior:'instant'}));
await page.screenshot({path:`${output}/${width}-top.png`});
await headings.first().scrollIntoViewIfNeeded();
await page.screenshot({path:`${output}/${width}-assessment.png`});
await page.screenshot({path:`${output}/${width}-full.png`, fullPage:true});
await page.locator('.strategy-content summary').focus();
await page.keyboard.press('Enter');
await page.waitForFunction(() => document.querySelector('.strategy-content details').open);
await page.locator('.strategy-content details p').last().waitFor({state:'visible'});
assert((await page.locator('.strategy-content details').innerText()).includes('exact-artifact approvals'));
const links = await page.locator('.strategy-content a').evaluateAll(nodes => nodes.filter(n => !n.classList.contains('hash-link')).map(n => n.href));
for (const href of [...new Set(links)]) {
const r = await page.goto(href, {waitUntil:'networkidle'}); assert.equal(r.status(), 200);
assert.equal(page.url(), href);
}
assert.deepEqual(errors, []);
results.push({width, geometry, links:links.length, controls:true, export:true, sidebar:true, visibleAssessment:true});
fs.writeFileSync(`${output}/results.json`, JSON.stringify(results,null,2));
await context.close();
}
console.log(`PASS ${results.length} Roadmap viewports: three-level story, sidebar, diagram geometry, controls, export and links`);
} finally {await browser.close();}
})().catch(error => {console.error(error); process.exitCode = 1;});