|
1 | | -import { afterEach, describe, expect, it } from "vitest"; |
| 1 | +import { afterEach, describe, expect, it, vi } from "vitest"; |
| 2 | +import { mkdtempSync, rmSync } from "node:fs"; |
| 3 | +import { tmpdir } from "node:os"; |
| 4 | +import { join } from "node:path"; |
2 | 5 | import type { Page } from "puppeteer-core"; |
| 6 | +import { captureFrameToBuffer, createCaptureSession } from "./frameCapture.js"; |
| 7 | +import { pageScreenshotCapture } from "./screenshotService.js"; |
3 | 8 | import { releaseEndedWebglContexts } from "./webglRelease.js"; |
4 | 9 |
|
| 10 | +const ended = { start: 0, end: 3 }; |
| 11 | +const lostByCapture: string[] = []; |
| 12 | +const hiddenEndedClip = { |
| 13 | + hasAttribute: (n: string) => n === "data-hf-clip-hidden", |
| 14 | + parentElement: null, |
| 15 | +}; |
| 16 | + |
| 17 | +vi.mock("./browserManager.js", async (importOriginal) => ({ |
| 18 | + ...(await importOriginal<typeof import("./browserManager.js")>()), |
| 19 | + resolveBrowserGpuMode: async () => "hardware", |
| 20 | + acquireBrowser: async () => ({ |
| 21 | + captureMode: "screenshot", |
| 22 | + browser: { |
| 23 | + version: async () => "Chrome/150.0.0.0", |
| 24 | + newPage: async () => ({ |
| 25 | + mainFrame: () => ({}), |
| 26 | + evaluateOnNewDocument: async () => {}, |
| 27 | + setViewport: async () => {}, |
| 28 | + evaluate: async (fn: unknown, ...args: unknown[]) => { |
| 29 | + if (typeof fn !== "function" || !String(fn).includes("__hf_gl_contexts")) |
| 30 | + return undefined; |
| 31 | + const ctx = { |
| 32 | + getExtension: () => ({ loseContext: () => lostByCapture.push("3d") }), |
| 33 | + }; |
| 34 | + const previous = globalThis.window; |
| 35 | + Object.assign(globalThis, { |
| 36 | + window: { |
| 37 | + __hf_gl_contexts: [{ canvas: { ...hiddenEndedClip }, ctx }], |
| 38 | + __hf: { clipWindow: () => ended }, |
| 39 | + }, |
| 40 | + }); |
| 41 | + try { |
| 42 | + return await (fn as (...a: unknown[]) => unknown)(...args); |
| 43 | + } finally { |
| 44 | + Object.assign(globalThis, { window: previous }); |
| 45 | + } |
| 46 | + }, |
| 47 | + }), |
| 48 | + }, |
| 49 | + }), |
| 50 | +})); |
| 51 | +vi.mock("./screenshotService.js", async (importOriginal) => ({ |
| 52 | + ...(await importOriginal<typeof import("./screenshotService.js")>()), |
| 53 | + pageScreenshotCapture: vi.fn(), |
| 54 | +})); |
| 55 | + |
5 | 56 | type Node = { |
6 | 57 | hasAttribute: (n: string) => boolean; |
7 | 58 | parentElement: Node | null; |
@@ -93,3 +144,27 @@ describe("releaseEndedWebglContexts", () => { |
93 | 144 | expect(lost).toEqual([]); |
94 | 145 | }); |
95 | 146 | }); |
| 147 | + |
| 148 | +describe("capturing a frame", () => { |
| 149 | + it("a WebGL canvas in a hidden clip whose window has ended keeps its context (never loseContext)", async () => { |
| 150 | + const dir = mkdtempSync(join(tmpdir(), "hf-webgl-keep-")); |
| 151 | + try { |
| 152 | + vi.mocked(pageScreenshotCapture).mockResolvedValue(Buffer.from("frame")); |
| 153 | + const capture = await createCaptureSession( |
| 154 | + "http://localhost:0", |
| 155 | + dir, |
| 156 | + { width: 320, height: 180, fps: { num: 30, den: 1 }, format: "jpeg" }, |
| 157 | + null, |
| 158 | + { forceScreenshot: true }, |
| 159 | + ); |
| 160 | + capture.isInitialized = true; |
| 161 | + lostByCapture.length = 0; |
| 162 | + |
| 163 | + await captureFrameToBuffer(capture, 0, 5); |
| 164 | + |
| 165 | + expect(lostByCapture).toEqual([]); |
| 166 | + } finally { |
| 167 | + rmSync(dir, { recursive: true, force: true }); |
| 168 | + } |
| 169 | + }); |
| 170 | +}); |
0 commit comments