From ea0d92ea55d7463a992b2293d95af9849ab0f9ed Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Sat, 24 Oct 2026 22:25:52 -0500 Subject: [PATCH] add viewer export test and remove stale non-admin case text --- web/e2e/specs/export.spec.ts | 29 +++++++++++++++++++ web/public/locales/en/components/dialog.json | 1 - .../components/overlay/MultiExportDialog.tsx | 7 +---- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/web/e2e/specs/export.spec.ts b/web/e2e/specs/export.spec.ts index f167e16177..8328ab5993 100644 --- a/web/e2e/specs/export.spec.ts +++ b/web/e2e/specs/export.spec.ts @@ -1,4 +1,5 @@ import { test, expect, type FrigateApp } from "../fixtures/frigate-test"; +import { viewerProfile } from "../fixtures/mock-data/profile"; import { expectBodyInteractive, waitForBodyInteractive, @@ -1040,6 +1041,34 @@ test.describe("Multi-Camera Export from History @high", () => { await expect.poll(() => capturedPayload).not.toBeNull(); expect(typeof capturedPayload!.items[0].end_time).toBe("number"); }); + + test("viewers can export without a case", async ({ frigateApp }) => { + await frigateApp.installDefaults({ profile: viewerProfile() }); + + let capturedPayload: Record | null = null; + await frigateApp.page.route("**/api/exports/batch", async (route) => { + capturedPayload = route.request().postDataJSON(); + await route.fulfill({ + status: 202, + json: { export_case_id: null, export_ids: [], results: [] }, + }); + }); + + const form = await openMultiCameraTab(frigateApp); + + const firstCamera = form.locator("button[aria-pressed]").first(); + await expect(firstCamera).toBeVisible({ timeout: 5_000 }); + if ((await firstCamera.getAttribute("aria-pressed")) === "false") { + await firstCamera.click(); + } + + await expect(form.getByPlaceholder(/new case name/i)).toBeHidden(); + await form.getByRole("button", { name: /^export \d+ cameras?$/i }).click(); + + await expect.poll(() => capturedPayload).not.toBeNull(); + expect(capturedPayload).not.toHaveProperty("new_case_name"); + expect(capturedPayload).not.toHaveProperty("export_case_id"); + }); }); test.describe("Export Page - Active Job Progress @medium", () => { diff --git a/web/public/locales/en/components/dialog.json b/web/public/locales/en/components/dialog.json index d50f08670f..8ef88ad1e6 100644 --- a/web/public/locales/en/components/dialog.json +++ b/web/public/locales/en/components/dialog.json @@ -66,7 +66,6 @@ "newCaseNamePlaceholder": "New case name", "newCaseDescriptionPlaceholder": "Case description", "label": "Case", - "nonAdminHelp": "A new case will be created for these exports.", "placeholder": "Select a case", "nameRequired": "Case name is required" }, diff --git a/web/src/components/overlay/MultiExportDialog.tsx b/web/src/components/overlay/MultiExportDialog.tsx index 4b89e90c69..ef4d00407a 100644 --- a/web/src/components/overlay/MultiExportDialog.tsx +++ b/web/src/components/overlay/MultiExportDialog.tsx @@ -68,9 +68,7 @@ export default function MultiExportDialog({ const isAdmin = useIsAdmin(); const { data: config } = useSWR("config"); - // Only admins can attach exports to an existing case (enforced server-side - // by POST /exports/batch). Skip fetching the case list entirely for - // non-admins — they can only ever use the "Create new case" branch. + // Only admins can assign exports to a case, so non-admins skip the case list const { data: cases } = useSWR(isAdmin ? "cases" : null); const [open, setOpen] = useState(false); @@ -284,9 +282,6 @@ export default function MultiExportDialog({ t, ]); - // New-case inputs: rendered below the Select when caseSelection === "new", - // or rendered standalone for non-admins (who never see the Select since - // they cannot attach to an existing case). const newCaseInputs = (