Files
frigate/web/e2e/specs/system.spec.ts
T
Josh Hawkins 962d36323b Improve frontend e2e tests (#22958)
* add mock data

* add helpers

* page objects

* updated specs

* remove PENDING_REWARITE

* formatting
2026-04-21 16:32:18 -06:00

238 lines
7.8 KiB
TypeScript

/**
* System page tests -- MEDIUM tier (promoted to cover migrated
* RestartDialog test from radix-overlay-regressions.spec.ts).
*
* Tab switching, version + last-refreshed display, and the
* RestartDialog cancel flow.
*/
import { test, expect } from "../fixtures/frigate-test";
import {
expectBodyInteractive,
waitForBodyInteractive,
} from "../helpers/overlay-interaction";
test.describe("System — tabs @medium", () => {
test("general tab is active by default via #general hash", async ({
frigateApp,
}) => {
await frigateApp.goto("/system#general");
await expect(frigateApp.page.getByLabel("Select general")).toHaveAttribute(
"data-state",
"on",
{ timeout: 15_000 },
);
await expect(frigateApp.page.getByLabel("Select storage")).toBeVisible();
await expect(frigateApp.page.getByLabel("Select cameras")).toBeVisible();
});
test("Storage tab activates and deactivates General", async ({
frigateApp,
}) => {
await frigateApp.goto("/system#general");
await expect(frigateApp.page.getByLabel("Select general")).toHaveAttribute(
"data-state",
"on",
{ timeout: 15_000 },
);
await frigateApp.page.getByLabel("Select storage").click();
await expect(frigateApp.page.getByLabel("Select storage")).toHaveAttribute(
"data-state",
"on",
{ timeout: 5_000 },
);
await expect(frigateApp.page.getByLabel("Select general")).toHaveAttribute(
"data-state",
"off",
);
});
test("Cameras tab activates", async ({ frigateApp }) => {
await frigateApp.goto("/system#general");
await expect(frigateApp.page.getByLabel("Select general")).toHaveAttribute(
"data-state",
"on",
{ timeout: 15_000 },
);
await frigateApp.page.getByLabel("Select cameras").click();
await expect(frigateApp.page.getByLabel("Select cameras")).toHaveAttribute(
"data-state",
"on",
{ timeout: 5_000 },
);
});
test("general tab shows version and last-refreshed", async ({
frigateApp,
}) => {
await frigateApp.goto("/system#general");
await expect(frigateApp.page.getByLabel("Select general")).toHaveAttribute(
"data-state",
"on",
{ timeout: 15_000 },
);
await expect(frigateApp.page.getByText("0.15.0-test")).toBeVisible();
await expect(frigateApp.page.getByText(/Last refreshed/)).toBeVisible();
});
test("storage tab renders content after switching", async ({
frigateApp,
}) => {
await frigateApp.goto("/system#general");
await expect(frigateApp.page.getByLabel("Select general")).toHaveAttribute(
"data-state",
"on",
{ timeout: 15_000 },
);
await frigateApp.page.getByLabel("Select storage").click();
await expect(frigateApp.page.getByLabel("Select storage")).toHaveAttribute(
"data-state",
"on",
{ timeout: 5_000 },
);
// On desktop, tab buttons render text labels so the word "storage"
// always appears in #pageRoot after switching. On mobile, tabs are
// icon-only, so we verify the general-tab content disappears instead
// (the storage tab's metrics section is hidden but general is gone).
if (!frigateApp.isMobile) {
await expect
.poll(
async () => (await frigateApp.page.textContent("#pageRoot")) ?? "",
{ timeout: 10_000 },
)
.toMatch(/storage|mount|disk|used|free/i);
} else {
// Mobile: tab activation (data-state "on") already asserted above.
// Additionally confirm general tab is no longer the active tab.
await expect(
frigateApp.page.getByLabel("Select general"),
).toHaveAttribute("data-state", "off", { timeout: 5_000 });
}
});
test("cameras tab renders each configured camera", async ({ frigateApp }) => {
await frigateApp.goto("/system#general");
await expect(frigateApp.page.getByLabel("Select general")).toHaveAttribute(
"data-state",
"on",
{ timeout: 15_000 },
);
await frigateApp.page.getByLabel("Select cameras").click();
await expect(frigateApp.page.getByLabel("Select cameras")).toHaveAttribute(
"data-state",
"on",
{ timeout: 5_000 },
);
// Cameras tab lists every camera from config/stats. The default
// mock has front_door, backyard, garage.
for (const cam of ["front_door", "backyard", "garage"]) {
await expect(
frigateApp.page
.getByText(new RegExp(cam.replace("_", ".?"), "i"))
.first(),
).toBeVisible({ timeout: 10_000 });
}
});
test("enrichments tab renders when semantic search is enabled", async ({
frigateApp,
}) => {
// Override config to guarantee the enrichments tab is present.
// System.tsx shows the tab when semantic_search.enabled === true.
await frigateApp.installDefaults({
config: { semantic_search: { enabled: true } },
});
await frigateApp.goto("/system#general");
await expect(frigateApp.page.getByLabel("Select general")).toHaveAttribute(
"data-state",
"on",
{ timeout: 15_000 },
);
const enrichTab = frigateApp.page.getByLabel(/select enrichments/i).first();
await expect(enrichTab).toBeVisible({ timeout: 5_000 });
await enrichTab.click();
await expect(enrichTab).toHaveAttribute("data-state", "on", {
timeout: 5_000,
});
});
});
test.describe("System — RestartDialog @medium", () => {
test.skip(
({ frigateApp }) => frigateApp.isMobile,
"Sidebar menu is desktop-only",
);
test("cancelling restart leaves body interactive", async ({ frigateApp }) => {
// Migrated from radix-overlay-regressions.spec.ts.
await frigateApp.goto("/");
const sidebarTriggers = frigateApp.page
.locator('[role="complementary"] [aria-haspopup="menu"]')
.or(frigateApp.page.locator('aside [aria-haspopup="menu"]'));
const triggerCount = await sidebarTriggers.count();
expect(triggerCount).toBeGreaterThan(0);
let opened = false;
for (let i = 0; i < triggerCount; i++) {
const trigger = sidebarTriggers.nth(i);
await trigger.click().catch(() => {});
const restartItem = frigateApp.page
.getByRole("menuitem", { name: /restart/i })
.first();
const visible = await expect(restartItem)
.toBeVisible({ timeout: 300 })
.then(() => true)
.catch(() => false);
if (visible) {
await restartItem.click();
opened = true;
break;
}
await frigateApp.page.keyboard.press("Escape").catch(() => {});
}
expect(opened).toBe(true);
const cancel = frigateApp.page.getByRole("button", { name: /cancel/i });
await expect(cancel).toBeVisible({ timeout: 3_000 });
await cancel.click();
await waitForBodyInteractive(frigateApp.page);
await expectBodyInteractive(frigateApp.page);
const postCancelTrigger = sidebarTriggers.first();
await postCancelTrigger.click();
await expect(
frigateApp.page
.locator('[role="menu"], [data-radix-menu-content]')
.first(),
).toBeVisible({ timeout: 3_000 });
});
});
test.describe("System — mobile @medium @mobile", () => {
test.skip(({ frigateApp }) => !frigateApp.isMobile, "Mobile-only");
test("tabs render at mobile viewport", async ({ frigateApp }) => {
await frigateApp.goto("/system#general");
await expect(frigateApp.page.getByLabel("Select general")).toBeVisible({
timeout: 15_000,
});
});
test("switching tabs works at mobile viewport", async ({ frigateApp }) => {
await frigateApp.goto("/system#general");
await expect(frigateApp.page.getByLabel("Select general")).toHaveAttribute(
"data-state",
"on",
{ timeout: 15_000 },
);
await frigateApp.page.getByLabel("Select storage").click();
await expect(frigateApp.page.getByLabel("Select storage")).toHaveAttribute(
"data-state",
"on",
{ timeout: 5_000 },
);
});
});