waybetter-for-x/apps/desktop/scripts/make-shots.mjs
2026-10-01 06:01:13 +07:00

110 lines
6.1 KiB
JavaScript

// "Make" section screenshots and flow checks on the panel preview harness (`/?panel&section=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&section=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;