Files
searchi/tools/desktop-capture/capture.mjs
T

401 lines
13 KiB
JavaScript
Raw Normal View History

/** Capture the production frontend against a live, real local Rust/Gemma fixture. */
import { mkdir, writeFile } from "node:fs/promises";
import { resolve, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { readFile } from "node:fs/promises";
import { execFileSync } from "node:child_process";
process.env.PLAYWRIGHT_BROWSERS_PATH = "0";
const { chromium } = await import("playwright");
const repository = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
const target = new URL(
process.env.SEARCHI_CAPTURE_URL ?? "http://127.0.0.1:1431/?capture=1",
);
assert(
target.protocol === "http:" &&
["127.0.0.1", "localhost"].includes(target.hostname),
"Capture must use an owned loopback server",
);
assert(
target.searchParams.get("capture") === "1",
"The explicit capture adapter must be enabled",
);
const out = resolve(
repository,
process.env.SEARCHI_CAPTURE_OUTPUT ?? "docs/screenshots",
);
const infoResponse = await fetch(new URL("/capture-info", target), {
signal: AbortSignal.timeout(15000),
});
assert(infoResponse.ok, "Real fixture backend is not ready");
const info = await infoResponse.json();
assert(
info.mode === "readme-capture" && info.ready === true,
"Requires an initialized real-engine capture backend",
);
await mkdir(out, { recursive: true });
const browser = await chromium.launch({
headless: true,
args: [
"--disable-gpu",
"--disable-accelerated-video-decode",
"--disable-webgl",
],
});
const failures = [];
const evidence = [];
const measured = [];
let latestMeasured;
const source = {
base_commit: execFileSync("git", ["rev-parse", "HEAD"], {
cwd: repository,
encoding: "utf8",
}).trim(),
working_tree_dirty:
execFileSync("git", ["status", "--porcelain"], {
cwd: repository,
encoding: "utf8",
}).trim().length > 0,
};
try {
const context = await browser.newContext({
viewport: { width: 1440, height: 1000 },
deviceScaleFactor: 1,
colorScheme: "light",
reducedMotion: "reduce",
});
await context.route("**/*", (route) => {
const url = new URL(route.request().url());
return url.origin === target.origin ||
["data:", "blob:"].includes(url.protocol)
? route.continue()
: route.abort();
});
const page = await context.newPage();
page.on("pageerror", (error) => failures.push(String(error)));
await page.goto(target.href, { waitUntil: "networkidle", timeout: 30000 });
await page.waitForFunction(
() =>
document
.querySelector("#semantic-status")
?.textContent?.includes("Semantic search ready"),
{},
{ timeout: 120000 },
);
await page.evaluate(async () => {
await document.fonts.ready;
});
assert(
await page
.locator(".brand-symbol")
.evaluate((image) => image.complete && image.naturalWidth > 0),
"Brand asset failed to load",
);
async function query(text, debug = false) {
const awaited = page.waitForResponse(
(response) => {
if (
new URL(response.url()).pathname !==
"/capture-api/hybrid_search_measured"
)
return false;
const args = response.request().postDataJSON();
return args?.query === text && Boolean(args.debugPerformance) === debug;
},
{ timeout: 60000 },
);
await page.locator("#query").fill(text);
const response = await awaited;
assert(response.ok(), "Real measured engine query failed");
const body = await response.json();
assert(
Array.isArray(body.results) && body.metrics,
"Measured backend response missing actual results/metrics",
);
latestMeasured = {
query: text,
mode: debug ? "debug" : "basic",
result_count: body.results.length,
metrics: body.metrics,
first_filename: body.results[0]?.filename,
first_source: body.results[0]?.source,
};
measured.push(latestMeasured);
await page.waitForFunction(
(expected) => {
const summary =
document.querySelector("#result-summary")?.textContent ?? "";
return (
document.querySelector("#query")?.value === expected.query &&
summary.includes("KEYWORD + MEANING") &&
document.querySelector("#results .result strong")?.textContent ===
expected.filename &&
document.querySelectorAll("#results .result").length ===
expected.count
);
},
{
query: text,
filename: latestMeasured.first_filename,
count: latestMeasured.result_count,
},
{ timeout: 60000 },
);
assert(
(await page.locator("#results .result").count()) > 0,
`Real query returned no matches: ${text}`,
);
await page.locator("#results .result").first().click();
await page.waitForFunction(
() => !document.querySelector("#preview")?.hidden,
);
if (text === "cancel the supplier partnership") {
assert.equal(
latestMeasured.first_filename,
"Supplier Agreement.pdf",
"Contract query must retrieve the known PDF fixture",
);
assert.equal(
latestMeasured.first_source?.kind,
"page",
"PDF match must retain actual page source",
);
assert.equal(
latestMeasured.first_source?.number,
2,
"Termination clause must be on actual page2",
);
const passage = await page.locator("#preview-text").innerText();
assert.match(
passage,
/sixty|\b60\b/i,
"Actual PDF preview is missing notice period",
);
assert.match(
passage,
/terminat/i,
"Actual PDF preview is missing termination section",
);
const literalWords = ["supplier", "partnership"];
const rowMarks = await page
.locator("#results .result")
.first()
.locator('.snippet mark[data-match="literal"]')
.allTextContents();
const passageMarks = await page
.locator('#preview-text mark[data-match="literal"]')
.allTextContents();
for (const word of literalWords) {
assert(
rowMarks.some((mark) => mark.toLowerCase() === word),
`Result snippet must highlight actual query word ${word}`,
);
assert(
passageMarks.some((mark) => mark.toLowerCase() === word),
`PDF passage must highlight actual query word ${word}`,
);
}
const why = page.locator("#match-reason");
const disclosure = why.locator("xpath=ancestor::details[1]");
assert.equal(
await disclosure.count(),
1,
"Match reason must live in a compact disclosure",
);
assert.equal(
await disclosure.evaluate((element) => element.open),
false,
"Why details should stay collapsed by default",
);
await disclosure.locator(":scope > summary").click();
await why.waitFor({ state: "visible" });
assert.match(
await page.locator("#match-reason").innerText(),
/page\s*2/i,
"Match explanation must use actual PDF page2",
);
await disclosure.locator(":scope > summary").click();
assert.equal(
await disclosure.evaluate((element) => element.open),
false,
"Normal screenshot must preserve collapsed why details",
);
latestMeasured.ui_checks = {
literal_query_marks: true,
actual_page_explanation: true,
why_disclosure_collapsed_by_default: true,
};
}
if (text === "mountains reflected in calm water") {
assert.equal(
latestMeasured.first_filename,
"Mountain Lake.jpg",
"Visual query must retrieve the known licensed lake",
);
assert.equal(
await page.locator("#preview-text mark").count(),
0,
"Meaning-only photo result must not invent word-level alignment",
);
latestMeasured.ui_checks = { meaning_only_image_has_no_text_marks: true };
}
await page.evaluate(
() =>
new Promise((resolve) =>
requestAnimationFrame(() => requestAnimationFrame(resolve)),
),
);
}
async function screenshot(name, queryText) {
assert.deepEqual(failures, [], "Frontend page errors occurred");
const path = resolve(out, `${name}.png`);
await page.screenshot({ path, animations: "disabled", fullPage: false });
const selected = await page.locator("#preview-title").innerText();
assert.equal(
selected,
latestMeasured.first_filename,
"Selected DOM result is not the actual first hybrid result",
);
evidence.push({
file: `${name}.png`,
sha256: createHash("sha256")
.update(await readFile(path))
.digest("hex"),
query: queryText,
mode: latestMeasured.mode,
result_count: latestMeasured.result_count,
selected_filename: selected,
first_source_kind: latestMeasured.first_source?.kind,
first_source_number: latestMeasured.first_source?.number,
theme: await page.evaluate(() =>
matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light",
),
summary: await page.locator("#result-summary").innerText(),
state_assertions: {
semantic_ready: true,
hybrid_final: true,
image_visible: name === "media-search",
actual_worker_inference:
latestMeasured.mode === "debug" &&
latestMeasured.metrics.trace.some(
(span) => span.counters.worker_inference_us > 0,
),
...latestMeasured.ui_checks,
},
});
}
const documentQuery = "cancel the supplier partnership";
await query(documentQuery);
await screenshot("search-light", documentQuery);
await page.emulateMedia({ colorScheme: "dark" });
await screenshot("search-dark", documentQuery);
await page.emulateMedia({ colorScheme: "light" });
const imageQuery = "mountains reflected in calm water";
await query(imageQuery);
await page.waitForFunction(
() => {
const image = document.querySelector("#media-preview img");
return (
image instanceof HTMLImageElement &&
image.complete &&
image.naturalWidth > 0
);
},
{},
{ timeout: 60000 },
);
await screenshot("media-search", imageQuery);
await page.locator("#settings > summary").click();
await page.locator("#performance-toggle").check();
await page.locator("#settings > summary").click();
// Use a genuinely unseen query so this screenshot records real inference,
// rather than attributing a bounded query-vector cache hit to the worker.
const performanceQuery = "how can we cancel the supplier partnership";
await query(performanceQuery, true);
await page.locator("#performance-panel").waitFor({ state: "visible" });
await page.locator("#performance-panel summary").click();
await page.waitForFunction(
() =>
document
.querySelector("#performance-detail")
?.textContent?.includes("hybrid") ||
document
.querySelector("#performance-detail")
?.textContent?.includes("Hybrid"),
);
assert(
latestMeasured.metrics.trace?.some(
(span) => span.counters.worker_inference_us > 0,
),
"Detailed trace did not record real worker inference",
);
await page
.locator("#performance-detail .performance-span")
.filter({ hasText: "worker inference" })
.first()
.scrollIntoViewIfNeeded();
await screenshot("performance-details", performanceQuery);
const semanticResponse = await context.request.post(
new URL("/capture-api/semantic_status", target).href,
{
headers: { "X-Searchi-Capture-Token": info.token, Origin: target.origin },
data: {},
},
);
assert(semanticResponse.ok(), "Actual semantic status unavailable");
const semantic = await semanticResponse.json();
assert.equal(
semantic.worker_info?.model,
"google/embeddinggemma-2",
"Capture requires the actual Google model",
);
assert.equal(
semantic.worker_info?.threads,
1,
"Capture inference requires one CPU thread",
);
assert.equal(
semantic.worker_info?.device,
"cpu",
"Capture worker is not CPU",
);
await writeFile(
resolve(out, "capture-manifest.json"),
JSON.stringify(
{
kind: "browser-rendered-production-ui-real-cpu-engine",
source,
viewport: { width: 1440, height: 1000 },
runner: { playwright: "1.64.0", chromium: browser.version() },
backend: {
mode: info.mode,
model: semantic.worker_info.model,
device: semantic.worker_info.device,
runtime: semantic.worker_info.runtime,
threads: semantic.worker_info.threads,
dimensions: semantic.worker_info.dimensions,
ready: info.ready,
rust_build_profile: info.rust_build_profile,
},
cpu_flags: [
"--disable-gpu",
"--disable-accelerated-video-decode",
"--disable-webgl",
],
native_window_chrome: false,
native_visual_qa_complete: false,
measured_queries: measured,
screenshots: evidence,
},
null,
2,
) + "\n",
);
console.log(
`Captured ${evidence.length} real-engine UI screenshots in ${out}`,
);
await context.close();
} finally {
await browser.close();
}