#!/usr/bin/env node // Screenshot a real product screen for the ad. // // node capture.cjs --url http://localhost:3000/menu --out a/phone.png \ // --width 390 --height 844 --dpr 3 --selector "#menu" \ // --hide "nextjs-portal,.cookie-banner" --storage "tour-seen=1" \ // --click "text=House Double" --wait 800 // // --selector screenshot one element (tall content comes out full height) // --full full-page screenshot instead of the viewport // --hide comma-separated selectors to hide (dev overlays, chat widgets) // --storage key=value for localStorage before load (repeatable), e.g. to skip tours // --click Playwright selector to click before the shot (repeatable, in order) // --wait extra ms after fonts and images settle (default 600) // --channel chrome (default) or chromium const { chromium } = require("playwright-core"); const argv = process.argv.slice(2); const all = (k) => argv.flatMap((a, i) => (a === `--${k}` ? [argv[i + 1]] : [])); const one = (k, d) => all(k)[0] ?? d; const flag = (k) => argv.includes(`--${k}`); (async () => { const url = one("url"); const out = one("out"); if (!url || !out) throw new Error("usage: capture.cjs --url --out [options]"); const browser = await chromium.launch({ channel: one("channel", "chrome") }); const context = await browser.newContext({ viewport: { width: Number(one("width", 1440)), height: Number(one("height", 900)) }, deviceScaleFactor: Number(one("dpr", 2)), }); const storage = all("storage").map((kv) => kv.split(/=(.*)/s).slice(0, 2)); if (storage.length) { await context.addInitScript((pairs) => { for (const [k, v] of pairs) try { localStorage.setItem(k, v); } catch {} }, storage); } const p = await context.newPage(); await p.goto(url, { waitUntil: "networkidle", timeout: 120000 }); const settle = async () => { await p.evaluate(async () => { await document.fonts.ready; await Promise.all([...document.images].map((i) => (i.complete ? 0 : new Promise((r) => { i.onload = i.onerror = r; })))); }); }; await settle(); const hide = one("hide", "nextjs-portal"); if (hide) await p.addStyleTag({ content: `${hide} { display: none !important; }` }); for (const sel of all("click")) { await p.locator(sel).last().click(); await p.waitForTimeout(600); } await settle(); await p.mouse.move(5, 5); // park the pointer so no hover states leak into the shot await p.waitForTimeout(Number(one("wait", 600))); const selector = one("selector"); if (selector) await p.locator(selector).first().screenshot({ path: out }); else await p.screenshot({ path: out, fullPage: flag("full") }); console.log(`wrote ${out}`); await browser.close(); })().catch((e) => { console.error(e.message || e); process.exit(1); });