( opts: CaptureOptions, onProgress?: (stage: string, detail?: string) => void, )
| 44 | |
| 45 | // fallow-ignore-next-line complexity |
| 46 | export async function captureWebsite( |
| 47 | opts: CaptureOptions, |
| 48 | onProgress?: (stage: string, detail?: string) => void, |
| 49 | ): Promise<CaptureResult> { |
| 50 | const { |
| 51 | url, |
| 52 | outputDir, |
| 53 | viewportWidth = 1920, |
| 54 | viewportHeight = 1080, |
| 55 | timeout = 120000, |
| 56 | settleTime = 3000, |
| 57 | maxScreenshots: _maxScreenshots = 24, |
| 58 | skipAssets = false, |
| 59 | } = opts; |
| 60 | |
| 61 | const warnings: string[] = []; |
| 62 | const progress = (stage: string, detail?: string) => { |
| 63 | onProgress?.(stage, detail); |
| 64 | }; |
| 65 | |
| 66 | // Load .env file from repo root if it exists (for GEMINI_API_KEY, etc.) |
| 67 | loadEnvFile(outputDir); |
| 68 | |
| 69 | // Create output directories |
| 70 | mkdirSync(join(outputDir, "extracted"), { recursive: true }); |
| 71 | mkdirSync(join(outputDir, "screenshots"), { recursive: true }); |
| 72 | mkdirSync(join(outputDir, "assets"), { recursive: true }); |
| 73 | |
| 74 | // Launch browser |
| 75 | progress("browser", "Launching headless Chrome..."); |
| 76 | const { ensureBrowser } = await import("../browser/manager.js"); |
| 77 | const browser = await ensureBrowser(); |
| 78 | const puppeteer = await import("puppeteer-core"); |
| 79 | const chromeBrowser = await puppeteer.default.launch({ |
| 80 | headless: true, |
| 81 | executablePath: browser.executablePath, |
| 82 | args: [ |
| 83 | "--no-sandbox", |
| 84 | "--disable-dev-shm-usage", |
| 85 | "--enable-webgl", |
| 86 | "--ignore-gpu-blocklist", |
| 87 | "--use-gl=angle", |
| 88 | "--use-angle=swiftshader", |
| 89 | "--disable-blink-features=AutomationControlled", |
| 90 | "--disable-background-timer-throttling", |
| 91 | "--disable-renderer-backgrounding", |
| 92 | `--window-size=${viewportWidth},${viewportHeight}`, |
| 93 | ], |
| 94 | }); |
| 95 | |
| 96 | let animationCatalog: CaptureResult["animationCatalog"]; |
| 97 | |
| 98 | try { |
| 99 | // ═══════════════════════════════════════════════════════════════ |
| 100 | // PASS 1: Full page load — all JS runs |
| 101 | // Goal: Catalog animations + take screenshots (with JS rendering) |
| 102 | // ═══════════════════════════════════════════════════════════════ |
| 103 |
no test coverage detected