diff --git a/packages/engine/src/services/frameCapture-videoMetadataHints.browser.test.ts b/packages/engine/src/services/frameCapture-videoMetadataHints.browser.test.ts new file mode 100644 index 0000000000..94c8090d91 --- /dev/null +++ b/packages/engine/src/services/frameCapture-videoMetadataHints.browser.test.ts @@ -0,0 +1,62 @@ +import { spawnSync } from "node:child_process"; +import { afterAll, beforeAll, describe, expect, it } from "vitest"; +import puppeteer, { type Browser } from "puppeteer-core"; +import { resolveHeadlessShellPath } from "./browserManager.js"; +import { applyVideoMetadataHints } from "./frameCapture.js"; + +const chromePath = resolveHeadlessShellPath(); +const hasBrowser = chromePath !== undefined && spawnSync(chromePath, ["--version"]).status === 0; + +// Boxes Chrome gives these styles once a 640x360 video's metadata has loaded. +const cases = { + noSize: { style: "", box: "640x360" }, + widthOnly: { style: "width:200px", box: "200x112.5" }, + heightOnly: { style: "height:90px", box: "160x90" }, + bothSizes: { style: "width:200px;height:300px;object-fit:cover", box: "200x300" }, + maxWidthOnly: { style: "max-width:100px", box: "100x56.25" }, + // A flex item may not shrink below its natural width, so its border overflows the 400px row. + borderedFlexItem: { style: "width:100%;border:8px solid", box: "416x241" }, +}; +const ids = Object.keys(cases); + +describe.skipIf(!hasBrowser)("applyVideoMetadataHints in Chrome", () => { + let browser: Browser; + + beforeAll(async () => { + browser = await puppeteer.launch({ + executablePath: chromePath, + headless: true, + args: ["--no-sandbox", "--disable-dev-shm-usage"], + }); + }); + afterAll(async () => { + await browser?.close(); + }); + + it("lays out a video without loaded metadata as it would with it", async () => { + const page = await browser.newPage(); + const videos = Object.entries(cases).map( + ([id, { style }]) => + `
`, + ); + await page.setContent(`${videos.join("")}`); + + await applyVideoMetadataHints( + page, + ids.map((id) => ({ id, width: 640, height: 360 })), + ); + + const boxes = await page.evaluate((videoIds) => { + return Object.fromEntries( + videoIds.map((id) => { + const rect = document.getElementById(id)!.getBoundingClientRect(); + return [id, `${rect.width}x${rect.height}`]; + }), + ); + }, ids); + expect(boxes).toEqual( + Object.fromEntries(Object.entries(cases).map(([id, { box }]) => [id, box])), + ); + await page.close(); + }); +}); diff --git a/packages/engine/src/services/frameCapture.ts b/packages/engine/src/services/frameCapture.ts index c71a963b5f..8c24bfe849 100644 --- a/packages/engine/src/services/frameCapture.ts +++ b/packages/engine/src/services/frameCapture.ts @@ -2217,7 +2217,7 @@ async function decodeAllImages(page: Page): Promise