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

115 lines
5.7 KiB
JavaScript

// Visual check of the Theme Builder, font roles, the Google Fonts browser and Appearance's account /
// pane themes through the panel harness (/?panel, mocked native commands; the harness fetches Google
// Fonts itself, like native code would). Needs a Vite dev server (default 127.0.0.1:1437).
//
// node scripts/theme-builder-shots.mjs [outDir] [port]
import { mkdirSync } from 'node:fs';
import { chromium } from 'playwright';
const out = process.argv[2] ?? '../../verification/theme-builder';
const port = process.argv[3] ?? '1437';
mkdirSync(out, { recursive: true });
const browser = await chromium.launch();
const results = [];
async function shot(name, { width = 1440, height = 900, query = '', action } = {}) {
const page = await browser.newPage({ viewport: { width, height }, deviceScaleFactor: 2, colorScheme: 'dark' });
const errors = [];
page.on('pageerror', error => errors.push(error.message));
page.on('console', message => { if (message.type() === 'error') errors.push(message.text()); });
await page.goto(`http://127.0.0.1:${port}/?panel${query ? `&${query}` : ''}`);
await page.waitForTimeout(1000);
let note = '';
if (action) note = (await action(page)) ?? '';
await page.waitForTimeout(500);
await page.screenshot({ path: `${out}/${name}.png` });
results.push(`${name}: ${errors.length ? `ERRORS ${errors.join(' | ')}` : 'ok'}${note ? ` (${note})` : ''}`);
await page.close();
}
// Builder at desktop and split-pane widths.
await shot('builder-wide', { query: 'section=builder' });
await shot('builder-narrow', { width: 520, height: 1000, query: 'section=builder' });
// A light base: palette, contrast report with failures, then auto-fix.
await shot('builder-contrast', {
query: 'section=builder',
action: async page => {
await page.getByLabel('Background color hex').fill('#fbf8f1'); await page.getByLabel('Background color hex').press('Enter');
await page.getByLabel('Text color hex').fill('#8a8a8a'); await page.getByLabel('Text color hex').press('Enter');
await page.getByLabel('Accent color hex').fill('#ffd400'); await page.getByLabel('Accent color hex').press('Enter');
await page.locator('.wb-card[aria-label="Contrast"]').scrollIntoViewIfNeeded();
const failing = await page.locator('.wb-contrast li.is-fail').count();
return `${failing} failing pairs before auto-fix`;
},
});
await shot('builder-autofix', {
query: 'section=builder',
action: async page => {
await page.getByLabel('Text color hex').fill('#8a8a8a'); await page.getByLabel('Text color hex').press('Enter');
await page.getByRole('button', { name: /Auto-fix/ }).click();
await page.locator('.wb-card[aria-label="Contrast"]').scrollIntoViewIfNeeded();
const failing = await page.locator('.wb-contrast li.is-fail').count();
// Undo brings the failing palette back, redo the fix.
await page.keyboard.press('Control+z');
const undone = await page.locator('.wb-contrast li.is-fail').count();
await page.keyboard.press('Control+Shift+z');
const redone = await page.locator('.wb-contrast li.is-fail').count();
return `after fix ${failing} failing; undo ${undone}; redo ${redone}`;
},
});
await shot('builder-fonts', {
query: 'section=builder',
action: async page => {
await page.locator('.wb-card[aria-label="Type"]').scrollIntoViewIfNeeded();
await page.getByRole('button', { name: 'Fine-tune Numbers' }).click();
},
});
// The font browser: live previews, search, detail with sample text and weights.
await shot('font-browser', {
query: 'section=builder',
action: async page => {
await page.locator('.wb-card[aria-label="Type"]').scrollIntoViewIfNeeded();
await page.getByRole('button', { name: /^Post text font/ }).click();
await page.waitForTimeout(2500);
},
});
await shot('font-browser-detail', {
query: 'section=builder',
action: async page => {
await page.locator('.wb-card[aria-label="Type"]').scrollIntoViewIfNeeded();
await page.getByRole('button', { name: /^Post text font/ }).click();
await page.getByLabel('Search Google Fonts').fill('fraunces');
await page.waitForTimeout(600);
await page.getByRole('option', { name: /Fraunces/ }).first().click();
await page.waitForTimeout(2500);
},
});
await shot('font-browser-installed', {
query: 'section=builder',
action: async page => {
await page.locator('.wb-card[aria-label="Type"]').scrollIntoViewIfNeeded();
await page.getByRole('button', { name: /^Names and headings font/ }).click();
await page.getByLabel('Search Google Fonts').fill('space grotesk');
await page.waitForTimeout(500);
await page.getByRole('option', { name: /Space Grotesk/ }).first().click();
await page.getByRole('button', { name: /Download and use/ }).click();
await page.waitForSelector('.wb-sheet', { state: 'detached', timeout: 20000 });
await page.waitForTimeout(1200);
return (await page.locator('.wb-role[aria-label="Names and headings"] .wb-role-name').innerText()).replace(/\s+/g, ' ');
},
});
await shot('font-browser-narrow', {
width: 560, height: 980, query: 'section=builder',
action: async page => {
await page.locator('.wb-card[aria-label="Type"]').scrollIntoViewIfNeeded();
await page.getByRole('button', { name: /^Post text font/ }).click();
await page.getByRole('radio', { name: 'Serif' }).click();
await page.waitForTimeout(2500);
},
});
await shot('appearance-fonts', { query: 'section=themes', action: async page => { await page.locator('[aria-label="Interface"]').first().scrollIntoViewIfNeeded(); } });
await shot('appearance-scopes', { query: 'section=themes', action: async page => { await page.locator('#theme-scopes-h').scrollIntoViewIfNeeded(); } });
await shot('appearance-light-chrome', { query: 'section=themes&theme=paper' });
await browser.close();
console.log(results.join('\n'));