#!/usr/bin/env node import fs from "node:fs/promises"; import path from "node:path"; import process from "node:process"; import { chromium } from "../apps/web/node_modules/playwright/index.mjs"; const routes = [ "/", "/waterbodies", "/records", "/report", "/status", "/media", "/rules", "/waterbodies/р-вьюнок", "/fish/pike", "/admin", "/admin/moderation", "/admin/external-sources", "/admin/media", ]; const viewports = [ [320, 800], [390, 844], [768, 900], [1280, 900], ]; const themes = ["system", "light", "dark"]; function usage() { console.log("Usage: node scripts/capture-visual-matrix.mjs [--base-url URL] [--output DIR]"); } function parseArgs(argv) { const args = { baseUrl: process.env.WEB_URL || "http://127.0.0.1:4321", output: "/tmp/rf4-visual-matrix" }; for (let index = 0; index < argv.length; index += 1) { if (argv[index] === "--help" || argv[index] === "-h") { usage(); process.exit(0); } if (argv[index] === "--base-url") args.baseUrl = argv[++index]; else if (argv[index] === "--output") args.output = argv[++index]; else throw new Error(`unknown argument: ${argv[index]}`); } return args; } function safeName(value) { return value.replace(/^\//, "home").replaceAll("/", "-").replace(/[^\p{L}\p{N}._-]+/gu, "-"); } const { baseUrl, output } = parseArgs(process.argv.slice(2)); const outputDir = path.resolve(output); await fs.mkdir(outputDir, { recursive: true }); const browser = await chromium.launch({ headless: true }); const page = await browser.newPage(); const manifest = []; try { for (const theme of themes) { await page.context().clearCookies(); if (theme !== "system") { await page.context().addCookies([{ name: "rf4-theme", value: theme, url: baseUrl }]); } await page.emulateMedia({ colorScheme: theme === "system" ? "light" : theme }); for (const [width, height] of viewports) { await page.setViewportSize({ width, height }); for (const route of routes) { await page.goto(new URL(route, baseUrl).toString(), { waitUntil: "networkidle" }); const state = await page.evaluate(() => ({ clientWidth: document.documentElement.clientWidth, scrollWidth: document.documentElement.scrollWidth, theme: document.documentElement.dataset.theme || "system", })); const filename = `${theme}-${width}x${height}-${safeName(route)}.png`; await page.screenshot({ path: path.join(outputDir, filename), fullPage: true }); manifest.push({ theme, width, height, route, filename, ...state }); } } } } finally { await browser.close(); } await fs.writeFile( path.join(outputDir, "manifest.json"), `${JSON.stringify({ baseUrl, count: manifest.length, items: manifest }, null, 2)}\n`, "utf8", ); const overflow = manifest.filter((item) => item.scrollWidth > item.clientWidth); console.log(`captured ${manifest.length} screenshots in ${outputDir}`); console.log(`document overflow: ${overflow.length}`); if (overflow.length) process.exitCode = 1;