Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from bundle
Inject DOM or CSS into live pages with Playwright and screenshot the result before editing source code.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
scripts/capture-with-injection.js
1#!/usr/bin/env node23import { mkdir } from "node:fs/promises";4import path from "node:path";5import { chromium } from "playwright";67function usage(exitCode = 1) {8const message = [9"Usage: node scripts/capture-with-injection.js --url <url> --out <file> [options]",10"",11"Options:",12" --script <file> Inject a browser-side JavaScript file after load",13" --css <file> Inject a CSS file after load",14" --width <px> Viewport width (default: 1440)",15" --height <px> Viewport height (default: 2200)",16" --device-scale-factor <n> Device scale factor (default: 1)",17" --wait-until <event> domcontentloaded | load | networkidle (default: networkidle)",18" --wait-for-selector <sel> Wait for a selector before injections",19" --wait-for-text <text> Wait for visible text before injections",20" --after-wait-ms <ms> Wait after injections before screenshot",21" --mobile Enable mobile/touch page settings",22" --full-page Capture a full-page screenshot",23" --help Show this help",24].join("\n");25console.error(message);26process.exit(exitCode);27}2829function resolveInput(value) {30return path.resolve(process.cwd(), value);31}3233function readFlagValue(args, index, name) {34const value = args[index + 1];35if (!value || value.startsWith("--")) {36throw new Error(`Missing value for ${name}`);37}38return value;39}4041async function main() {42const args = process.argv.slice(2);43const options = {44url: "",45out: "",46width: 1440,47height: 2200,48deviceScaleFactor: 1,49waitUntil: "networkidle",50waitForSelector: "",51waitForText: "",52afterWaitMs: 0,53mobile: false,54fullPage: false,55scripts: [],56cssFiles: [],57};5859for (let index = 0; index < args.length; index += 1) {60const arg = args[index];61switch (arg) {62case "--url":63options.url = readFlagValue(args, index, arg);64index += 1;65break;66case "--out":67options.out = resolveInput(readFlagValue(args, index, arg));68index += 1;69break;70case "--script":71options.scripts.push(resolveInput(readFlagValue(args, index, arg)));72index += 1;73break;74case "--css":75options.cssFiles.push(resolveInput(readFlagValue(args, index, arg)));76index += 1;77break;78case "--width":79options.width = Number.parseInt(readFlagValue(args, index, arg), 10);80index += 1;81break;82case "--height":83options.height = Number.parseInt(readFlagValue(args, index, arg), 10);84index += 1;85break;86case "--device-scale-factor":87options.deviceScaleFactor = Number.parseFloat(readFlagValue(args, index, arg));88index += 1;89break;90case "--wait-until":91options.waitUntil = readFlagValue(args, index, arg);92index += 1;93break;94case "--wait-for-selector":95options.waitForSelector = readFlagValue(args, index, arg);96index += 1;97break;98case "--wait-for-text":99options.waitForText = readFlagValue(args, index, arg);100index += 1;101break;102case "--after-wait-ms":103options.afterWaitMs = Number.parseInt(readFlagValue(args, index, arg), 10);104index += 1;105break;106case "--mobile":107options.mobile = true;108break;109case "--full-page":110options.fullPage = true;111break;112case "--help":113usage(0);114break;115default:116throw new Error(`Unknown argument: ${arg}`);117}118}119120if (!options.url || !options.out) {121usage(1);122}123124if (!Number.isFinite(options.width) || !Number.isFinite(options.height)) {125throw new Error("Viewport width and height must be numbers.");126}127128await mkdir(path.dirname(options.out), { recursive: true });129130const browser = await chromium.launch({ headless: true });131try {132const page = await browser.newPage({133viewport: { width: options.width, height: options.height },134deviceScaleFactor: options.deviceScaleFactor,135isMobile: options.mobile,136hasTouch: options.mobile,137});138139await page.goto(options.url, { waitUntil: options.waitUntil });140141if (options.waitForSelector) {142await page.waitForSelector(options.waitForSelector);143}144145if (options.waitForText) {146await page.getByText(options.waitForText).first().waitFor();147}148149for (const cssPath of options.cssFiles) {150await page.addStyleTag({ path: cssPath });151}152153for (const scriptPath of options.scripts) {154await page.addScriptTag({ path: scriptPath });155}156157if (options.afterWaitMs > 0) {158await page.waitForTimeout(options.afterWaitMs);159}160161await page.screenshot({162path: options.out,163fullPage: options.fullPage,164});165166const payload = {167title: await page.title(),168url: page.url(),169output: options.out,170viewport: {171width: options.width,172height: options.height,173deviceScaleFactor: options.deviceScaleFactor,174mobile: options.mobile,175},176injections: {177scripts: options.scripts,178cssFiles: options.cssFiles,179},180};181182console.log(JSON.stringify(payload, null, 2));183} finally {184await browser.close();185}186}187188main().catch((error) => {189console.error(error instanceof Error ? error.message : String(error));190process.exit(1);191});192