mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-04-22 23:17:17 +08:00
Add frontend tests (#22783)
* basic e2e frontend test framework * improve mock data generation and add test cases * more cases * add e2e tests to PR template * don't generate mock data in PR CI * satisfy codeql check * fix flaky system page tab tests by guarding against crashes from incomplete mock stats * reduce local test runs to 4 workers to match CI
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* WebSocket mock using Playwright's native page.routeWebSocket().
|
||||
*
|
||||
* Intercepts the app's WebSocket connection and simulates the Frigate
|
||||
* WS protocol: onConnect handshake, camera_activity expansion, and
|
||||
* topic-based state updates.
|
||||
*/
|
||||
|
||||
import type { Page, WebSocketRoute } from "@playwright/test";
|
||||
import { cameraActivityPayload } from "../fixtures/mock-data/camera-activity";
|
||||
|
||||
export class WsMocker {
|
||||
private mockWs: WebSocketRoute | null = null;
|
||||
private cameras: string[];
|
||||
|
||||
constructor(cameras: string[] = ["front_door", "backyard", "garage"]) {
|
||||
this.cameras = cameras;
|
||||
}
|
||||
|
||||
async install(page: Page) {
|
||||
await page.routeWebSocket("**/ws", (ws) => {
|
||||
this.mockWs = ws;
|
||||
|
||||
ws.onMessage((msg) => {
|
||||
this.handleClientMessage(msg.toString());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private handleClientMessage(raw: string) {
|
||||
let data: { topic: string; payload?: unknown; message?: string };
|
||||
try {
|
||||
data = JSON.parse(raw);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.topic === "onConnect") {
|
||||
// Send initial camera_activity state
|
||||
this.sendCameraActivity();
|
||||
|
||||
// Send initial stats
|
||||
this.send(
|
||||
"stats",
|
||||
JSON.stringify({
|
||||
cameras: Object.fromEntries(
|
||||
this.cameras.map((c) => [
|
||||
c,
|
||||
{
|
||||
camera_fps: 5,
|
||||
detection_fps: 5,
|
||||
process_fps: 5,
|
||||
skipped_fps: 0,
|
||||
detection_enabled: 1,
|
||||
connection_quality: "excellent",
|
||||
},
|
||||
]),
|
||||
),
|
||||
service: {
|
||||
last_updated: Date.now() / 1000,
|
||||
uptime: 86400,
|
||||
version: "0.15.0-test",
|
||||
latest_version: "0.15.0",
|
||||
storage: {},
|
||||
},
|
||||
detectors: {},
|
||||
cpu_usages: {},
|
||||
gpu_usages: {},
|
||||
camera_fps: 15,
|
||||
process_fps: 15,
|
||||
skipped_fps: 0,
|
||||
detection_fps: 15,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// Echo back state commands (e.g., modelState, jobState, etc.)
|
||||
if (data.topic === "modelState") {
|
||||
this.send("model_state", JSON.stringify({}));
|
||||
}
|
||||
if (data.topic === "embeddingsReindexProgress") {
|
||||
this.send("embeddings_reindex_progress", JSON.stringify(null));
|
||||
}
|
||||
if (data.topic === "birdseyeLayout") {
|
||||
this.send("birdseye_layout", JSON.stringify(null));
|
||||
}
|
||||
if (data.topic === "jobState") {
|
||||
this.send("job_state", JSON.stringify({}));
|
||||
}
|
||||
if (data.topic === "audioTranscriptionState") {
|
||||
this.send("audio_transcription_state", JSON.stringify("idle"));
|
||||
}
|
||||
|
||||
// Camera toggle commands: echo back the new state
|
||||
const toggleMatch = data.topic?.match(
|
||||
/^(.+)\/(detect|recordings|snapshots|audio|enabled|notifications|ptz_autotracker|review_alerts|review_detections|object_descriptions|review_descriptions|audio_transcription)\/set$/,
|
||||
);
|
||||
if (toggleMatch) {
|
||||
const [, camera, feature] = toggleMatch;
|
||||
this.send(`${camera}/${feature}/state`, data.payload);
|
||||
}
|
||||
}
|
||||
|
||||
/** Send a raw WS message to the app */
|
||||
send(topic: string, payload: unknown) {
|
||||
if (!this.mockWs) return;
|
||||
this.mockWs.send(JSON.stringify({ topic, payload }));
|
||||
}
|
||||
|
||||
/** Send camera_activity with default or custom state */
|
||||
sendCameraActivity(overrides?: Parameters<typeof cameraActivityPayload>[1]) {
|
||||
const payload = cameraActivityPayload(this.cameras, overrides);
|
||||
this.send("camera_activity", payload);
|
||||
}
|
||||
|
||||
/** Send a review update */
|
||||
sendReview(review: unknown) {
|
||||
this.send("reviews", JSON.stringify(review));
|
||||
}
|
||||
|
||||
/** Send an event update */
|
||||
sendEvent(event: unknown) {
|
||||
this.send("events", JSON.stringify(event));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user