share the setup wizard e2e helpers and mock users

This commit is contained in:
Josh Hawkins
2026-08-26 11:53:05 -05:00
parent c7a0c426ca
commit e0a869de25
3 changed files with 63 additions and 47 deletions
+11
View File
@@ -47,6 +47,7 @@ export interface ApiMockOverrides {
recommended: string;
available?: { key: string; presets: Record<string, string> }[];
};
users?: { username: string; role: string }[];
}
export class ApiMocker {
@@ -200,6 +201,16 @@ export class ApiMocker {
}),
);
// Users. GET lists them; POST/PUT (create, password) just succeed, so
// tests assert on the intercepted request body instead of a response.
await this.page.route("**/api/users**", (route) =>
route.request().method() === "GET"
? route.fulfill({
json: overrides?.users ?? [{ username: "admin", role: "admin" }],
})
: route.fulfill({ json: { message: "ok" } }),
);
// Go2RTC streams
await this.page.route("**/api/go2rtc/streams**", (route) =>
route.fulfill({ json: {} }),
+51
View File
@@ -0,0 +1,51 @@
/**
* Shared setup-wizard e2e helpers.
*
* The wizard shows when config has no cameras, so a first run is mocked by
* serving a camera-less config until the returned callback is fired.
*/
import type { Page } from "@playwright/test";
import { expect } from "../fixtures/frigate-test";
import { configFactory } from "../fixtures/mock-data/config";
import type { ApiMockOverrides } from "./api-mocker";
export async function installFirstRun(
frigateApp: { installDefaults: (o?: ApiMockOverrides) => Promise<void> },
page: Page,
overrides?: ApiMockOverrides,
): Promise<() => void> {
await frigateApp.installDefaults(overrides);
const full = configFactory(overrides?.config);
let cameras: unknown = {};
await page.route("**/api/config", (route) => {
if (route.request().method() === "GET") {
return route.fulfill({ json: { ...full, cameras } });
}
return route.fulfill({ json: { success: true } });
});
return () => {
cameras = full.cameras;
};
}
export async function gotoDetectorStep(page: Page, addCamera: () => void) {
await page.getByRole("button", { name: "Get Started" }).click();
await expect(page.getByText("Add Your First Camera")).toBeVisible();
// the camera step's Next only renders once its local addedCameras fills,
// which SetupCamera does by refetching config when the dialog closes, so
// opening and cancelling the dialog is what reveals Next
await page.getByRole("button", { name: "Add Camera" }).click();
addCamera();
await page.getByRole("button", { name: "Cancel" }).click();
await expect(page.getByRole("button", { name: "Next" })).toBeVisible({
timeout: 10_000,
});
await page.getByRole("button", { name: "Next" }).click();
await expect(page.getByText("Object Detection")).toBeVisible();
}
+1 -47
View File
@@ -9,8 +9,7 @@
import { test, expect } from "../../fixtures/frigate-test";
import type { Page } from "@playwright/test";
import { configFactory } from "../../fixtures/mock-data/config";
import type { ApiMockOverrides } from "../../helpers/api-mocker";
import { gotoDetectorStep, installFirstRun } from "../../helpers/setup-wizard";
const NVIDIA_HARDWARE = [
{
@@ -57,51 +56,6 @@ async function captureRestarts(page: Page): Promise<string[]> {
return calls;
}
/**
* Install mocks for a first run: the wizard shows when config has no cameras,
* so the config route starts empty and only reports cameras once the returned
* callback is fired, standing in for the camera the user adds.
*/
async function installFirstRun(
frigateApp: { installDefaults: (o?: ApiMockOverrides) => Promise<void> },
page: Page,
overrides?: ApiMockOverrides,
): Promise<() => void> {
await frigateApp.installDefaults(overrides);
const full = configFactory(overrides?.config);
let cameras: unknown = {};
await page.route("**/api/config", (route) => {
if (route.request().method() === "GET") {
return route.fulfill({ json: { ...full, cameras } });
}
return route.fulfill({ json: { success: true } });
});
return () => {
cameras = full.cameras;
};
}
async function gotoDetectorStep(page: Page, addCamera: () => void) {
await page.getByRole("button", { name: "Get Started" }).click();
await expect(page.getByText("Add Your First Camera")).toBeVisible();
// the camera step's Next only renders once its local addedCameras fills,
// which SetupCamera does by refetching config when the dialog closes, so
// opening and cancelling the dialog is what reveals Next
await page.getByRole("button", { name: "Add Camera" }).click();
addCamera();
await page.getByRole("button", { name: "Cancel" }).click();
await expect(page.getByRole("button", { name: "Next" })).toBeVisible({
timeout: 10_000,
});
await page.getByRole("button", { name: "Next" }).click();
await expect(page.getByText("Object Detection")).toBeVisible();
}
test.describe("setup wizard hardware @high @mobile", () => {
test("lists probed hardware and writes a models config", async ({
frigateApp,