take-screenshots.mjs
Rama main · réplica de lectura del repositorio original.
/**
* Take screenshots + measure computed styles of the DSH Web Tools settings
* page with the provider modal open, via Edge CDP (DevTools Protocol).
*
* Uses an isolated --user-data-dir so it never touches the user's own browser.
*/
import { spawn } from "node:child_process";
import { writeFileSync, mkdirSync, existsSync } from "node:fs";
import { rmSync } from "node:fs";
import { pathToFileURL } from "node:url";
const EDGE = "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe";
const OUT = "D:\\web\\dsh-plugins\\dsh-web-tools\\screenshots";
mkdirSync(OUT, { recursive: true });
const PORT = 9333;
const tmpDir = `${process.env.TEMP}\\edge-dsh-shot-${Date.now()}`;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
class CDP {
constructor(wsUrl) {
this.wsUrl = wsUrl;
this.msgId = 0;
this.pending = new Map();
this.events = [];
}
async connect() {
this.ws = new WebSocket(this.wsUrl);
this.ws.addEventListener("message", (ev) => {
const msg = JSON.parse(ev.data.toString());
if (msg.id) {
const cb = this.pending.get(msg.id);
if (cb) {
this.pending.delete(msg.id);
cb(msg);
}
} else {
this.events.push(msg);
}
});
await new Promise((resolve, reject) => {
this.ws.addEventListener("open", resolve);
this.ws.addEventListener("error", reject);
});
}
async send(method, params = {}) {
const id = ++this.msgId;
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
this.pending.delete(id);
reject(new Error(`CDP timeout: ${method}`));
}, 30000);
this.pending.set(id, (msg) => {
clearTimeout(timer);
if (msg.error) reject(new Error(`${method}: ${msg.error.message}`));
else resolve(msg.result);
});
this.ws.send(JSON.stringify({ id, method, params }));
});
}
/** Evaluate JS in the page and return the value (awaitPromise). */
async eval(expression) {
const r = await this.send("Runtime.evaluate", {
expression,
awaitPromise: true,
returnByValue: true,
userGesture: true,
});
if (r.exceptionDetails) throw new Error("eval exception: " + JSON.stringify(r.exceptionDetails).slice(0, 500));
return r.result?.value;
}
close() {
try { this.ws.close(); } catch {}
}
}
/** Wait for a JS condition to become truthy. */
async function waitFor(cdp, expression, timeoutMs = 30000, intervalMs = 500) {
const start = Date.now();
while (Date.now() - start < timeoutMs) {
try {
const v = await cdp.eval(expression);
if (v === true) return;
} catch {}
await sleep(intervalMs);
}
throw new Error("waitFor timed out: " + expression);
}
let edge = null;
try {
edge = spawn(EDGE, [
"--headless=new",
"--disable-gpu",
"--no-sandbox",
"--disable-software-rasterizer",
`--user-data-dir=${tmpDir}`,
"--no-first-run",
"--disable-sync",
"--disable-default-apps",
"--disable-extensions",
`--remote-debugging-port=${PORT}`,
"--window-size=1440,900",
"about:blank",
], { stdio: "ignore" });
// Wait for the debug endpoint
let version;
for (let i = 0; i < 40; i++) {
try {
const resp = await fetch(`http://127.0.0.1:${PORT}/json/version`, { signal: AbortSignal.timeout(2000) });
version = await resp.json();
break;
} catch {
await sleep(500);
}
}
if (!version) throw new Error("CDP endpoint never became ready");
console.log("Connected to Edge:", version.Browser);
// Use a page target (Page.* commands need a page, not the browser target)
const targetsResp = await fetch(`http://127.0.0.1:${PORT}/json/list`);
const targets = await targetsResp.json();
const pageTarget = targets.find((t) => t.type === "page");
if (!pageTarget) throw new Error("No page target found");
console.log("Page target:", pageTarget.url || "(blank)");
const cdp = new CDP(pageTarget.webSocketDebuggerUrl);
await cdp.connect();
await cdp.send("Page.enable");
await cdp.send("Runtime.enable");
// Navigate to settings
console.log("Navigate /settings ...");
const navPromise = cdp.send("Page.navigate", { url: "http://127.0.0.1:3080/settings" });
// Wait for the load event
await new Promise((resolve) => {
const handler = (ev) => {
if (ev.method === "Page.loadEventFired") {
cdp.ws.removeEventListener("message", handler);
resolve();
}
};
cdp.ws.addEventListener("message", handler);
// Timeout guard
setTimeout(resolve, 30000);
});
console.log("Page load event fired.");
await cdp.send("Emulation.setDeviceMetricsOverride", { width: 1440, height: 900, deviceScaleFactor: 1, mobile: false });
// Wait for the page and web-tools section to render (longer timeout)
await waitFor(cdp, `!!document.querySelector('.dswt-group-card, .dswt-settings-row') || document.body.innerText.indexOf('网页搜索') !== -1`, 60000);
console.log("Web tools UI rendered.");
// Give the settings section time to hydrate provider rows
await sleep(2500);
const info = await cdp.eval(`(() => {
const rows = [...document.querySelectorAll('.dswt-settings-row.clickable')];
const providerRows = rows.filter(r => r.querySelector('img'));
return {
totalRows: rows.length,
providerRows: providerRows.map(r => (r.querySelector('img')||{}).alt || r.textContent.slice(0,40)),
hasBody: document.body.innerText.length,
};
})()`);
console.log("Page info:", JSON.stringify(info, null, 2));
// Screenshot: overview at 1440x900
let shot = await cdp.send("Page.captureScreenshot", { format: "png" });
writeFileSync(`${OUT}\\overview_1440x900.png`, Buffer.from(shot.data, "base64"));
console.log("Saved overview_1440x900.png");
// Click the first provider row to open the modal
const clickResult = await cdp.eval(`(() => {
const rows = [...document.querySelectorAll('.dswt-settings-row.clickable')];
const providerRow = rows.find(r => r.querySelector('img'));
if (!providerRow) return 'NO_PROVIDER_ROW';
providerRow.click();
return 'clicked';
})()`);
console.log("Click provider row:", clickResult);
await waitFor(cdp, `!!document.querySelector('.dswt-modal-content, .wt-modal-content')`, 20000);
console.log("Modal open.");
// Collapsed modal screenshot
await sleep(800);
shot = await cdp.send("Page.captureScreenshot", { format: "png" });
writeFileSync(`${OUT}\\brave_detail_collapsed_1440x900.png`, Buffer.from(shot.data, "base64"));
console.log("Saved brave_detail_collapsed_1440x900.png");
// Measure computed styles inside the modal (pixel-level audit)
const styles = await cdp.eval(`(() => {
const pick = (sel, props) => {
const el = document.querySelector(sel);
if (!el) return null;
const cs = getComputedStyle(el);
const out = { sel };
for (const p of props) out[p] = cs[p];
return out;
};
const content = document.querySelector('.dswt-modal-content') || document.querySelector('.wt-modal-content');
const dialog = document.querySelector('.dswt-modal-dialog') || document.querySelector('.wt-modal-dialog');
return {
dialog: pick('.dswt-modal-dialog, .wt-modal-dialog', ['width','maxHeight']),
content: pick('.dswt-modal-content, .wt-modal-content', ['paddingTop','paddingRight','paddingBottom','paddingLeft']),
header: pick('.dswt-provider-header', ['marginBottom','gap']),
logo: pick('.dswt-provider-logo', ['width','height','borderRadius']),
name: pick('.dswt-provider-name', ['fontSize','fontWeight','lineHeight']),
meta: pick('.dswt-provider-meta', ['fontSize','fontWeight','color']),
groupTitle: pick('.dswt-group-title', ['fontSize','fontWeight','color']),
settingsRow: pick('.dswt-settings-row', ['minHeight','paddingTop','paddingLeft','paddingRight','paddingBottom']),
segmentedTrack: pick('.dswt-segmented-track', ['height','padding','borderRadius','backgroundColor','borderTopWidth','borderTopColor']),
segmentedSelected: (() => {
const el = document.querySelector('.dswt-segmented-btn.selected');
if (!el) return null;
const cs = getComputedStyle(el);
return { sel: '.dswt-segmented-btn.selected', fontSize: cs.fontSize, fontWeight: cs.fontWeight, borderTopLeftRadius: cs.borderTopLeftRadius, backgroundColor: cs.backgroundColor, boxShadow: cs.boxShadow };
})(),
advancedSurface: pick('.dswt-advanced-surface', ['backgroundColor','borderRadius','paddingTop','paddingLeft','paddingRight','paddingBottom','borderTopColor','marginTop']),
searchCardInner: pick('.dswt-search-card-inner', ['paddingTop','paddingRight','paddingBottom','paddingLeft']),
prefDesc: pick('.dswt-pref-desc', ['fontSize','lineHeight','color']),
};
})()`);
console.log("=== MEASURED STYLES (collapsed) ===");
console.log(JSON.stringify(styles, null, 2));
// Expand advanced params
const expandResult = await cdp.eval(`(() => {
const btn = document.querySelector('.dswt-advanced-btn');
if (btn) { btn.click(); return 'clicked .dswt-advanced-btn'; }
return 'NO_ADVANCED_BTN';
})()`);
console.log("Expand advanced:", expandResult);
await sleep(600);
shot = await cdp.send("Page.captureScreenshot", { format: "png" });
writeFileSync(`${OUT}\\brave_detail_advanced_1440x900.png`, Buffer.from(shot.data, "base64"));
console.log("Saved brave_detail_advanced_1440x900.png");
// Measure advanced surface now that it's expanded
const advanced = await cdp.eval(`(() => {
const el = document.querySelector('.dswt-advanced-surface');
if (!el) return null;
const cs = getComputedStyle(el);
return { backgroundColor: cs.backgroundColor, borderRadius: cs.borderRadius,
paddingTop: cs.paddingTop, paddingLeft: cs.paddingLeft, paddingRight: cs.paddingRight, paddingBottom: cs.paddingBottom,
marginTop: cs.marginTop, borderTopColor: cs.borderTopColor, borderTopWidth: cs.borderTopWidth };
})()`);
console.log("=== ADVANCED SURFACE (expanded) ===");
console.log(JSON.stringify(advanced, null, 2));
// Close modal
await cdp.eval(`(() => {
const btn = document.querySelector('.dswt-modal-close-btn');
if (btn) { btn.click(); return true; }
return false;
})()`);
await sleep(600);
// Screenshot overview at 1920x1080
await cdp.send("Emulation.setDeviceMetricsOverride", { width: 1920, height: 1080, deviceScaleFactor: 1, mobile: false });
await sleep(600);
shot = await cdp.send("Page.captureScreenshot", { format: "png" });
writeFileSync(`${OUT}\\overview_1920x1080.png`, Buffer.from(shot.data, "base64"));
console.log("Saved overview_1920x1080.png");
// Reopen modal for 1080p modal shot
await cdp.eval(`(() => {
const rows = [...document.querySelectorAll('.dswt-settings-row.clickable')];
const providerRow = rows.find(r => r.querySelector('img'));
if (providerRow) { providerRow.click(); return true; }
return false;
})()`);
await waitFor(cdp, `!!document.querySelector('.dswt-modal-content, .wt-modal-content')`, 20000);
await sleep(800);
shot = await cdp.send("Page.captureScreenshot", { format: "png" });
writeFileSync(`${OUT}\\brave_detail_collapsed_1920x1080.png`, Buffer.from(shot.data, "base64"));
console.log("Saved brave_detail_collapsed_1920x1080.png");
await cdp.eval(`(() => {
const btn = document.querySelector('.dswt-advanced-btn');
if (btn) { btn.click(); return true; }
return false;
})()`);
await sleep(500);
shot = await cdp.send("Page.captureScreenshot", { format: "png" });
writeFileSync(`${OUT}\\brave_detail_advanced_1920x1080.png`, Buffer.from(shot.data, "base64"));
console.log("Saved brave_detail_advanced_1920x1080.png");
cdp.close();
console.log("ALL SCREENSHOTS DONE");
} catch (e) {
console.error("FATAL:", e);
process.exitCode = 1;
} finally {
if (edge && !edge.killed) edge.kill();
try { rmSync(tmpDir, { recursive: true, force: true }); } catch {}
}