mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-26 18:31:34 +01:00
Captures the mixed-stream fixture (two plain bootstrap lines followed by
OpenCode JSONL) rendered through the real process adapter module via a
dev-only route at /tests/screenshots/process-opencode-transcript (gated
on import.meta.env.DEV so it tree-shakes out of production builds). The
companion script at
tools/process-opencode-transcript-screenshots/capture.mjs boots vite,
navigates to the route, and writes one PNG per scenario:
01-before-raw-jsonl-passthrough — previous pass-through parser dumping
OpenCode JSONL as raw stdout
02-after-rich-opencode-transcript — same stream with detection: plain
lines stay raw, JSONL renders as
the rich transcript
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
121 lines
3.8 KiB
JavaScript
121 lines
3.8 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Capture before/after screenshots of the process-adapter OpenCode transcript
|
|
* rendering for PR review.
|
|
*
|
|
* Boots the UI Vite dev server and uses Playwright (chromium) to render the
|
|
* `/tests/screenshots/process-opencode-transcript` dev-only route (see
|
|
* `ui/src/pages/__screenshots__/ProcessOpencodeTranscriptScreenshots.tsx`).
|
|
* One PNG per `[data-screenshot-id]` section is written to
|
|
* `docs/screenshots/process-opencode-transcript/`.
|
|
*
|
|
* Usage:
|
|
* node tools/process-opencode-transcript-screenshots/capture.mjs
|
|
*
|
|
* Idempotent — overwrites existing PNGs.
|
|
*/
|
|
import { spawn } from "node:child_process";
|
|
import { mkdirSync } from "node:fs";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { chromium } from "@playwright/test";
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
const REPO_ROOT = path.resolve(__dirname, "..", "..");
|
|
const UI_DIR = path.join(REPO_ROOT, "ui");
|
|
const OUTPUT_DIR = path.join(REPO_ROOT, "docs", "screenshots", "process-opencode-transcript");
|
|
const PORT = Number(process.env.SCREENSHOT_PORT ?? 5173);
|
|
const BASE_URL = `http://127.0.0.1:${PORT}`;
|
|
const SCREENSHOT_PATH = "/tests/screenshots/process-opencode-transcript";
|
|
|
|
const SECTIONS = [
|
|
"01-before-raw-jsonl-passthrough",
|
|
"02-after-rich-opencode-transcript",
|
|
];
|
|
|
|
async function waitForServer(url, timeoutMs = 60_000) {
|
|
const deadline = Date.now() + timeoutMs;
|
|
while (Date.now() < deadline) {
|
|
try {
|
|
const res = await fetch(url, { method: "GET" });
|
|
if (res.ok || res.status === 404) return;
|
|
} catch {
|
|
// not ready
|
|
}
|
|
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
}
|
|
throw new Error(`Server at ${url} never became ready`);
|
|
}
|
|
|
|
async function startDevServer() {
|
|
const proc = spawn(
|
|
"pnpm",
|
|
["vite", "--port", String(PORT), "--host", "127.0.0.1", "--strictPort"],
|
|
{
|
|
cwd: UI_DIR,
|
|
env: { ...process.env, NODE_ENV: "development" },
|
|
stdio: ["ignore", "pipe", "pipe"],
|
|
},
|
|
);
|
|
proc.stdout?.on("data", (chunk) => {
|
|
process.stdout.write(`[vite] ${chunk}`);
|
|
});
|
|
proc.stderr?.on("data", (chunk) => {
|
|
process.stderr.write(`[vite] ${chunk}`);
|
|
});
|
|
return proc;
|
|
}
|
|
|
|
async function captureScreenshots() {
|
|
mkdirSync(OUTPUT_DIR, { recursive: true });
|
|
|
|
const browser = await chromium.launch();
|
|
try {
|
|
const context = await browser.newContext({
|
|
viewport: { width: 1280, height: 1600 },
|
|
deviceScaleFactor: 2,
|
|
});
|
|
const page = await context.newPage();
|
|
page.on("console", (msg) => {
|
|
if (msg.type() === "error") {
|
|
console.error(`[browser console error] ${msg.text()}`);
|
|
}
|
|
});
|
|
await page.goto(`${BASE_URL}${SCREENSHOT_PATH}`, { waitUntil: "networkidle" });
|
|
await page.waitForSelector(`[data-screenshot-id="${SECTIONS[0]}"]`, { timeout: 15_000 });
|
|
|
|
for (const section of SECTIONS) {
|
|
const locator = page.locator(`[data-screenshot-id="${section}"]`);
|
|
await locator.scrollIntoViewIfNeeded();
|
|
await page.waitForTimeout(200);
|
|
const filename = path.join(OUTPUT_DIR, `${section}.png`);
|
|
await locator.screenshot({ path: filename });
|
|
console.log(`Wrote ${path.relative(REPO_ROOT, filename)}`);
|
|
}
|
|
} finally {
|
|
await browser.close();
|
|
}
|
|
}
|
|
|
|
async function main() {
|
|
console.log(`[capture] booting vite dev server on ${BASE_URL}`);
|
|
const server = await startDevServer();
|
|
try {
|
|
await waitForServer(BASE_URL);
|
|
console.log(`[capture] vite ready, capturing screenshots`);
|
|
await captureScreenshots();
|
|
} finally {
|
|
if (server.exitCode === null) {
|
|
server.kill("SIGTERM");
|
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
if (server.exitCode === null) server.kill("SIGKILL");
|
|
}
|
|
}
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error(err);
|
|
process.exit(1);
|
|
});
|