DS-konvensjon-adopsjon på 14 renderere over 6 sesjoner. Etter v1.13.0/.1 patchet 10+ symptomatiske visuelle bugs (191 linjer lokal CSS, 21 fix-kommentarer), grep v1.14.0 root-cause via DS v0.4.0 + per-renderer refaktor. Sesjon 2 — DS v0.4.0: - B-DS-1: kanban-card word-break (break-all → break-word) - B-DS-2: expansion title-main/sub display:block (var inline) - B-DS-3: matrix-bubble cursor + hover/focus Sesjon 3 — risk-renderere til DS-summary-grid + ros-layout (renderDpia, renderSecurity, renderRos) Sesjon 4 — 6 compliance/govern-renderere bytter .report-meta-wrapper mot DS-konvensjon (renderAiActPyramid, renderRequirements, renderConformity, renderTransparency, renderFria, renderReview) Sesjon 5 — phase-renderere til expansion-list per fase (renderMigrate, renderPoc — slett .phase-detail-CSS) Sesjon 5b — lavt-scope renderer-fixes: - renderCost: ekstraher .monthly fra p50/p90-objekter (key-stats viste \"[object Object]\") - renderCompare: distinctive-token-matching erstatter firstWord-heuristikk - renderUtredning: droppet misvisende role=\"tab\" Sesjon 6 — ship: kommentar-kompaksjon (145 → 122 linjer), 24 screenshots regenerert til v1.14.0/, dokumentasjon (3 nivåer), versjonsbump, mellomfiler slettet. Lokal style-blokk: 191 → 122 effektive linjer (~36% reduksjon) DS bumpet til v0.4.0 (delt mellom plugins, andre re-syncer på eget tempo) 17 renderere PASS visuell QA mot demo-data i begge themes 219 plugin-validering, 272 E2E playground, 7 migrations PASS Refs V1.14.0-PLAN + V1.14.0-AUDIT (slettet ved ship per plan).
186 lines
6.2 KiB
JavaScript
186 lines
6.2 KiB
JavaScript
#!/usr/bin/env node
|
|
// Capture playground screenshots for v1.14.0 documentation.
|
|
//
|
|
// Opens the single-file playground HTML via file://, drives it through:
|
|
// - Initial onboarding (empty state)
|
|
// - "Last inn demo-data" → project surface with all 17 reports rehydrated
|
|
// - All 4 project screen-tabs (oversikt / rapporter / kontekst / eksport)
|
|
// - Each rapport-tab category (regulatory / security / economy / docs / tool)
|
|
// - Both themes (dark + light)
|
|
//
|
|
// Output: playground/screenshots/v1.14.0/<surface>-<theme>.png
|
|
//
|
|
// Usage:
|
|
// cd tests/screenshot
|
|
// npm install
|
|
// npx playwright install chromium # ~150MB download, one-time
|
|
// node run.mjs
|
|
|
|
import { chromium } from 'playwright';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { dirname, resolve, join } from 'node:path';
|
|
import { mkdirSync, existsSync } from 'node:fs';
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = dirname(__filename);
|
|
const PLUGIN_ROOT = resolve(__dirname, '..', '..');
|
|
const HTML_PATH = join(PLUGIN_ROOT, 'playground', 'ms-ai-architect-playground.html');
|
|
const OUT_DIR = join(PLUGIN_ROOT, 'playground', 'screenshots', 'v1.14.0');
|
|
const HTML_URL = 'file://' + HTML_PATH;
|
|
|
|
const VIEWPORT = { width: 1440, height: 900 };
|
|
const FULL_PAGE = true;
|
|
|
|
function ensureOutDir() {
|
|
if (!existsSync(OUT_DIR)) mkdirSync(OUT_DIR, { recursive: true });
|
|
}
|
|
|
|
async function setTheme(page, theme) {
|
|
await page.evaluate((t) => {
|
|
document.documentElement.setAttribute('data-theme', t);
|
|
try { localStorage.setItem('ms-ai-architect-theme', t); } catch (e) {}
|
|
const labels = document.querySelectorAll('[data-theme-label]');
|
|
for (const l of labels) l.textContent = t === 'dark' ? 'Mørk' : 'Lys';
|
|
}, theme);
|
|
await page.waitForTimeout(150);
|
|
}
|
|
|
|
async function clearState(page) {
|
|
await page.evaluate(() => {
|
|
try { localStorage.clear(); } catch (e) {}
|
|
try {
|
|
// Best-effort: clear IndexedDB databases.
|
|
const dbs = ['ms-ai-architect-state-v1', 'ms-ai-architect-playground'];
|
|
dbs.forEach((n) => indexedDB.deleteDatabase(n));
|
|
} catch (e) {}
|
|
});
|
|
}
|
|
|
|
async function loadDemo(page) {
|
|
await page.evaluate(() => {
|
|
const action = document.querySelector('[data-action="load-demo"]');
|
|
if (action) action.click();
|
|
});
|
|
// Wait for project surface to render + rehydrate paste-imports.
|
|
await page.waitForSelector('[data-surface="project"]:not([hidden])', { timeout: 5000 });
|
|
await page.waitForTimeout(800); // settle rehydrate microtasks
|
|
}
|
|
|
|
async function clickAction(page, action) {
|
|
await page.evaluate((a) => {
|
|
const el = document.querySelector('[data-action="' + a + '"]');
|
|
if (el) el.click();
|
|
}, action);
|
|
await page.waitForTimeout(300);
|
|
}
|
|
|
|
async function clickProjectTab(page, tabId) {
|
|
await page.evaluate((t) => {
|
|
const el = document.querySelector('[data-action="project-tab"][data-tab="' + t + '"]');
|
|
if (el) el.click();
|
|
}, tabId);
|
|
await page.waitForTimeout(400);
|
|
}
|
|
|
|
async function clickProjectScreen(page, screenId) {
|
|
await page.evaluate((s) => {
|
|
const el = document.querySelector('[data-action="project-screen"][data-screen="' + s + '"]');
|
|
if (el) el.click();
|
|
}, screenId);
|
|
await page.waitForTimeout(400);
|
|
}
|
|
|
|
async function shoot(page, name) {
|
|
const path = join(OUT_DIR, name + '.png');
|
|
await page.screenshot({ path, fullPage: FULL_PAGE });
|
|
console.log(' → ' + name + '.png');
|
|
}
|
|
|
|
async function captureAllSurfaces(page, theme) {
|
|
console.log('\n[' + theme + ' theme]');
|
|
|
|
// 1. Onboarding (empty state)
|
|
await clearState(page);
|
|
await page.goto(HTML_URL);
|
|
await page.waitForSelector('[data-surface="onboarding"]:not([hidden])', { timeout: 5000 });
|
|
await setTheme(page, theme);
|
|
await shoot(page, '01-onboarding-empty-' + theme);
|
|
|
|
// 2. Load demo → project surface (rapporter screen, regulatory tab default)
|
|
await loadDemo(page);
|
|
await setTheme(page, theme);
|
|
await shoot(page, '02-project-rapporter-regulatory-' + theme);
|
|
|
|
// 3. Project tab cycle (5 categories)
|
|
const TABS = [
|
|
{ id: 'security', label: 'security' },
|
|
{ id: 'economy', label: 'economy' },
|
|
{ id: 'documentation', label: 'documentation' },
|
|
{ id: 'tool', label: 'tool' }
|
|
];
|
|
for (const tab of TABS) {
|
|
await clickProjectTab(page, tab.id);
|
|
await page.waitForTimeout(500);
|
|
await shoot(page, '03-project-rapporter-' + tab.label + '-' + theme);
|
|
}
|
|
|
|
// 4. Project screen-tabs (oversikt / kontekst / eksport)
|
|
await clickProjectScreen(page, 'oversikt');
|
|
await shoot(page, '04-project-oversikt-' + theme);
|
|
await clickProjectScreen(page, 'kontekst');
|
|
await shoot(page, '05-project-kontekst-' + theme);
|
|
await clickProjectScreen(page, 'eksport');
|
|
await shoot(page, '06-project-eksport-' + theme);
|
|
|
|
// Back to rapporter for nav screenshots
|
|
await clickProjectScreen(page, 'rapporter');
|
|
|
|
// 5. Home surface
|
|
await clickAction(page, 'goto-home');
|
|
await page.waitForSelector('[data-surface="home"]:not([hidden])');
|
|
await page.waitForTimeout(300);
|
|
await shoot(page, '07-home-' + theme);
|
|
|
|
// 6. Catalog surface
|
|
await clickAction(page, 'goto-catalog');
|
|
await page.waitForSelector('[data-surface="catalog"]:not([hidden])');
|
|
await page.waitForTimeout(300);
|
|
await shoot(page, '08-catalog-' + theme);
|
|
|
|
// 7. Onboarding (with prefilled state from demo)
|
|
await clickAction(page, 'goto-onboarding');
|
|
await page.waitForSelector('[data-surface="onboarding"]:not([hidden])');
|
|
await page.waitForTimeout(300);
|
|
await shoot(page, '09-onboarding-prefilled-' + theme);
|
|
}
|
|
|
|
async function main() {
|
|
ensureOutDir();
|
|
console.log('[screenshot] launching Chromium…');
|
|
const browser = await chromium.launch();
|
|
const context = await browser.newContext({
|
|
viewport: VIEWPORT,
|
|
deviceScaleFactor: 2 // crisper screenshots for retina
|
|
});
|
|
const page = await context.newPage();
|
|
page.on('console', (msg) => {
|
|
const t = msg.type();
|
|
if (t === 'error' || t === 'warning') {
|
|
console.warn(' [browser ' + t + '] ' + msg.text());
|
|
}
|
|
});
|
|
|
|
try {
|
|
for (const theme of ['dark', 'light']) {
|
|
await captureAllSurfaces(page, theme);
|
|
}
|
|
console.log('\n[screenshot] done — output: ' + OUT_DIR);
|
|
} finally {
|
|
await browser.close();
|
|
}
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error('[screenshot] FAILED:', err);
|
|
process.exit(1);
|
|
});
|