mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-06-21 03:41:55 +03:00
188 lines
6.4 KiB
TypeScript
188 lines
6.4 KiB
TypeScript
/**
|
|
* Add-camera wizard — PTZ controls pane.
|
|
*
|
|
* The pane lives on Step 3 (Stream Configuration) and only appears when the
|
|
* ONVIF probe from Step 2 reported `ptz_supported`. These tests drive the
|
|
* wizard to Step 3 with a mocked probe and assert the pane's contract:
|
|
* 1. Visible + enabled when the probe reports PTZ, with the connection
|
|
* fields collapsed by default and pre-filled once expanded.
|
|
* 2. Toggling the switch off hides the disclosure and its fields.
|
|
* 3. Clearing the host shows the validation warning and blocks "Next".
|
|
* 4. Absent entirely when the probe reports no PTZ support.
|
|
*
|
|
* The save path (writing the `onvif` section to config/set) runs through
|
|
* Step 4's live-validation flow, which registers go2rtc streams and renders
|
|
* MSE previews that are unreliable in headless Chromium. Consistent with
|
|
* clone-camera.spec.ts, that assertion is deferred to manual QA; the logic is
|
|
* covered by the Step 4 -> parent handleSave wiring.
|
|
*/
|
|
|
|
import { test, expect } from "../../fixtures/frigate-test";
|
|
import type { Page, Locator } from "@playwright/test";
|
|
|
|
const PTZ_PROBE = {
|
|
success: true,
|
|
host: "192.168.1.100",
|
|
port: 80,
|
|
manufacturer: "Acme",
|
|
model: "PTZ-1",
|
|
firmware_version: "1.0",
|
|
profiles_count: 1,
|
|
ptz_supported: true,
|
|
pan_tilt_supported: true,
|
|
presets_count: 2,
|
|
autotrack_supported: false,
|
|
rtsp_candidates: [
|
|
{
|
|
source: "GetStreamUri",
|
|
profile_token: "profile_1",
|
|
uri: "rtsp://admin:pw@192.168.1.100:554/stream1",
|
|
},
|
|
],
|
|
};
|
|
|
|
async function mockProbe(page: Page, probe: object) {
|
|
await page.route("**/api/onvif/probe**", (route) =>
|
|
route.fulfill({ json: probe }),
|
|
);
|
|
}
|
|
|
|
/** Open the wizard and drive Step 1 -> Step 2 -> Step 3. */
|
|
async function gotoStep3(page: Page, host = "192.168.1.100") {
|
|
await page.getByRole("button", { name: /Add New Camera/i }).click();
|
|
const dialog = page.getByRole("dialog");
|
|
await expect(dialog).toBeVisible();
|
|
|
|
// Step 1: name + host (probe mode is the default), then Continue.
|
|
await dialog.getByPlaceholder(/front_door/i).fill("ptz_test_camera");
|
|
await dialog.getByPlaceholder("192.168.1.100").fill(host);
|
|
await dialog.getByRole("button", { name: /^Continue$/i }).click();
|
|
|
|
// Step 2: the probe auto-runs on mount; once candidates exist, Next enables.
|
|
const next = dialog.getByRole("button", { name: /^Next$/i });
|
|
await expect(next).toBeEnabled({ timeout: 10_000 });
|
|
await next.click();
|
|
|
|
// Step 3 is the stream-configuration step; key off a stable control rather
|
|
// than the description text (which has been reworded).
|
|
await expect(
|
|
dialog.getByRole("button", { name: /Add Another Stream/i }),
|
|
).toBeVisible();
|
|
return dialog;
|
|
}
|
|
|
|
/** The PTZ enable switch (scoped to the PTZ card header row). */
|
|
function ptzSwitch(dialog: Locator) {
|
|
return dialog
|
|
.locator("div.justify-between", { hasText: "Enable PTZ Controls" })
|
|
.getByRole("switch");
|
|
}
|
|
|
|
/** Expand the (collapsed-by-default) ONVIF connection-detail fields. */
|
|
async function expandOnvifDetails(dialog: Locator) {
|
|
await dialog
|
|
.getByRole("button", { name: /ONVIF connection details/i })
|
|
.click();
|
|
}
|
|
|
|
test.describe("Camera wizard PTZ pane @medium @mobile", () => {
|
|
test.beforeEach(async ({ frigateApp }) => {
|
|
// not in the default mock; unmocked it 500s and trips the error collector
|
|
await frigateApp.page.route("**/api/config/raw_paths", (route) =>
|
|
route.fulfill({ json: {} }),
|
|
);
|
|
await frigateApp.goto("/settings?page=cameraManagement");
|
|
await expect(
|
|
frigateApp.page.getByRole("heading", { name: /Manage Cameras/i }),
|
|
).toBeVisible();
|
|
});
|
|
|
|
test("shows an enabled PTZ pane with collapsed, pre-filled fields", async ({
|
|
frigateApp,
|
|
}) => {
|
|
await mockProbe(frigateApp.page, PTZ_PROBE);
|
|
const dialog = await gotoStep3(frigateApp.page);
|
|
|
|
// Card is present with the detected note and the switch defaults on.
|
|
await expect(
|
|
dialog.getByText("Enable PTZ Controls", { exact: true }),
|
|
).toBeVisible();
|
|
await expect(
|
|
dialog.getByText(/PTZ support has been detected via ONVIF/i),
|
|
).toBeVisible();
|
|
await expect(ptzSwitch(dialog)).toBeChecked();
|
|
|
|
// Connection fields are collapsed by default.
|
|
await expect(dialog.getByPlaceholder("192.168.1.100")).toHaveCount(0);
|
|
|
|
// Expanding reveals the host pre-filled from the probe.
|
|
await expandOnvifDetails(dialog);
|
|
await expect(dialog.getByPlaceholder("192.168.1.100")).toHaveValue(
|
|
"192.168.1.100",
|
|
);
|
|
});
|
|
|
|
test("toggling the switch off hides the disclosure and fields", async ({
|
|
frigateApp,
|
|
}) => {
|
|
await mockProbe(frigateApp.page, PTZ_PROBE);
|
|
const dialog = await gotoStep3(frigateApp.page);
|
|
|
|
await expandOnvifDetails(dialog);
|
|
await expect(dialog.getByPlaceholder("192.168.1.100")).toBeVisible();
|
|
|
|
await ptzSwitch(dialog).click();
|
|
|
|
await expect(dialog.getByPlaceholder("192.168.1.100")).toHaveCount(0);
|
|
await expect(
|
|
dialog.getByRole("button", { name: /ONVIF connection details/i }),
|
|
).toHaveCount(0);
|
|
});
|
|
|
|
test("clearing the host shows the warning and blocks Next", async ({
|
|
frigateApp,
|
|
}) => {
|
|
await mockProbe(frigateApp.page, PTZ_PROBE);
|
|
const dialog = await gotoStep3(frigateApp.page);
|
|
|
|
await expandOnvifDetails(dialog);
|
|
await dialog.getByPlaceholder("192.168.1.100").fill("");
|
|
|
|
await expect(
|
|
dialog.getByText(/An ONVIF host and port are required/i),
|
|
).toBeVisible();
|
|
await expect(
|
|
dialog.getByRole("button", { name: /^Next$/i }),
|
|
).toBeDisabled();
|
|
});
|
|
|
|
test("shows the pane but leaves the switch off without continuous pan/tilt", async ({
|
|
frigateApp,
|
|
}) => {
|
|
// e.g. a varifocal camera: PTZ service present, but zoom/focus only
|
|
await mockProbe(frigateApp.page, {
|
|
...PTZ_PROBE,
|
|
pan_tilt_supported: false,
|
|
});
|
|
const dialog = await gotoStep3(frigateApp.page);
|
|
|
|
await expect(
|
|
dialog.getByText("Enable PTZ Controls", { exact: true }),
|
|
).toBeVisible();
|
|
await expect(ptzSwitch(dialog)).not.toBeChecked();
|
|
// with the switch off, the connection fields are not shown
|
|
await expect(dialog.getByPlaceholder("192.168.1.100")).toHaveCount(0);
|
|
});
|
|
|
|
test("hides the PTZ pane when the probe reports no PTZ support", async ({
|
|
frigateApp,
|
|
}) => {
|
|
await mockProbe(frigateApp.page, { ...PTZ_PROBE, ptz_supported: false });
|
|
const dialog = await gotoStep3(frigateApp.page);
|
|
|
|
await expect(
|
|
dialog.getByText("Enable PTZ Controls", { exact: true }),
|
|
).toHaveCount(0);
|
|
});
|
|
});
|