// "Make" section screenshots and flow checks on the panel preview harness (`/?panel§ion=make`, // needs `vite dev`; native commands are mocked in memory). Opens the real UI kit demos, edits an // example, walks the theme explorer and the bundled docs. Not a native or live X test. // Usage: node scripts/make-shots.mjs [baseUrl] [outDir] import { chromium } from 'playwright'; import { mkdirSync } from 'node:fs'; import { join } from 'node:path'; const base = process.argv[2] ?? 'http://127.0.0.1:1420'; const out = process.argv[3] ?? join(import.meta.dirname, '..', '..', '..', 'verification', 'dev-docs'); mkdirSync(out, { recursive: true }); const failures = []; const check = (label, ok, detail = '') => { console.log(`${ok ? 'ok ' : 'FAIL'} ${label}${detail ? ` (${detail})` : ''}`); if (!ok) failures.push(label); }; const browser = await chromium.launch(); async function open(viewport) { const page = await browser.newPage({ viewport, deviceScaleFactor: 1 }); page.on('pageerror', error => failures.push(`page error: ${error.message}`)); await page.goto(`${base}/?panel§ion=make`); await page.waitForSelector('.bmk'); await page.waitForTimeout(500); return page; } const shot = (page, name, fullPage = false) => page.screenshot({ path: join(out, `${name}.png`), fullPage }); const tab = (page, name) => page.locator('.bmk-tabs button', { hasText: name }).click(); /** What the kit drew in its shadow root (`#wb-make-ui`). */ const kit = (page, selector) => page.evaluate(sel => { const host = document.getElementById('wb-make-ui'); const found = host?.shadowRoot?.querySelector(sel); return found ? (found.textContent ?? '').trim() : null; }, selector); const page = await open({ width: 1180, height: 900 }); check('nav shows Make', await page.locator('.btp-nav button', { hasText: 'Make' }).getAttribute('aria-current') === 'page'); await shot(page, 'make-plugin'); // Editing the example updates the header preview and the warnings the Plugins list would show. const editor = page.locator('.bmk-editor textarea'); const original = await editor.inputValue(); await editor.fill(original.replace('@name Hide Trends', '@name Hide Trends Now')); check('header preview follows edits', (await page.locator('.bt-section-head h2', { hasText: 'Hide Trends Now' }).count()) === 1); await editor.fill('module.exports = {};'); check('missing header and stop() are flagged', (await page.locator('.bmk-warnings li').count()) >= 2); await page.locator('.bt-section-action button', { hasText: 'Reset' }).click(); check('reset restores the example', (await editor.inputValue()) === original); await page.locator('.bmk-example', { hasText: 'Settings dialog' }).click(); check('switching examples', (await editor.inputValue()).includes('@name Hide Promoted')); await shot(page, 'make-plugin-settings-example'); // UI kit gallery: real toasts and dialogs from the runtime's kit, in the current theme. await tab(page, 'UI kit'); await shot(page, 'make-kit'); await page.locator('.bmk-kit-row button', { hasText: 'Toast with Undo' }).click(); await page.waitForTimeout(350); check('toast is shown', (await kit(page, '.wb-toast'))?.includes('Post hidden') ?? false); await shot(page, 'make-kit-toast'); await page.locator('.bmk-kit-row button', { hasText: 'Confirm' }).click(); await page.waitForTimeout(350); check('confirm dialog is shown', (await kit(page, '[role="alertdialog"]'))?.includes('Delete this note?') ?? false); await shot(page, 'make-kit-confirm'); await page.keyboard.press('Escape'); await page.waitForTimeout(300); check('Esc resolves confirm to false', (await page.locator('.bmk-kit-result').textContent())?.includes('false') ?? false); await page.locator('.bmk-kit-row button', { hasText: 'Prompt' }).click(); await page.waitForTimeout(300); await page.keyboard.press('Control+a'); await page.keyboard.type('ab'); await page.keyboard.press('Enter'); await page.waitForTimeout(200); check('prompt validation refuses a short value', (await kit(page, '[role="dialog"]'))?.includes('Use at least 3 characters.') ?? false); await shot(page, 'make-kit-prompt'); await page.keyboard.type('c'); await page.keyboard.press('Enter'); await page.waitForTimeout(300); check('prompt resolves with the value', (await page.locator('.bmk-kit-result').textContent())?.includes('"abc"') ?? false); await page.locator('.bmk-kit-row button', { hasText: /^Dialog$/ }).click(); await page.waitForTimeout(350); check('dialog is shown', (await kit(page, '[role="dialog"]'))?.includes('Hide trends') ?? false); await shot(page, 'make-kit-dialog'); await page.keyboard.press('Escape'); // Theme API explorer and the bundled docs. await tab(page, 'Theme API'); await page.waitForSelector('.bmk-swatch'); // Leaving the gallery closes what it opened (the toast plays its exit animation first). await page.waitForTimeout(700); check('leaving the gallery closes its toasts and dialogs', await page.evaluate(() => !document.getElementById('wb-make-ui'))); check('every color role is listed', (await page.locator('.bmk-swatch').count()) === 11); check('variables are named like themeVars', (await page.locator('.bmk-swatch code').allTextContents()).includes('--wb-on-accent')); await shot(page, 'make-theme-api', true); await tab(page, 'Docs'); await page.waitForSelector('.bmk-doc h2'); check('PLUGINS.md renders', (await page.locator('.bmk-doc h2').first().textContent())?.includes('plugin') ?? false); check('PLUGINS.md tables render', (await page.locator('.bmk-doc table').count()) >= 2); await shot(page, 'make-docs'); await page.locator('.bmk-doc .bmk-doc-link', { hasText: 'PLUGIN-IDEAS.md' }).first().click(); check('links between bundled docs switch docs', (await page.locator('.bmk-docs [aria-checked="true"], .bmk-docs .is-on').first().textContent())?.includes('PLUGIN-IDEAS') ?? false); await page.close(); // A narrow split pane beside X. const narrow = await open({ width: 460, height: 900 }); await shot(narrow, 'make-narrow'); await tab(narrow, 'UI kit'); await shot(narrow, 'make-kit-narrow'); await narrow.close(); await browser.close(); console.log(failures.length ? `\n${failures.length} failed:\n ${failures.join('\n ')}` : '\nall checks passed'); process.exitCode = failures.length ? 1 : 0;