Ctrl K
ComunidadGuías…

take-screenshots.mjs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
/**
 * 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 {}
}