fix(cli): preload render.js once in renderLocal test suite to fix Windows CI

The first dynamic `await import("./render.js")` cold-load takes >5 s on
Windows runners — long enough to blow vitest's default 5 s timeout in
whichever test ran it first. Subsequent imports are <10 ms because the
module is now cached, so only test #1 ever times out.

The downstream failure is more subtle: when test #1 times out, vitest
moves on, but its leaked async function eventually hits the synchronous
`producer.createRenderJob(...)` line and pushes a stale config to
`producerState.createdJobs`. That push lands AFTER test #2's `beforeEach`
clears the array, so test #2's `createdJobs[0]` is the leaked test #1
entry instead of its own. That's why test #2 saw `browserGpuMode: 'software'`
when it expected `'auto'`.

Hoist the import into `beforeAll` (matching the pattern the existing
`parseVariablesArg` and `validateVariablesAgainstProject` describe blocks
in this file already use). Cold-load happens once outside any test's
timeout window, every test stays fast, no leaked promise can corrupt
state.

Failing run: https://github.com/heygen-com/hyperframes/actions/runs/25470257972/job/74732502915
Started failing on main with the merge of #642 (auto-detect-browser-gpu),
which added the "forwards browserGpuMode='auto'" test as test #2.
This commit is contained in:
James
2026-05-07 02:06:03 +00:00
parent 31acf7fdec
commit a739be58b1
+14 -12
View File
@@ -26,6 +26,19 @@ vi.mock("../telemetry/events.js", () => ({
describe("renderLocal browser GPU config", () => {
const savedEnv = new Map<string, string | undefined>();
// Pre-resolve once. The first dynamic `import("./render.js")` in this file
// takes >5 s on Windows runners (cold module load) — long enough to blow
// vitest's default 5 s timeout in whichever test happens to be first. When
// that test times out, its leaked late `createRenderJob` call lands AFTER
// the next test's `beforeEach` clears `producerState.createdJobs`, shifting
// index 0 and corrupting unrelated assertions. Importing once in
// `beforeAll` keeps every test fast and isolated.
let renderLocal: typeof import("./render.js").renderLocal;
let resolveBrowserGpuForCli: typeof import("./render.js").resolveBrowserGpuForCli;
beforeAll(async () => {
({ renderLocal, resolveBrowserGpuForCli } = await import("./render.js"));
});
function setEnv(key: string, value: string) {
savedEnv.set(key, process.env[key]);
@@ -54,7 +67,6 @@ describe("renderLocal browser GPU config", () => {
it("passes an explicit software override for --no-browser-gpu even when env requests hardware", async () => {
setEnv("PRODUCER_BROWSER_GPU_MODE", "hardware");
const { renderLocal } = await import("./render.js");
await renderLocal("/tmp/project", "/tmp/out.mp4", {
fps: 30,
quality: "standard",
@@ -73,7 +85,6 @@ describe("renderLocal browser GPU config", () => {
});
it("forwards browserGpuMode='auto' into producer config (probe-then-choose)", async () => {
const { renderLocal } = await import("./render.js");
await renderLocal("/tmp/project", "/tmp/out.mp4", {
fps: 30,
quality: "standard",
@@ -92,7 +103,6 @@ describe("renderLocal browser GPU config", () => {
});
it("passes an explicit hardware override for default local browser GPU", async () => {
const { renderLocal } = await import("./render.js");
await renderLocal("/tmp/project", "/tmp/out.mp4", {
fps: 30,
quality: "standard",
@@ -110,9 +120,7 @@ describe("renderLocal browser GPU config", () => {
});
});
it("resolves browser GPU from CLI flags, Docker mode, and env fallback", async () => {
const { resolveBrowserGpuForCli } = await import("./render.js");
it("resolves browser GPU from CLI flags, Docker mode, and env fallback", () => {
// Default (no flag, no env): auto — engine probes and chooses.
expect(resolveBrowserGpuForCli(false, undefined, undefined)).toBe("auto");
// Env override
@@ -128,7 +136,6 @@ describe("renderLocal browser GPU config", () => {
});
it("forwards parsed --variables payload to createRenderJob", async () => {
const { renderLocal } = await import("./render.js");
await renderLocal("/tmp/project", "/tmp/out.mp4", {
fps: 30,
quality: "standard",
@@ -144,7 +151,6 @@ describe("renderLocal browser GPU config", () => {
});
it("forwards format: png-sequence through to createRenderJob", async () => {
const { renderLocal } = await import("./render.js");
await renderLocal("/tmp/project", "/tmp/frames", {
fps: 30,
quality: "standard",
@@ -159,7 +165,6 @@ describe("renderLocal browser GPU config", () => {
});
it("omits variables from createRenderJob when not provided", async () => {
const { renderLocal } = await import("./render.js");
await renderLocal("/tmp/project", "/tmp/out.mp4", {
fps: 30,
quality: "standard",
@@ -174,7 +179,6 @@ describe("renderLocal browser GPU config", () => {
});
it("forwards entryFile to createRenderJob when --composition is set", async () => {
const { renderLocal } = await import("./render.js");
await renderLocal("/tmp/project", "/tmp/out.mp4", {
fps: 30,
quality: "standard",
@@ -190,7 +194,6 @@ describe("renderLocal browser GPU config", () => {
});
it("omits entryFile from createRenderJob when --composition is not set", async () => {
const { renderLocal } = await import("./render.js");
await renderLocal("/tmp/project", "/tmp/out.mp4", {
fps: 30,
quality: "standard",
@@ -211,7 +214,6 @@ describe("renderLocal browser GPU config", () => {
.mockImplementation((code?: string | number | null): never => {
throw new Error(`process.exit:${code ?? ""}`);
});
const { renderLocal } = await import("./render.js");
await renderLocal("/tmp/project", "/tmp/out.mp4", {
fps: 30,