import fs from 'fs/promises'; import path from 'path'; import { chromium } from '@playwright/test'; /** * README * * Usage: * - `pnpm template:screenshot` to generate all screenshots * - `pnpm template:screenshot material-ui` to generate all screenshots for Material-UI templates * - `pnpm template:screenshot order-dashboard` to generate screenshots for file named `order-dashboard.tsx` * - `pnpm template:screenshot material-ui dashboard` to generate screenshots for file named `dashboard.tsx` of Material UI templates * * Note: * - The screenshot with `-dark` suffix is generated if the page has a button with id `toggle-mode` * * * - The screenshot with `-default` suffix is generated if the page has a button with id `toggle-default-theme` * * * - The screenshot with `-default-dark` suffix is generated if the page has both buttons * * Debug: * - Set `chromium.launch({ headless: false })` in line:50 to see the browser */ function sleep(duration: number): Promise { return new Promise((resolve) => { setTimeout(() => { resolve(); }, duration); }); } const host = process.env.DEPLOY_PREVIEW || 'http://localhost:3000'; /** * project key should be `mui.com//*` */ const projects = { 'material-ui': { input: path.join(process.cwd(), 'docs/pages/material-ui/getting-started/templates'), output: 'docs/public/static/screenshots', viewport: { width: 813 * 2, height: 457 * 2 }, }, 'joy-ui': { input: path.join(process.cwd(), 'docs/pages/joy-ui/getting-started/templates'), output: 'docs/public/static/screenshots', viewport: { width: 1600, height: 800 }, }, }; const names = new Set(process.argv.slice(2)); (async () => { // eslint-disable-next-line no-console console.info('Host:', host); const browser = await chromium.launch({ headless: true }); await Promise.all( Object.entries(projects) .filter(([project]) => names.size === 0 || names.has(project)) .map(async ([project, { input, output, viewport }]) => { const page = await browser.newPage({ viewport, reducedMotion: 'reduce', }); names.delete(project); const files = await fs.readdir(input); const urls = files .filter( (file) => !file.startsWith('index') && (names.size === 0 || names.has(file.replace(/\.(js|tsx)$/, ''))), ) .map( (file) => `/${project}/getting-started/templates/${file.replace(/\.(js|tsx)$/, '/')}`, ); async function toggleMode() { await page.locator('css=[data-screenshot="toggle-mode"]').locator('visible=true').click(); } async function captureDarkMode(outputPath: string) { const btn = await page.$('[data-screenshot="toggle-mode"]'); if (btn) { if ((await btn.getAttribute('aria-haspopup')) === 'true') { await toggleMode(); await page.getByRole('menuitem').filter({ hasText: /dark/i }).click(); await page.waitForLoadState('networkidle'); // changing to dark mode might trigger image loading await sleep(100); // give time for image decoding, resizing, rendering await page.screenshot({ path: outputPath, animations: 'disabled' }); await toggleMode(); await page .getByRole('menuitem') .filter({ hasText: /system/i }) .click(); // switch back to light } else if ((await btn.getAttribute('aria-haspopup')) === 'listbox') { await toggleMode(); await page.getByRole('option').filter({ hasText: /dark/i }).click(); await page.waitForLoadState('networkidle'); // changing to dark mode might trigger image loading await sleep(100); // give time for image decoding, resizing, rendering await page.screenshot({ path: outputPath, animations: 'disabled' }); await toggleMode(); await page .getByRole('option') .filter({ hasText: /system/i }) .click(); // switch back to light } else { await toggleMode(); await page.waitForLoadState('networkidle'); // changing to dark mode might trigger image loading await sleep(100); // give time for image decoding, resizing, rendering await page.screenshot({ path: outputPath, animations: 'disabled' }); await toggleMode(); // switch back to light } } } try { await Promise.resolve().then(() => urls.reduce(async (sequence, aUrl) => { await sequence; await page.goto(`${host}${aUrl}?hideFrame=true`, { waitUntil: 'networkidle' }); const filePath = `${output}${aUrl.replace(/\/$/, '')}.jpg`; // eslint-disable-next-line no-console console.info('Saving screenshot to:', filePath); await page.screenshot({ path: filePath, animations: 'disabled' }); await captureDarkMode(filePath.replace('.jpg', '-dark.jpg')); // capture custom theme const toggleTheme = await page.$('[data-screenshot="toggle-default-theme"]'); if (toggleTheme) { await page.click('[data-screenshot="toggle-default-theme"]'); await page.screenshot({ path: filePath.replace('.jpg', '-default.jpg'), animations: 'disabled', }); await captureDarkMode(filePath.replace('.jpg', '-default-dark.jpg')); } return Promise.resolve(); }, Promise.resolve()), ); } catch (error) { console.error(error); } }), ); await browser.close(); })();