Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
130 lines
5.8 KiB
JavaScript
130 lines
5.8 KiB
JavaScript
// Captura a interface do Guará (explorer, vista de pastas e configurações) nos temas
|
|
// claro/escuro e nos idiomas pt/en. Chamado por scripts/screenshots.sh:
|
|
// node shots.mjs <binário guara> <imagem de teste> <dir-saída>
|
|
import { execFileSync, spawn } from "node:child_process";
|
|
import { homedir } from "node:os";
|
|
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
|
|
const [bin, image, out] = process.argv.slice(2);
|
|
const core = process.env.PUPPETEER_CORE || `${homedir()}/.claude/tools/node_modules/puppeteer-core/lib/esm/puppeteer/puppeteer-core.js`;
|
|
const { default: puppeteer } = await import(core);
|
|
const chrome = process.env.CHROME || execFileSync("bash", ["-c", `ls -1d "$HOME"/.cache/puppeteer/chrome/*/chrome-linux64/chrome | sort -V | tail -1`], { encoding: "utf8" }).trim();
|
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
|
|
// Dispositivo fictício: a lista real do computador não deve aparecer nas capturas.
|
|
const FAKE_DEVICES = [{ path: "/dev/nvme0n1", name: "SSD interno", kind: "NVMe", size: 954 * 1024 ** 3, readable: false, mounted_at: [], removable: false }];
|
|
const LABEL = "Pendrive 64 GB";
|
|
|
|
const variants = [
|
|
{ theme: "dark", lang: "pt" }, { theme: "dark", lang: "en" },
|
|
{ theme: "light", lang: "pt" }, { theme: "light", lang: "en" },
|
|
];
|
|
|
|
// Sobe `guara ui` com config isolada (sessões e ajustes próprios) e devolve a URL com o token.
|
|
async function startServer(theme, lang) {
|
|
const cfg = mkdtempSync(join(tmpdir(), "guara-shots-"));
|
|
mkdirSync(cfg, { recursive: true });
|
|
writeFileSync(join(cfg, "settings.json"), JSON.stringify({ theme, lang, notify: true, maps: false, updates: false }));
|
|
const child = spawn(bin, ["ui", "--no-open", "--port", "0"], { env: { ...process.env, GUARA_CONFIG_DIR: cfg }, stdio: ["ignore", "ignore", "pipe"] });
|
|
const url = await new Promise((resolve, reject) => {
|
|
let buf = "";
|
|
child.stderr.on("data", (d) => { buf += d; const m = buf.match(/http:\/\/\S+/); if (m) resolve(m[0]); });
|
|
child.on("exit", () => reject(new Error("guara ui saiu antes de subir")));
|
|
setTimeout(() => reject(new Error("timeout subindo guara ui")), 20000);
|
|
});
|
|
return { url, stop: () => { child.kill(); rmSync(cfg, { recursive: true, force: true }); } };
|
|
}
|
|
|
|
async function newPage(browser, url, w = 1600, h = 940) {
|
|
const page = await browser.newPage();
|
|
await page.setViewport({ width: w, height: h, deviceScaleFactor: 1 });
|
|
await page.setRequestInterception(true);
|
|
page.on("request", (req) => {
|
|
if (req.url().includes("/rpc/devices")) {
|
|
req.respond({ status: 200, contentType: "application/json", body: JSON.stringify(FAKE_DEVICES) });
|
|
} else req.continue();
|
|
});
|
|
await page.goto(url, { waitUntil: "networkidle0" });
|
|
await sleep(1200);
|
|
return page;
|
|
}
|
|
|
|
// Abre a imagem de teste, faz a varredura profunda e espera terminar.
|
|
async function scanImage(page) {
|
|
await page.click("#openImageBtn");
|
|
await sleep(400);
|
|
await page.type("#dlg input[name=v]", image);
|
|
await page.click("#dlg button[value=ok]");
|
|
await sleep(1200);
|
|
await page.click("#goDeep");
|
|
await page.waitForFunction(() => document.querySelector(".xp-tile, .xp-lrow"), { timeout: 120000 });
|
|
await page.waitForFunction(() => !document.querySelector("#progressArea .progress-card"), { timeout: 120000 });
|
|
await sleep(2500); // miniaturas e autosave da sessão
|
|
}
|
|
|
|
const relabel = (page) => page.evaluate((t) => {
|
|
const sub = document.querySelector("#scanSub"); if (sub) sub.innerHTML = `<span class="mono">${t}</span>`;
|
|
}, LABEL);
|
|
|
|
const browser = await puppeteer.launch({ executablePath: chrome, args: ["--no-sandbox"] });
|
|
try {
|
|
for (const { theme, lang } of variants) {
|
|
const srv = await startServer(theme, lang);
|
|
try {
|
|
// 1ª rodada: só para deixar uma sessão salva em "Recentes" (a 2ª rodada cria a outra).
|
|
let page = await newPage(browser, srv.url);
|
|
await scanImage(page);
|
|
await page.close();
|
|
|
|
page = await newPage(browser, srv.url);
|
|
await page.evaluate(() => { try { localStorage.setItem("guara-view", "grid"); } catch {} });
|
|
await page.reload({ waitUntil: "networkidle0" });
|
|
await sleep(1000);
|
|
await scanImage(page);
|
|
await relabel(page);
|
|
|
|
// Explorador em grade, com a primeira foto marcada.
|
|
const chk = await page.$(".xp-tile .xp-chk");
|
|
if (chk) await chk.click(); else await (await page.$(".xp-tile")).click();
|
|
await sleep(800);
|
|
await page.screenshot({ path: `${out}/explorer-${theme}-${lang}.png` });
|
|
console.log("ok", `explorer-${theme}-${lang}`);
|
|
|
|
if (theme === "light") {
|
|
// Vista de pastas: pasta DCIM marcada e selecionada.
|
|
await page.click('button[data-view="folders"]');
|
|
await sleep(1000);
|
|
const dcim = await page.$('.xp-lrow.xp-dir[data-dir="0"]');
|
|
await (await dcim.$(".xp-chk")).click();
|
|
await sleep(400);
|
|
await dcim.click({ offset: { x: 200, y: 20 } });
|
|
await sleep(800);
|
|
await relabel(page);
|
|
await page.screenshot({ path: `${out}/folders-${theme}-${lang}.png` });
|
|
console.log("ok", `folders-${theme}-${lang}`);
|
|
await page.click('button[data-view="grid"]');
|
|
await sleep(500);
|
|
}
|
|
|
|
if (theme === "light") {
|
|
// Diálogo de configurações, recortado com a moldura borrada ao redor.
|
|
await page.click("#settingsBtn");
|
|
await sleep(900);
|
|
const box = await (await page.$("#dlg")).boundingBox();
|
|
const pad = 24;
|
|
await page.screenshot({
|
|
path: `${out}/settings-language-${lang}.png`,
|
|
clip: { x: Math.max(0, box.x - pad), y: Math.max(0, box.y - pad), width: box.width + 2 * pad, height: box.height + 2 * pad },
|
|
});
|
|
console.log("ok", `settings-language-${lang}`);
|
|
}
|
|
} finally {
|
|
srv.stop();
|
|
}
|
|
}
|
|
} finally {
|
|
await browser.close();
|
|
}
|