diff --git a/packages/studio/tests/e2e/edit-accuracy/case.mjs b/packages/studio/tests/e2e/edit-accuracy/case.mjs index 4aa2c859f11..90d2e3216d2 100644 --- a/packages/studio/tests/e2e/edit-accuracy/case.mjs +++ b/packages/studio/tests/e2e/edit-accuracy/case.mjs @@ -770,7 +770,7 @@ async function nudgeGesture(ctx, pre) { const blockedCdnUrls = new Set(); /** Serves the fixtures' CDN requests from the repo; any other CDN URL is blocked and named once. */ -async function serveFixtureAssetsLocally(page) { +export async function serveFixtureAssetsLocally(page) { const cdp = await page.createCDPSession(); cdp.on("Fetch.requestPaused", ({ requestId, request }) => { const file = localAsset(request.url); diff --git a/packages/studio/tests/e2e/edit-accuracy/render.mjs b/packages/studio/tests/e2e/edit-accuracy/render.mjs index 8d859d5e8ff..1865cf77e10 100644 --- a/packages/studio/tests/e2e/edit-accuracy/render.mjs +++ b/packages/studio/tests/e2e/edit-accuracy/render.mjs @@ -9,6 +9,7 @@ import { createFileServer, initializeSession, } from "../../../../producer/src/index.js"; +import { serveFixtureAssetsLocally } from "./case.mjs"; import { BACKGROUND, COMPOSITION, PLAYHEAD, TARGET } from "./grid.mjs"; const FPS = { num: 30, den: 1 }; @@ -81,8 +82,11 @@ export async function renderBox(dir, decoder, time = PLAYHEAD) { }, null, // A composition with no GSAP never registers a timeline; the default 45 s wait for one changes no pixel. - { playerReadyTimeout: 10_000 }, + // Static-frame dedup opens a second page that would fetch GSAP from the CDN; one frame needs none. + { playerReadyTimeout: 10_000, staticFrameDedup: false }, ); + // The producer's own browser would fetch GSAP from the CDN; a stalled fetch renders the 0 s frame. + await serveFixtureAssetsLocally(session.page); await initializeSession(session); const { buffer } = await captureFrameToBuffer(session, 0, time); // Diagnostic only: a DOM rect ignores clip-path, so it cannot score a crop. diff --git a/packages/studio/tests/e2e/edit-accuracy/render.test.mjs b/packages/studio/tests/e2e/edit-accuracy/render.test.mjs new file mode 100644 index 00000000000..15ac4e4bc60 --- /dev/null +++ b/packages/studio/tests/e2e/edit-accuracy/render.test.mjs @@ -0,0 +1,57 @@ +import { mkdtempSync, readFileSync, rmSync } from "node:fs"; +import { createRequire } from "node:module"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { describe, expect, it, vi } from "vitest"; +import { FIXTURE_CDN } from "./grid.mjs"; + +const require = createRequire(import.meta.url); +const gsapUrl = `${FIXTURE_CDN}npm/gsap@${require("gsap/package.json").version}/dist/gsap.min.js`; +// Chrome's Fetch urlPattern: the whole URL, `*` any run of characters, `?` one character. +const chromeUrlPattern = (pattern) => + new RegExp( + `^${pattern + .replace(/[.+^${}()|[\]\\]/g, "\\$&") + .replace(/\*/g, ".*") + .replace(/\?/g, ".")}$`, + ); +const sent = []; +const handlers = {}; +const sessionArgs = []; +const page = { + createCDPSession: async () => ({ + on: (event, handler) => (handlers[event] = handler), + send: async (method, params) => void sent.push({ method, params }), + }), + evaluate: async () => null, +}; + +vi.mock("../../../../producer/src/index.js", () => ({ + createFileServer: async () => ({ url: "http://127.0.0.1:1", close() {} }), + createCaptureSession: async (...args) => (sessionArgs.push(args), { page }), + // Navigation: Chrome pauses the fixture's GSAP request here when it is intercepted. + initializeSession: async () => + handlers["Fetch.requestPaused"]?.({ requestId: "1", request: { url: gsapUrl } }), + captureFrameToBuffer: async () => ({ buffer: Buffer.alloc(0) }), + closeCaptureSession: async () => undefined, +})); + +describe("renderBox", () => { + it("serves the fixture's GSAP from the repo and opens no second page that would fetch it", async () => { + const { renderBox } = await import("./render.mjs"); + const dir = mkdtempSync(join(tmpdir(), "edit-bench-render-")); + const decoder = { evaluate: async () => ({ left: 0, right: 1, top: 0, bottom: 1, area: 1 }) }; + try { + await renderBox(dir, decoder); + } finally { + rmSync(dir, { recursive: true }); + } + expect(sent.map((s) => s.method)).toEqual(["Fetch.enable", "Fetch.fulfillRequest"]); + const { patterns } = sent[0].params; + expect(patterns).toEqual([{ urlPattern: patterns[0].urlPattern }]); + expect(gsapUrl).toMatch(chromeUrlPattern(patterns[0].urlPattern)); + const gsap = readFileSync(require.resolve("gsap/dist/gsap.min.js")).toString("base64"); + expect(sent[1].params.body).toBe(gsap); + expect(sessionArgs[0][4]).toMatchObject({ staticFrameDedup: false }); + }); +});