import { DESKTOP_PREVIEW_RECORDING_CAPTURE_TRIGGER, EnvironmentId, ThreadId,} from "@t3tools/contracts";import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test"; import { ensureClientSettingsHydrated } from "~/hooks/useSettings"; const { clientSettings, events, getDisplayMedia, registrySet, requestDisplayMediaCapture, save, startScreencast, stopScreencast,} = vi.hoisted(() => { const events: string[] = []; return { clientSettings: { browserRecordingFrameRate: 30 as 30 | 60 }, events, getDisplayMedia: vi.fn(), requestDisplayMediaCapture: vi.fn((_tabId: string) => undefined), registrySet: vi.fn((_atom: unknown, value: { readonly tabIds: ReadonlySet<string> }) => { events.push( value.tabIds.size === 0 ? "clear" : `publish:${Array.from(value.tabIds).join(",")}`, ); }), save: vi.fn(async (tabId: string) => ({ id: "recording-test", tabId, path: "/tmp/recording-test.webm", mimeType: "video/webm" as const, sizeBytes: 0, createdAt: "2026-06-26T00:00:00.000Z", })), startScreencast: vi.fn(async (_tabId: string) => { events.push("start-screencast"); }), stopScreencast: vi.fn(async () => undefined), };}); vi.mock("~/components/preview/previewBridge", () => ({ previewBridge: { recording: { onFrame: vi.fn(), save, startScreencast: async (tabId: string) => { await startScreencast(tabId); requestDisplayMediaCapture(tabId); }, stopScreencast, }, },})); vi.mock("~/rpc/atomRegistry", () => ({ appAtomRegistry: { set: registrySet },})); vi.mock("~/hooks/useSettings", () => ({ ensureClientSettingsHydrated: vi.fn(async () => undefined), getClientSettings: () => clientSettings,})); import { BROWSER_RECORDING_PAINT_SETTLE_TIMEOUT_MS, BROWSER_RECORDING_STARTUP_SETTLE_TIMEOUT_MS, BrowserRecordingCaptureTimeoutError, BrowserRecordingConflictError, BrowserRecordingFormatUnavailableError, BrowserRecordingStartCancelledError, findActiveBrowserRecordingRuntimeTabId, readActiveBrowserRecordingTabIds, readActiveBrowserRecordingTargets, startBrowserRecording, stopBrowserRecording, stopBrowserRecordingForUpload,} from "./browserRecording";import { useBrowserSurfaceStore } from "./browserSurfaceStore";import { previewRuntimeTabId } from "./previewRuntimeTabId"; class FakeMediaRecorder { static readonly instances: FakeMediaRecorder[] = []; static supportedTypes = new Set(["video/webm;codecs=vp9"]); static outputMimeType: string | undefined; static stopError: unknown; static isTypeSupported(type: string): boolean { return this.supportedTypes.has(type); } state: RecordingState = "inactive"; readonly mimeType: string; readonly stream: MediaStream; readonly options: MediaRecorderOptions | undefined; private readonly listeners = new Map<string, Set<EventListenerOrEventListenerObject>>(); constructor(stream: MediaStream, options?: MediaRecorderOptions) { this.stream = stream; this.options = options; this.mimeType = FakeMediaRecorder.outputMimeType ?? options?.mimeType ?? "video/browser-default"; FakeMediaRecorder.instances.push(this); } addEventListener(type: string, listener: EventListenerOrEventListenerObject): void { const listeners = this.listeners.get(type) ?? new Set(); listeners.add(listener); this.listeners.set(type, listeners); } start(): void { this.state = "recording"; } stop(): void { if (FakeMediaRecorder.stopError !== undefined) throw FakeMediaRecorder.stopError; this.state = "inactive"; for (const listener of this.listeners.get("stop") ?? []) { if (typeof listener === "function") listener(new Event("stop")); else listener.handleEvent(new Event("stop")); } }} describe("browser recording", () => { let animationFrameCount = 0; beforeEach(() => { events.length = 0; vi.clearAllMocks(); FakeMediaRecorder.instances.length = 0; FakeMediaRecorder.supportedTypes = new Set(["video/webm;codecs=vp9"]); FakeMediaRecorder.outputMimeType = undefined; FakeMediaRecorder.stopError = undefined; clientSettings.browserRecordingFrameRate = 30; animationFrameCount = 0; vi.stubGlobal("window", globalThis); vi.stubGlobal("requestAnimationFrame", (callback: FrameRequestCallback) => { animationFrameCount += 1; callback(animationFrameCount); return animationFrameCount; }); vi.stubGlobal("cancelAnimationFrame", vi.fn()); vi.stubGlobal("MediaRecorder", FakeMediaRecorder as unknown as typeof MediaRecorder); getDisplayMedia.mockResolvedValue({ getVideoTracks: () => [], getTracks: () => [{ stop: vi.fn() }], }); requestDisplayMediaCapture.mockImplementation((tabId: string) => { const trigger = Reflect.get(globalThis, DESKTOP_PREVIEW_RECORDING_CAPTURE_TRIGGER); if (typeof trigger !== "function" || trigger(tabId) !== true) { throw new Error(`No pending display-media capture for ${tabId}.`); } }); vi.stubGlobal("navigator", { mediaDevices: { getDisplayMedia } }); useBrowserSurfaceStore.setState({ activityByTabId: {}, byTabId: {} }); }); afterEach(() => { vi.useRealTimers(); vi.unstubAllGlobals(); }); it("starts recording for a visible tab", async () => { await startBrowserRecording("recording-tab"); const startupEvents = [...events]; await stopBrowserRecording("recording-tab"); expect(startupEvents).toEqual(["publish:recording-tab", "start-screencast"]); }); it("routes gesture-free starts through the desktop capture trigger", async () => { await startBrowserRecording("automation-recording-tab"); expect(requestDisplayMediaCapture).toHaveBeenCalledWith("automation-recording-tab"); expect(getDisplayMedia).toHaveBeenCalledOnce(); await stopBrowserRecording("automation-recording-tab"); }); it("saves locally and releases capture before transferring the encoded recording once", async () => { const stopTrack = vi.fn(); getDisplayMedia.mockResolvedValue({ getVideoTracks: () => [], getTracks: () => [{ stop: stopTrack }], }); await startBrowserRecording("transfer-tab"); let finishUpload!: () => void; const uploaded = new Promise<void>((resolve) => { finishUpload = resolve; }); const transfer = vi.fn(async (artifact, blob: Blob) => { expect(save).toHaveBeenCalledOnce(); expect(stopTrack).toHaveBeenCalled(); expect(artifact.path).toBe("/tmp/recording-test.webm"); expect(blob.type).toBe("video/webm;codecs=vp9"); await uploaded; return "uploaded-recording"; }); const localStop = stopBrowserRecording("transfer-tab"); const firstStop = stopBrowserRecordingForUpload("transfer-tab", transfer); const secondStop = stopBrowserRecordingForUpload("transfer-tab", transfer); finishUpload(); expect(await firstStop).toEqual(await secondStop); expect((await firstStop)?.uploadedAttachmentId).toBe("uploaded-recording"); expect((await localStop)?.path).toBe("/tmp/recording-test.webm"); expect(transfer).toHaveBeenCalledOnce(); }); it("keeps the saved desktop file and releases the recording when transfer fails", async () => { await startBrowserRecording("failed-transfer-tab"); await expect( stopBrowserRecordingForUpload("failed-transfer-tab", async () => { throw new Error("Connection interrupted"); }), ).rejects.toThrow("Connection interrupted"); expect(save).toHaveBeenCalledOnce(); expect(readActiveBrowserRecordingTabIds().has("failed-transfer-tab")).toBe(false); await startBrowserRecording("failed-transfer-tab"); await stopBrowserRecording("failed-transfer-tab"); }); it("paints and holds a hidden browser surface for the recording lifetime", async () => { startScreencast.mockImplementationOnce(async (tabId: string) => { expect(animationFrameCount).toBe(2); expect(useBrowserSurfaceStore.getState().activityByTabId[tabId]).toBe(1); }); getDisplayMedia.mockImplementationOnce(async () => { expect(animationFrameCount).toBe(2); expect(useBrowserSurfaceStore.getState().activityByTabId["background-tab"]).toBe(1); return { getVideoTracks: () => [], getTracks: () => [{ stop: vi.fn() }] }; }); await startBrowserRecording("background-tab"); expect(useBrowserSurfaceStore.getState().activityByTabId["background-tab"]).toBe(1); await stopBrowserRecording("background-tab"); expect(useBrowserSurfaceStore.getState().activityByTabId["background-tab"]).toBeUndefined(); }); it("bounds compositor warmup when animation frames are paused", async () => { vi.useFakeTimers(); const cancelAnimationFrame = vi.fn(); vi.stubGlobal( "requestAnimationFrame", vi.fn(() => 42), ); vi.stubGlobal("cancelAnimationFrame", cancelAnimationFrame); const startPromise = startBrowserRecording("hidden-window-tab"); await vi.advanceTimersByTimeAsync(BROWSER_RECORDING_PAINT_SETTLE_TIMEOUT_MS); await startPromise; expect(cancelAnimationFrame).toHaveBeenCalledWith(42); await stopBrowserRecording("hidden-window-tab"); }); it.each([ { width: 1280, height: 720, frameRate: 60, bitrate: 2_764_800 }, { width: 320, height: 240, frameRate: 30, bitrate: 2_500_000 }, { width: 3840, height: 2160, frameRate: 60, bitrate: 24_883_200 }, { width: 7680, height: 4320, frameRate: 60, bitrate: 50_000_000 }, ])("records the native $width x $height stream at $frameRate fps", async (settings) => { const stopTrack = vi.fn(); const stream = { getVideoTracks: () => [{ getSettings: () => settings }], getTracks: () => [{ stop: stopTrack }], } as unknown as MediaStream; getDisplayMedia.mockResolvedValueOnce(stream); await startBrowserRecording("recording-tab"); expect(getDisplayMedia).toHaveBeenCalledWith({ audio: false, video: { frameRate: { ideal: 30, max: 30 } }, }); expect(FakeMediaRecorder.instances[0]?.stream).toBe(stream); expect(FakeMediaRecorder.instances[0]?.options?.videoBitsPerSecond).toBe(settings.bitrate); await stopBrowserRecording("recording-tab"); expect(stopTrack).toHaveBeenCalledOnce(); expect(stopTrack.mock.invocationCallOrder[0]).toBeLessThan(save.mock.invocationCallOrder[0]!); }); it("uses the configured recording frame rate", async () => { clientSettings.browserRecordingFrameRate = 60; await startBrowserRecording("recording-tab"); expect(getDisplayMedia).toHaveBeenCalledWith({ audio: false, video: { frameRate: { ideal: 60, max: 60 } }, }); await stopBrowserRecording("recording-tab"); }); it("clears a failed settings read before retrying recording", async () => { const tabId = "settings-read-failure-tab"; const error = new Error("Settings read failed"); vi.mocked(ensureClientSettingsHydrated).mockRejectedValueOnce(error); await expect(startBrowserRecording(tabId)).rejects.toBe(error); expect(readActiveBrowserRecordingTabIds()).toEqual(new Set()); expect(useBrowserSurfaceStore.getState().activityByTabId[tabId]).toBeUndefined(); expect(animationFrameCount).toBe(0); expect(startScreencast).not.toHaveBeenCalled(); expect(stopScreencast).not.toHaveBeenCalled(); expect(getDisplayMedia).not.toHaveBeenCalled(); expect(FakeMediaRecorder.instances).toHaveLength(0); clientSettings.browserRecordingFrameRate = 60; await startBrowserRecording(tabId); expect(getDisplayMedia).toHaveBeenCalledWith({ audio: false, video: { frameRate: { ideal: 60, max: 60 } }, }); await stopBrowserRecording(tabId); expect(startScreencast).toHaveBeenCalledOnce(); expect(readActiveBrowserRecordingTabIds()).toEqual(new Set()); expect(useBrowserSurfaceStore.getState().activityByTabId[tabId]).toBeUndefined(); }); it("stops the native stream when MediaRecorder cleanup fails", async () => { const stopTrack = vi.fn(); getDisplayMedia.mockResolvedValueOnce({ getVideoTracks: () => [], getTracks: () => [{ stop: stopTrack }], }); await startBrowserRecording("recording-tab"); FakeMediaRecorder.stopError = new Error("stop failed"); await expect(stopBrowserRecording("recording-tab")).rejects.toMatchObject({ operation: "cleanup", tabId: "recording-tab", }); expect(stopTrack).toHaveBeenCalledOnce(); }); it("uses the best supported encoder and saves the recorder's actual format", async () => { FakeMediaRecorder.supportedTypes = new Set([ "video/mp4;codecs=avc1", "video/mp4;codecs=avc1.42e01e", "video/webm;codecs=vp9", "video/webm;codecs=av1", ]); FakeMediaRecorder.outputMimeType = "video/webm;codecs=av01"; await startBrowserRecording("recording-tab"); await stopBrowserRecording("recording-tab"); expect(FakeMediaRecorder.instances[0]?.options).toEqual({ mimeType: "video/mp4;codecs=avc1", videoBitsPerSecond: 3_110_400, }); expect(save).toHaveBeenCalledWith( "recording-tab", "video/webm;codecs=av01", expect.any(Uint8Array), ); }); it("lets the browser select the format when no preferred encoding is supported", async () => { FakeMediaRecorder.supportedTypes = new Set(); FakeMediaRecorder.outputMimeType = "video/platform-default"; await startBrowserRecording("recording-tab"); await stopBrowserRecording("recording-tab"); expect(FakeMediaRecorder.instances[0]?.options).toEqual({ videoBitsPerSecond: 3_110_400 }); expect(save).toHaveBeenCalledWith( "recording-tab", "video/platform-default", expect.any(Uint8Array), ); }); it("reports when MediaRecorder provides no output format", async () => { FakeMediaRecorder.supportedTypes = new Set(); FakeMediaRecorder.outputMimeType = ""; await startBrowserRecording("recording-tab"); await expect(stopBrowserRecording("recording-tab")).rejects.toBeInstanceOf( BrowserRecordingFormatUnavailableError, ); expect(save).not.toHaveBeenCalled(); expect(readActiveBrowserRecordingTabIds()).toEqual(new Set()); }); it("releases the native capture lease when stream acquisition fails", async () => { getDisplayMedia.mockRejectedValueOnce(new Error("capture failed")); await expect(startBrowserRecording("recording-tab")).rejects.toMatchObject({ operation: "capture-media-stream", tabId: "recording-tab", }); expect(stopScreencast).toHaveBeenCalledWith("recording-tab"); expect(events.at(-1)).toBe("clear"); }); it("times out stalled stream acquisition and stops a late stream", async () => { vi.useFakeTimers(); let finishCapture!: (stream: MediaStream) => void; const stopTrack = vi.fn(); getDisplayMedia.mockImplementationOnce( () => new Promise<MediaStream>((resolve) => { finishCapture = resolve; }), ); const startPromise = startBrowserRecording("recording-tab"); await vi.waitFor(() => expect(getDisplayMedia).toHaveBeenCalledOnce()); const rejection = expect(startPromise).rejects.toMatchObject({ _tag: "BrowserRecordingCaptureTimeoutError", tabId: "recording-tab", timeoutMs: BROWSER_RECORDING_STARTUP_SETTLE_TIMEOUT_MS, }); await vi.advanceTimersByTimeAsync(BROWSER_RECORDING_STARTUP_SETTLE_TIMEOUT_MS); await rejection; await expect(startPromise).rejects.toBeInstanceOf(BrowserRecordingCaptureTimeoutError); expect(stopScreencast).toHaveBeenCalledWith("recording-tab"); expect(readActiveBrowserRecordingTabIds()).toEqual(new Set()); expect(useBrowserSurfaceStore.getState().activityByTabId["recording-tab"]).toBeUndefined(); finishCapture({ getVideoTracks: () => [], getTracks: () => [{ stop: stopTrack }], } as unknown as MediaStream); await vi.advanceTimersByTimeAsync(0); expect(stopTrack).toHaveBeenCalledOnce(); }); it("records separate tabs concurrently", async () => { const firstThreadRef = { environmentId: EnvironmentId.make("environment-recording"), threadId: ThreadId.make("thread-recording-first"), }; const secondThreadRef = { environmentId: EnvironmentId.make("environment-recording"), threadId: ThreadId.make("thread-recording-second"), }; await Promise.all([ startBrowserRecording("recording-tab", firstThreadRef), startBrowserRecording("recording-tab-2", secondThreadRef), ]); expect(startScreencast).toHaveBeenCalledTimes(2); expect(events).toContain("publish:recording-tab,recording-tab-2"); expect(readActiveBrowserRecordingTabIds()).toEqual( new Set(["recording-tab", "recording-tab-2"]), ); expect(readActiveBrowserRecordingTabIds(firstThreadRef)).toEqual(new Set(["recording-tab"])); expect(readActiveBrowserRecordingTabIds(secondThreadRef)).toEqual(new Set(["recording-tab-2"])); await stopBrowserRecording("recording-tab"); expect(readActiveBrowserRecordingTabIds()).toEqual(new Set(["recording-tab-2"])); await stopBrowserRecording("recording-tab-2"); expect(readActiveBrowserRecordingTabIds()).toEqual(new Set()); expect(save).toHaveBeenCalledTimes(2); }); it("serializes display media grants for concurrent recording starts", async () => { let finishFirstCapture!: (stream: MediaStream) => void; const stream = { getVideoTracks: () => [], getTracks: () => [{ stop: vi.fn() }], } as unknown as MediaStream; getDisplayMedia .mockImplementationOnce( () => new Promise<MediaStream>((resolve) => { finishFirstCapture = resolve; }), ) .mockResolvedValueOnce(stream); const firstStart = startBrowserRecording("recording-tab"); await vi.waitFor(() => expect(getDisplayMedia).toHaveBeenCalledOnce()); const secondStart = startBrowserRecording("recording-tab-2"); await vi.waitFor(() => expect(readActiveBrowserRecordingTabIds().size).toBe(2)); expect(startScreencast).toHaveBeenCalledTimes(1); finishFirstCapture(stream); await Promise.all([firstStart, secondStart]); expect(startScreencast.mock.calls).toEqual([["recording-tab"], ["recording-tab-2"]]); expect(getDisplayMedia).toHaveBeenCalledTimes(2); await Promise.all([ stopBrowserRecording("recording-tab"), stopBrowserRecording("recording-tab-2"),Lines 1–500 of 806. Load more
import { DESKTOP_PREVIEW_RECORDING_CAPTURE_TRIGGER, EnvironmentId, ThreadId, } from "@t3tools/contracts"; import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test"; import { ensureClientSettingsHydrated } from "~/hooks/useSettings"; const { clientSettings, events, getDisplayMedia, registrySet, requestDisplayMediaCapture, save, startScreencast, stopScreencast, } = vi.hoisted(() => { const events: string[] = []; return { clientSettings: { browserRecordingFrameRate: 30 as 30 | 60 }, events, getDisplayMedia: vi.fn(), requestDisplayMediaCapture: vi.fn((_tabId: string) => undefined), registrySet: vi.fn((_atom: unknown, value: { readonly tabIds: ReadonlySet<string> }) => { events.push( value.tabIds.size === 0 ? "clear" : `publish:${Array.from(value.tabIds).join(",")}`, ); }), save: vi.fn(async (tabId: string) => ({ id: "recording-test", tabId, path: "/tmp/recording-test.webm", mimeType: "video/webm" as const, sizeBytes: 0, createdAt: "2026-06-26T00:00:00.000Z", })), startScreencast: vi.fn(async (_tabId: string) => { events.push("start-screencast"); }), stopScreencast: vi.fn(async () => undefined), }; }); vi.mock("~/components/preview/previewBridge", () => ({ previewBridge: { recording: { onFrame: vi.fn(), save, startScreencast: async (tabId: string) => { await startScreencast(tabId); requestDisplayMediaCapture(tabId); }, stopScreencast, }, }, })); vi.mock("~/rpc/atomRegistry", () => ({ appAtomRegistry: { set: registrySet }, })); vi.mock("~/hooks/useSettings", () => ({ ensureClientSettingsHydrated: vi.fn(async () => undefined), getClientSettings: () => clientSettings, })); import { BROWSER_RECORDING_PAINT_SETTLE_TIMEOUT_MS, BROWSER_RECORDING_STARTUP_SETTLE_TIMEOUT_MS, BrowserRecordingCaptureTimeoutError, BrowserRecordingConflictError, BrowserRecordingFormatUnavailableError, BrowserRecordingStartCancelledError, findActiveBrowserRecordingRuntimeTabId, readActiveBrowserRecordingTabIds, readActiveBrowserRecordingTargets, startBrowserRecording, stopBrowserRecording, stopBrowserRecordingForUpload, } from "./browserRecording"; import { useBrowserSurfaceStore } from "./browserSurfaceStore"; import { previewRuntimeTabId } from "./previewRuntimeTabId"; class FakeMediaRecorder { static readonly instances: FakeMediaRecorder[] = []; static supportedTypes = new Set(["video/webm;codecs=vp9"]); static outputMimeType: string | undefined; static stopError: unknown; static isTypeSupported(type: string): boolean { return this.supportedTypes.has(type); } state: RecordingState = "inactive"; readonly mimeType: string; readonly stream: MediaStream; readonly options: MediaRecorderOptions | undefined; private readonly listeners = new Map<string, Set<EventListenerOrEventListenerObject>>(); constructor(stream: MediaStream, options?: MediaRecorderOptions) { this.stream = stream; this.options = options; this.mimeType = FakeMediaRecorder.outputMimeType ?? options?.mimeType ?? "video/browser-default"; FakeMediaRecorder.instances.push(this); } addEventListener(type: string, listener: EventListenerOrEventListenerObject): void { const listeners = this.listeners.get(type) ?? new Set(); listeners.add(listener); this.listeners.set(type, listeners); } start(): void { this.state = "recording"; } stop(): void { if (FakeMediaRecorder.stopError !== undefined) throw FakeMediaRecorder.stopError; this.state = "inactive"; for (const listener of this.listeners.get("stop") ?? []) { if (typeof listener === "function") listener(new Event("stop")); else listener.handleEvent(new Event("stop")); } } } describe("browser recording", () => { let animationFrameCount = 0; beforeEach(() => { events.length = 0; vi.clearAllMocks(); FakeMediaRecorder.instances.length = 0; FakeMediaRecorder.supportedTypes = new Set(["video/webm;codecs=vp9"]); FakeMediaRecorder.outputMimeType = undefined; FakeMediaRecorder.stopError = undefined; clientSettings.browserRecordingFrameRate = 30; animationFrameCount = 0; vi.stubGlobal("window", globalThis); vi.stubGlobal("requestAnimationFrame", (callback: FrameRequestCallback) => { animationFrameCount += 1; callback(animationFrameCount); return animationFrameCount; }); vi.stubGlobal("cancelAnimationFrame", vi.fn()); vi.stubGlobal("MediaRecorder", FakeMediaRecorder as unknown as typeof MediaRecorder); getDisplayMedia.mockResolvedValue({ getVideoTracks: () => [], getTracks: () => [{ stop: vi.fn() }], }); requestDisplayMediaCapture.mockImplementation((tabId: string) => { const trigger = Reflect.get(globalThis, DESKTOP_PREVIEW_RECORDING_CAPTURE_TRIGGER); if (typeof trigger !== "function" || trigger(tabId) !== true) { throw new Error(`No pending display-media capture for ${tabId}.`); } }); vi.stubGlobal("navigator", { mediaDevices: { getDisplayMedia } }); useBrowserSurfaceStore.setState({ activityByTabId: {}, byTabId: {} }); }); afterEach(() => { vi.useRealTimers(); vi.unstubAllGlobals(); }); it("starts recording for a visible tab", async () => { await startBrowserRecording("recording-tab"); const startupEvents = [...events]; await stopBrowserRecording("recording-tab"); expect(startupEvents).toEqual(["publish:recording-tab", "start-screencast"]); }); it("routes gesture-free starts through the desktop capture trigger", async () => { await startBrowserRecording("automation-recording-tab"); expect(requestDisplayMediaCapture).toHaveBeenCalledWith("automation-recording-tab"); expect(getDisplayMedia).toHaveBeenCalledOnce(); await stopBrowserRecording("automation-recording-tab"); }); it("saves locally and releases capture before transferring the encoded recording once", async () => { const stopTrack = vi.fn(); getDisplayMedia.mockResolvedValue({ getVideoTracks: () => [], getTracks: () => [{ stop: stopTrack }], }); await startBrowserRecording("transfer-tab"); let finishUpload!: () => void; const uploaded = new Promise<void>((resolve) => { finishUpload = resolve; }); const transfer = vi.fn(async (artifact, blob: Blob) => { expect(save).toHaveBeenCalledOnce(); expect(stopTrack).toHaveBeenCalled(); expect(artifact.path).toBe("/tmp/recording-test.webm"); expect(blob.type).toBe("video/webm;codecs=vp9"); await uploaded; return "uploaded-recording"; }); const localStop = stopBrowserRecording("transfer-tab"); const firstStop = stopBrowserRecordingForUpload("transfer-tab", transfer); const secondStop = stopBrowserRecordingForUpload("transfer-tab", transfer); finishUpload(); expect(await firstStop).toEqual(await secondStop); expect((await firstStop)?.uploadedAttachmentId).toBe("uploaded-recording"); expect((await localStop)?.path).toBe("/tmp/recording-test.webm"); expect(transfer).toHaveBeenCalledOnce(); }); it("keeps the saved desktop file and releases the recording when transfer fails", async () => { await startBrowserRecording("failed-transfer-tab"); await expect( stopBrowserRecordingForUpload("failed-transfer-tab", async () => { throw new Error("Connection interrupted"); }), ).rejects.toThrow("Connection interrupted"); expect(save).toHaveBeenCalledOnce(); expect(readActiveBrowserRecordingTabIds().has("failed-transfer-tab")).toBe(false); await startBrowserRecording("failed-transfer-tab"); await stopBrowserRecording("failed-transfer-tab"); }); it("paints and holds a hidden browser surface for the recording lifetime", async () => { startScreencast.mockImplementationOnce(async (tabId: string) => { expect(animationFrameCount).toBe(2); expect(useBrowserSurfaceStore.getState().activityByTabId[tabId]).toBe(1); }); getDisplayMedia.mockImplementationOnce(async () => { expect(animationFrameCount).toBe(2); expect(useBrowserSurfaceStore.getState().activityByTabId["background-tab"]).toBe(1); return { getVideoTracks: () => [], getTracks: () => [{ stop: vi.fn() }] }; }); await startBrowserRecording("background-tab"); expect(useBrowserSurfaceStore.getState().activityByTabId["background-tab"]).toBe(1); await stopBrowserRecording("background-tab"); expect(useBrowserSurfaceStore.getState().activityByTabId["background-tab"]).toBeUndefined(); }); it("bounds compositor warmup when animation frames are paused", async () => { vi.useFakeTimers(); const cancelAnimationFrame = vi.fn(); vi.stubGlobal( "requestAnimationFrame", vi.fn(() => 42), ); vi.stubGlobal("cancelAnimationFrame", cancelAnimationFrame); const startPromise = startBrowserRecording("hidden-window-tab"); await vi.advanceTimersByTimeAsync(BROWSER_RECORDING_PAINT_SETTLE_TIMEOUT_MS); await startPromise; expect(cancelAnimationFrame).toHaveBeenCalledWith(42); await stopBrowserRecording("hidden-window-tab"); }); it.each([ { width: 1280, height: 720, frameRate: 60, bitrate: 2_764_800 }, { width: 320, height: 240, frameRate: 30, bitrate: 2_500_000 }, { width: 3840, height: 2160, frameRate: 60, bitrate: 24_883_200 }, { width: 7680, height: 4320, frameRate: 60, bitrate: 50_000_000 }, ])("records the native $width x $height stream at $frameRate fps", async (settings) => { const stopTrack = vi.fn(); const stream = { getVideoTracks: () => [{ getSettings: () => settings }], getTracks: () => [{ stop: stopTrack }], } as unknown as MediaStream; getDisplayMedia.mockResolvedValueOnce(stream); await startBrowserRecording("recording-tab"); expect(getDisplayMedia).toHaveBeenCalledWith({ audio: false, video: { frameRate: { ideal: 30, max: 30 } }, }); expect(FakeMediaRecorder.instances[0]?.stream).toBe(stream); expect(FakeMediaRecorder.instances[0]?.options?.videoBitsPerSecond).toBe(settings.bitrate); await stopBrowserRecording("recording-tab"); expect(stopTrack).toHaveBeenCalledOnce(); expect(stopTrack.mock.invocationCallOrder[0]).toBeLessThan(save.mock.invocationCallOrder[0]!); }); it("uses the configured recording frame rate", async () => { clientSettings.browserRecordingFrameRate = 60; await startBrowserRecording("recording-tab"); expect(getDisplayMedia).toHaveBeenCalledWith({ audio: false, video: { frameRate: { ideal: 60, max: 60 } }, }); await stopBrowserRecording("recording-tab"); }); it("clears a failed settings read before retrying recording", async () => { const tabId = "settings-read-failure-tab"; const error = new Error("Settings read failed"); vi.mocked(ensureClientSettingsHydrated).mockRejectedValueOnce(error); await expect(startBrowserRecording(tabId)).rejects.toBe(error); expect(readActiveBrowserRecordingTabIds()).toEqual(new Set()); expect(useBrowserSurfaceStore.getState().activityByTabId[tabId]).toBeUndefined(); expect(animationFrameCount).toBe(0); expect(startScreencast).not.toHaveBeenCalled(); expect(stopScreencast).not.toHaveBeenCalled(); expect(getDisplayMedia).not.toHaveBeenCalled(); expect(FakeMediaRecorder.instances).toHaveLength(0); clientSettings.browserRecordingFrameRate = 60; await startBrowserRecording(tabId); expect(getDisplayMedia).toHaveBeenCalledWith({ audio: false, video: { frameRate: { ideal: 60, max: 60 } }, }); await stopBrowserRecording(tabId); expect(startScreencast).toHaveBeenCalledOnce(); expect(readActiveBrowserRecordingTabIds()).toEqual(new Set()); expect(useBrowserSurfaceStore.getState().activityByTabId[tabId]).toBeUndefined(); }); it("stops the native stream when MediaRecorder cleanup fails", async () => { const stopTrack = vi.fn(); getDisplayMedia.mockResolvedValueOnce({ getVideoTracks: () => [], getTracks: () => [{ stop: stopTrack }], }); await startBrowserRecording("recording-tab"); FakeMediaRecorder.stopError = new Error("stop failed"); await expect(stopBrowserRecording("recording-tab")).rejects.toMatchObject({ operation: "cleanup", tabId: "recording-tab", }); expect(stopTrack).toHaveBeenCalledOnce(); }); it("uses the best supported encoder and saves the recorder's actual format", async () => { FakeMediaRecorder.supportedTypes = new Set([ "video/mp4;codecs=avc1", "video/mp4;codecs=avc1.42e01e", "video/webm;codecs=vp9", "video/webm;codecs=av1", ]); FakeMediaRecorder.outputMimeType = "video/webm;codecs=av01"; await startBrowserRecording("recording-tab"); await stopBrowserRecording("recording-tab"); expect(FakeMediaRecorder.instances[0]?.options).toEqual({ mimeType: "video/mp4;codecs=avc1", videoBitsPerSecond: 3_110_400, }); expect(save).toHaveBeenCalledWith( "recording-tab", "video/webm;codecs=av01", expect.any(Uint8Array), ); }); it("lets the browser select the format when no preferred encoding is supported", async () => { FakeMediaRecorder.supportedTypes = new Set(); FakeMediaRecorder.outputMimeType = "video/platform-default"; await startBrowserRecording("recording-tab"); await stopBrowserRecording("recording-tab"); expect(FakeMediaRecorder.instances[0]?.options).toEqual({ videoBitsPerSecond: 3_110_400 }); expect(save).toHaveBeenCalledWith( "recording-tab", "video/platform-default", expect.any(Uint8Array), ); }); it("reports when MediaRecorder provides no output format", async () => { FakeMediaRecorder.supportedTypes = new Set(); FakeMediaRecorder.outputMimeType = ""; await startBrowserRecording("recording-tab"); await expect(stopBrowserRecording("recording-tab")).rejects.toBeInstanceOf( BrowserRecordingFormatUnavailableError, ); expect(save).not.toHaveBeenCalled(); expect(readActiveBrowserRecordingTabIds()).toEqual(new Set()); }); it("releases the native capture lease when stream acquisition fails", async () => { getDisplayMedia.mockRejectedValueOnce(new Error("capture failed")); await expect(startBrowserRecording("recording-tab")).rejects.toMatchObject({ operation: "capture-media-stream", tabId: "recording-tab", }); expect(stopScreencast).toHaveBeenCalledWith("recording-tab"); expect(events.at(-1)).toBe("clear"); }); it("times out stalled stream acquisition and stops a late stream", async () => { vi.useFakeTimers(); let finishCapture!: (stream: MediaStream) => void; const stopTrack = vi.fn(); getDisplayMedia.mockImplementationOnce( () => new Promise<MediaStream>((resolve) => { finishCapture = resolve; }), ); const startPromise = startBrowserRecording("recording-tab"); await vi.waitFor(() => expect(getDisplayMedia).toHaveBeenCalledOnce()); const rejection = expect(startPromise).rejects.toMatchObject({ _tag: "BrowserRecordingCaptureTimeoutError", tabId: "recording-tab", timeoutMs: BROWSER_RECORDING_STARTUP_SETTLE_TIMEOUT_MS, }); await vi.advanceTimersByTimeAsync(BROWSER_RECORDING_STARTUP_SETTLE_TIMEOUT_MS); await rejection; await expect(startPromise).rejects.toBeInstanceOf(BrowserRecordingCaptureTimeoutError); expect(stopScreencast).toHaveBeenCalledWith("recording-tab"); expect(readActiveBrowserRecordingTabIds()).toEqual(new Set()); expect(useBrowserSurfaceStore.getState().activityByTabId["recording-tab"]).toBeUndefined(); finishCapture({ getVideoTracks: () => [], getTracks: () => [{ stop: stopTrack }], } as unknown as MediaStream); await vi.advanceTimersByTimeAsync(0); expect(stopTrack).toHaveBeenCalledOnce(); }); it("records separate tabs concurrently", async () => { const firstThreadRef = { environmentId: EnvironmentId.make("environment-recording"), threadId: ThreadId.make("thread-recording-first"), }; const secondThreadRef = { environmentId: EnvironmentId.make("environment-recording"), threadId: ThreadId.make("thread-recording-second"), }; await Promise.all([ startBrowserRecording("recording-tab", firstThreadRef), startBrowserRecording("recording-tab-2", secondThreadRef), ]); expect(startScreencast).toHaveBeenCalledTimes(2); expect(events).toContain("publish:recording-tab,recording-tab-2"); expect(readActiveBrowserRecordingTabIds()).toEqual( new Set(["recording-tab", "recording-tab-2"]), ); expect(readActiveBrowserRecordingTabIds(firstThreadRef)).toEqual(new Set(["recording-tab"])); expect(readActiveBrowserRecordingTabIds(secondThreadRef)).toEqual(new Set(["recording-tab-2"])); await stopBrowserRecording("recording-tab"); expect(readActiveBrowserRecordingTabIds()).toEqual(new Set(["recording-tab-2"])); await stopBrowserRecording("recording-tab-2"); expect(readActiveBrowserRecordingTabIds()).toEqual(new Set()); expect(save).toHaveBeenCalledTimes(2); }); it("serializes display media grants for concurrent recording starts", async () => { let finishFirstCapture!: (stream: MediaStream) => void; const stream = { getVideoTracks: () => [], getTracks: () => [{ stop: vi.fn() }], } as unknown as MediaStream; getDisplayMedia .mockImplementationOnce( () => new Promise<MediaStream>((resolve) => { finishFirstCapture = resolve; }), ) .mockResolvedValueOnce(stream); const firstStart = startBrowserRecording("recording-tab"); await vi.waitFor(() => expect(getDisplayMedia).toHaveBeenCalledOnce()); const secondStart = startBrowserRecording("recording-tab-2"); await vi.waitFor(() => expect(readActiveBrowserRecordingTabIds().size).toBe(2)); expect(startScreencast).toHaveBeenCalledTimes(1); finishFirstCapture(stream); await Promise.all([firstStart, secondStart]); expect(startScreencast.mock.calls).toEqual([["recording-tab"], ["recording-tab-2"]]); expect(getDisplayMedia).toHaveBeenCalledTimes(2); await Promise.all([ stopBrowserRecording("recording-tab"), stopBrowserRecording("recording-tab-2"),