806 lines · 29.3 KB · TypeScript
RawDownload
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.