mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-07-15 00:11:15 +03:00
252 lines
7.8 KiB
TypeScript
252 lines
7.8 KiB
TypeScript
/**
|
|
* Export page tests — deepened per the test deepening spec.
|
|
*
|
|
* Covers the deepening checklist:
|
|
* 1. Clean render (auto via error fixture)
|
|
* 2. Mobile + desktop parity (@mobile test)
|
|
* 3. Primary interactions: search, delete, case open, case close
|
|
* 4. Empty / loading / error states (via mock-overrides)
|
|
* 5. No lenient waits — only real conditions
|
|
* 6. Data-driven assertions against fixtures/mock-data/exports.json
|
|
*/
|
|
|
|
import { test, expect } from "../fixtures/frigate-test";
|
|
import { mockEmpty, mockError, mockDelay } from "../helpers/mock-overrides";
|
|
|
|
test.describe("Export Page — Happy Path @high", () => {
|
|
test("renders unassigned exports in the main list", async ({
|
|
frigateApp,
|
|
}) => {
|
|
await frigateApp.goto("/export");
|
|
|
|
// export-001 (Front Door) and export-003 (Garage) are unassigned — visible
|
|
// in the main list. export-002 (Backyard) belongs to case-001 and appears
|
|
// only inside the case detail view, not in the top-level list.
|
|
await expect(
|
|
frigateApp.page.getByText("Front Door - Person Alert"),
|
|
).toBeVisible();
|
|
await expect(
|
|
frigateApp.page.getByText("Garage - In Progress"),
|
|
).toBeVisible();
|
|
});
|
|
|
|
test("renders the case card from mock data", async ({ frigateApp }) => {
|
|
await frigateApp.goto("/export");
|
|
await expect(
|
|
frigateApp.page.getByText("Package Theft Investigation"),
|
|
).toBeVisible();
|
|
});
|
|
|
|
test("clicking a case opens its detail view", async ({ frigateApp }) => {
|
|
await frigateApp.goto("/export");
|
|
await frigateApp.page
|
|
.getByText("Package Theft Investigation")
|
|
.first()
|
|
.click();
|
|
|
|
await expect(
|
|
frigateApp.page.getByRole("heading", {
|
|
name: "Package Theft Investigation",
|
|
}),
|
|
).toBeVisible();
|
|
await expect(
|
|
frigateApp.page.getByText("Backyard - Car Detection"),
|
|
).toBeVisible();
|
|
});
|
|
});
|
|
|
|
test.describe("Export Page — Search @high", () => {
|
|
test("search filter narrows the list to matching exports", async ({
|
|
frigateApp,
|
|
}) => {
|
|
await frigateApp.goto("/export");
|
|
await expect(
|
|
frigateApp.page.getByText("Front Door - Person Alert"),
|
|
).toBeVisible();
|
|
|
|
const search = frigateApp.page.getByPlaceholder(/search/i).first();
|
|
await search.fill("Front Door");
|
|
|
|
await expect(
|
|
frigateApp.page.getByText("Front Door - Person Alert"),
|
|
).toBeVisible();
|
|
// Garage (unassigned) and Package Theft Investigation (case) are filtered out
|
|
await expect(
|
|
frigateApp.page.getByText("Garage - In Progress"),
|
|
).toBeHidden();
|
|
await expect(
|
|
frigateApp.page.getByText("Package Theft Investigation"),
|
|
).toBeHidden();
|
|
});
|
|
|
|
test("clearing the search restores all exports", async ({ frigateApp }) => {
|
|
await frigateApp.goto("/export");
|
|
await expect(
|
|
frigateApp.page.getByText("Front Door - Person Alert"),
|
|
).toBeVisible();
|
|
|
|
const search = frigateApp.page.getByPlaceholder(/search/i).first();
|
|
await search.fill("Front Door");
|
|
await expect(
|
|
frigateApp.page.getByText("Garage - In Progress"),
|
|
).toBeHidden();
|
|
|
|
await search.fill("");
|
|
await expect(
|
|
frigateApp.page.getByText("Garage - In Progress"),
|
|
).toBeVisible();
|
|
});
|
|
});
|
|
|
|
test.describe("Export Page — Delete @high", () => {
|
|
test("clicking delete on a card opens the confirmation dialog", async ({
|
|
frigateApp,
|
|
}) => {
|
|
await frigateApp.goto("/export");
|
|
await expect(
|
|
frigateApp.page.getByText("Front Door - Person Alert"),
|
|
).toBeVisible();
|
|
|
|
// Find the card containing "Front Door - Person Alert" and open its menu.
|
|
// The menu trigger is an "Edit name" BlurredIconButton rendered inside
|
|
// each card. Since all three cards share this label, we scope the
|
|
// button query to the card that contains the matching text.
|
|
const cardMenuTrigger = frigateApp.page
|
|
.getByRole("button", { name: "Edit name" })
|
|
.first();
|
|
await cardMenuTrigger.click();
|
|
|
|
// Click the Delete export menu item
|
|
await frigateApp.page
|
|
.getByRole("menuitem", { name: "Delete export" })
|
|
.click();
|
|
|
|
// Confirmation alert dialog visible with the Delete Export button
|
|
// (capital E — distinct from the lowercase-e menu item)
|
|
await expect(
|
|
frigateApp.page.getByRole("button", { name: "Delete Export" }),
|
|
).toBeVisible();
|
|
});
|
|
|
|
test("cancelling the delete dialog leaves the card in place", async ({
|
|
frigateApp,
|
|
}) => {
|
|
await frigateApp.goto("/export");
|
|
await expect(
|
|
frigateApp.page.getByText("Front Door - Person Alert"),
|
|
).toBeVisible();
|
|
|
|
await frigateApp.page
|
|
.getByRole("button", { name: "Edit name" })
|
|
.first()
|
|
.click();
|
|
await frigateApp.page
|
|
.getByRole("menuitem", { name: "Delete export" })
|
|
.click();
|
|
|
|
await frigateApp.page.getByRole("button", { name: /^cancel$/i }).click();
|
|
|
|
await expect(
|
|
frigateApp.page.getByRole("button", { name: "Delete Export" }),
|
|
).toBeHidden();
|
|
await expect(
|
|
frigateApp.page.getByText("Front Door - Person Alert"),
|
|
).toBeVisible();
|
|
});
|
|
});
|
|
|
|
test.describe("Export Page — States @high", () => {
|
|
test("empty state renders when there are no exports or cases", async ({
|
|
page,
|
|
frigateApp,
|
|
}) => {
|
|
await mockEmpty(page, "**/api/exports**");
|
|
await mockEmpty(page, "**/api/cases**");
|
|
await frigateApp.goto("/export");
|
|
|
|
await expect(frigateApp.page.getByText("No exports found")).toBeVisible();
|
|
});
|
|
|
|
test("loading state — empty list resolves after delay", async ({
|
|
page,
|
|
frigateApp,
|
|
}) => {
|
|
await mockDelay(page, "**/api/exports**", 500, []);
|
|
await mockDelay(page, "**/api/cases**", 500, []);
|
|
await frigateApp.goto("/export");
|
|
|
|
await expect(frigateApp.page.locator("#pageRoot")).toBeVisible();
|
|
await expect(frigateApp.page.getByText("No exports found")).toBeVisible({
|
|
timeout: 5_000,
|
|
});
|
|
});
|
|
});
|
|
|
|
test.describe("Export Page — Error State @high", () => {
|
|
// The exports endpoint returning 500 will be logged by SWR. Use a single
|
|
// alternation regex per Playwright's isFixtureTuple collision.
|
|
test.use({
|
|
expectedErrors: [/500.*\/api\/exports|Failed to load resource.*500/],
|
|
});
|
|
|
|
test("API error does not crash the page", async ({ page, frigateApp }) => {
|
|
await mockError(page, "**/api/exports**");
|
|
await frigateApp.goto("/export");
|
|
|
|
// Page must still mount — the search input is rendered before data loads
|
|
await expect(
|
|
frigateApp.page.getByPlaceholder(/search/i).first(),
|
|
).toBeVisible();
|
|
});
|
|
});
|
|
|
|
test.describe("Export Page — Mobile @high @mobile", () => {
|
|
test("mobile viewport renders the export list and opens video pane", async ({
|
|
frigateApp,
|
|
}) => {
|
|
test.skip(!frigateApp.isMobile, "Mobile-only assertion");
|
|
|
|
await frigateApp.goto("/export");
|
|
await expect(
|
|
frigateApp.page.getByText("Front Door - Person Alert"),
|
|
).toBeVisible();
|
|
|
|
// Click the export card to open the video dialog. This is the exact
|
|
// code path that was silently broken for weeks before this spec existed.
|
|
await frigateApp.page
|
|
.getByText("Front Door - Person Alert")
|
|
.first()
|
|
.click();
|
|
|
|
// The dialog mounts with the export's name as title
|
|
await expect(
|
|
frigateApp.page
|
|
.getByRole("dialog")
|
|
.filter({ hasText: "Front Door - Person Alert" }),
|
|
).toBeVisible();
|
|
|
|
// The video element inside the dialog is present
|
|
await expect(
|
|
frigateApp.page.locator('[role="dialog"] video'),
|
|
).toBeVisible();
|
|
});
|
|
|
|
test("mobile viewport renders cases and opens case detail", async ({
|
|
frigateApp,
|
|
}) => {
|
|
test.skip(!frigateApp.isMobile, "Mobile-only assertion");
|
|
|
|
await frigateApp.goto("/export");
|
|
await frigateApp.page
|
|
.getByText("Package Theft Investigation")
|
|
.first()
|
|
.click();
|
|
|
|
await expect(
|
|
frigateApp.page.getByRole("heading", {
|
|
name: "Package Theft Investigation",
|
|
}),
|
|
).toBeVisible();
|
|
});
|
|
});
|