diff --git a/packages/cli/src/capture/mediaCapture.test.ts b/packages/cli/src/capture/mediaCapture.test.ts index 10aabdf12..f87a379ee 100644 --- a/packages/cli/src/capture/mediaCapture.test.ts +++ b/packages/cli/src/capture/mediaCapture.test.ts @@ -1,4 +1,4 @@ -import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import type { Browser, Page } from "puppeteer-core"; @@ -75,6 +75,55 @@ describe("Lottie capture budget", () => { expect(previewPage.setViewport).not.toHaveBeenCalled(); expect(previewPage.screenshot).not.toHaveBeenCalled(); }); + + it("omits a preview path when the budget expires after Lottie readiness", async () => { + const dir = tempDir(); + const lottieDir = join(dir, "assets", "lottie"); + mkdirSync(join(dir, "extracted"), { recursive: true }); + mkdirSync(lottieDir, { recursive: true }); + writeFileSync( + join(lottieDir, "logo.json"), + JSON.stringify({ + nm: "Logo", + w: 100, + h: 100, + fr: 30, + ip: 0, + op: 30, + layers: [], + }), + ); + + const screenshot = vi.fn(async () => undefined); + const previewPage = { + setViewport: vi.fn(async () => undefined), + setContent: vi.fn(async () => undefined), + evaluate: vi.fn(async () => undefined), + waitForFunction: vi.fn(async () => undefined), + screenshot, + close: vi.fn(async () => undefined), + }; + const browser = { newPage: vi.fn(async () => previewPage) } as unknown as Browser; + let budgetChecks = 0; + + await renderLottiePreviews(browser, lottieDir, dir, { + remainingMs: () => (++budgetChecks < 4 ? 10_000 : 0), + }); + + const manifest = JSON.parse( + readFileSync(join(dir, "extracted", "lottie-manifest.json"), "utf-8"), + ); + expect(screenshot).not.toHaveBeenCalled(); + expect(manifest).toHaveLength(1); + expect(manifest[0]).toMatchObject({ + file: "assets/lottie/logo.json", + name: "Logo", + width: 100, + height: 100, + }); + expect(manifest[0]).not.toHaveProperty("preview"); + expect(existsSync(join(lottieDir, "previews", "logo-preview.png"))).toBe(false); + }); }); describe("video capture live budget", () => { diff --git a/packages/cli/src/capture/mediaCapture.ts b/packages/cli/src/capture/mediaCapture.ts index e5add7674..8e149999d 100644 --- a/packages/cli/src/capture/mediaCapture.ts +++ b/packages/cli/src/capture/mediaCapture.ts @@ -122,7 +122,7 @@ export async function saveLottieAnimations( * * Opens each Lottie JSON in a headless Chrome page via lottie-web, * seeks to ~30% through the animation, and takes a transparent screenshot. - * Writes a lottie-manifest.json with metadata + preview paths. + * Writes a lottie-manifest.json with metadata and successfully rendered preview paths. */ // fallow-ignore-next-line complexity export async function renderLottiePreviews( @@ -133,7 +133,7 @@ export async function renderLottiePreviews( ): Promise { const manifest: Array<{ file: string; - preview: string; + preview?: string; name: string; width: number; height: number; @@ -152,6 +152,7 @@ export async function renderLottiePreviews( const fr = raw.fr || 30; const dur = ((raw.op || 0) - (raw.ip || 0)) / fr; const previewName = file.replace(".json", "-preview.png"); + let preview: string | undefined; // Render a mid-frame thumbnail using Puppeteer + lottie-web // Skip huge Lottie files for preview (CDP has a ~256MB message limit) @@ -202,6 +203,7 @@ export async function renderLottiePreviews( type: "png", omitBackground: true, }); + preview = `assets/lottie/previews/${previewName}`; } } catch { /* preview rendering failed — non-critical */ @@ -211,7 +213,7 @@ export async function renderLottiePreviews( manifest.push({ file: `assets/lottie/${file}`, - preview: `assets/lottie/previews/${previewName}`, + ...(preview ? { preview } : {}), name: raw.nm || file, width: raw.w || 0, height: raw.h || 0,