mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-08-02 09:02:15 +03:00
Improve frontend test framework (#22824)
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
CI / AMD64 Build (push) Waiting to run
* add error allowlist file for error collector * add error collector for console + page + request errors * wire error collector into frigateApp fixture * add self-tests for error collector fixture * gate strict error mode on E2E_STRICT_ERRORS=1 * triage pre-existing errors and seed allowlist * add mockEmpty/mockError/mockDelay helpers for state-driven tests * add self-tests for mock override helpers * add mobile affordance helpers to BasePage * add lint script for banned spec patterns and @mobile rule * apply prettier fixes to new e2e files * rewrite export.spec.ts * clean up * move export spec rewrite and bugfix to separate branch
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* Self-tests for the error collector fixture itself.
|
||||
*
|
||||
* These guard against future regressions in the safety net. Each test
|
||||
* deliberately triggers (or avoids triggering) an error to verify the
|
||||
* collector behaves correctly. Tests that expect to fail use the
|
||||
* `expectedErrors` fixture parameter to allowlist their own errors.
|
||||
*/
|
||||
|
||||
import { test, expect } from "../../fixtures/frigate-test";
|
||||
|
||||
// test.use applies to a whole describe block in Playwright, so each test
|
||||
// that needs a custom allowlist gets its own describe.
|
||||
|
||||
test.describe("Error Collector — clean @meta", () => {
|
||||
test("clean page passes", async ({ frigateApp }) => {
|
||||
await frigateApp.goto("/");
|
||||
// No errors triggered. The fixture teardown should not throw.
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Error Collector — unallowlisted console error fails @meta", () => {
|
||||
test("console.error fails the test when not allowlisted", async ({
|
||||
page,
|
||||
frigateApp,
|
||||
}) => {
|
||||
test.skip(
|
||||
process.env.E2E_STRICT_ERRORS !== "1",
|
||||
"Requires E2E_STRICT_ERRORS=1 to assert failure",
|
||||
);
|
||||
test.fail(); // We expect the fixture teardown to throw
|
||||
await frigateApp.goto("/");
|
||||
await page.evaluate(() => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("UNEXPECTED_DELIBERATE_TEST_ERROR_xyz123");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Error Collector — allowlisted console error passes @meta", () => {
|
||||
test.use({ expectedErrors: [/ALLOWED_DELIBERATE_TEST_ERROR_xyz123/] });
|
||||
|
||||
test("console.error is silenced when allowlisted via expectedErrors", async ({
|
||||
page,
|
||||
frigateApp,
|
||||
}) => {
|
||||
await frigateApp.goto("/");
|
||||
await page.evaluate(() => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("ALLOWED_DELIBERATE_TEST_ERROR_xyz123");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Error Collector — uncaught pageerror fails @meta", () => {
|
||||
test("uncaught pageerror fails the test", async ({ page, frigateApp }) => {
|
||||
test.skip(
|
||||
process.env.E2E_STRICT_ERRORS !== "1",
|
||||
"Requires E2E_STRICT_ERRORS=1 to assert failure",
|
||||
);
|
||||
test.fail();
|
||||
await frigateApp.goto("/");
|
||||
await page.evaluate(() => {
|
||||
setTimeout(() => {
|
||||
throw new Error("UNCAUGHT_DELIBERATE_TEST_ERROR_xyz789");
|
||||
}, 0);
|
||||
});
|
||||
// Wait a frame to let the throw propagate before fixture teardown.
|
||||
// The marker below silences the e2e:lint banned-pattern check on this line.
|
||||
await page.waitForTimeout(100); // e2e-lint-allow: deliberate; need to await async throw
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Error Collector — 5xx fails @meta", () => {
|
||||
test("same-origin 5xx response fails the test", async ({
|
||||
page,
|
||||
frigateApp,
|
||||
}) => {
|
||||
test.skip(
|
||||
process.env.E2E_STRICT_ERRORS !== "1",
|
||||
"Requires E2E_STRICT_ERRORS=1 to assert failure",
|
||||
);
|
||||
test.fail();
|
||||
await page.route("**/api/version", (route) =>
|
||||
route.fulfill({ status: 500, body: "boom" }),
|
||||
);
|
||||
await frigateApp.goto("/");
|
||||
await page.evaluate(() => fetch("/api/version").catch(() => {}));
|
||||
// Give the response listener a microtask to fire
|
||||
await expect.poll(async () => true).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Error Collector — allowlisted 5xx passes @meta", () => {
|
||||
// Use a single alternation regex so test.use() receives a 1-element array.
|
||||
// Playwright's isFixtureTuple() treats any [value, object] pair as a fixture
|
||||
// tuple, so a 2-element array whose second item is a RegExp would be
|
||||
// misinterpreted as [defaultValue, options]. Both the request collector
|
||||
// error ("500 … /api/version") and the browser console error
|
||||
// ("Failed to load resource … 500") are matched by the alternation below.
|
||||
test.use({
|
||||
expectedErrors: [/500.*\/api\/version|Failed to load resource.*500/],
|
||||
});
|
||||
|
||||
test("allowlisted 5xx passes", async ({ page, frigateApp }) => {
|
||||
await page.route("**/api/version", (route) =>
|
||||
route.fulfill({ status: 500, body: "boom" }),
|
||||
);
|
||||
await frigateApp.goto("/");
|
||||
await page.evaluate(() => fetch("/api/version").catch(() => {}));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Self-tests for the mock override helpers. Verifies each helper
|
||||
* intercepts the matched URL and returns the expected payload/status.
|
||||
*/
|
||||
|
||||
import { test, expect } from "../../fixtures/frigate-test";
|
||||
import { mockEmpty, mockError, mockDelay } from "../../helpers/mock-overrides";
|
||||
|
||||
test.describe("Mock Overrides — empty @meta", () => {
|
||||
test("mockEmpty returns []", async ({ page, frigateApp }) => {
|
||||
await mockEmpty(page, "**/api/__meta_test__");
|
||||
await frigateApp.goto("/");
|
||||
const result = await page.evaluate(async () => {
|
||||
const r = await fetch("/api/__meta_test__");
|
||||
return { status: r.status, body: await r.json() };
|
||||
});
|
||||
expect(result.status).toBe(200);
|
||||
expect(result.body).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Mock Overrides — error default @meta", () => {
|
||||
// Match both the collected request error and the browser's console echo.
|
||||
// Using a single alternation regex avoids Playwright's isFixtureTuple
|
||||
// collision with multi-element RegExp arrays.
|
||||
test.use({
|
||||
expectedErrors: [/500.*__meta_test__|Failed to load resource.*500/],
|
||||
});
|
||||
|
||||
test("mockError returns 500 by default", async ({ page, frigateApp }) => {
|
||||
await mockError(page, "**/api/__meta_test__");
|
||||
await frigateApp.goto("/");
|
||||
const status = await page.evaluate(async () => {
|
||||
const r = await fetch("/api/__meta_test__");
|
||||
return r.status;
|
||||
});
|
||||
expect(status).toBe(500);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Mock Overrides — error custom status @meta", () => {
|
||||
// The browser emits a "Failed to load resource" console.error for 404s,
|
||||
// which the error collector catches even though 404 is not a 5xx.
|
||||
test.use({
|
||||
expectedErrors: [/Failed to load resource.*404|404.*__meta_test_404__/],
|
||||
});
|
||||
|
||||
test("mockError accepts a custom status", async ({ page, frigateApp }) => {
|
||||
await mockError(page, "**/api/__meta_test_404__", 404);
|
||||
await frigateApp.goto("/");
|
||||
const status = await page.evaluate(async () => {
|
||||
const r = await fetch("/api/__meta_test_404__");
|
||||
return r.status;
|
||||
});
|
||||
expect(status).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Mock Overrides — delay @meta", () => {
|
||||
test("mockDelay delays response by the requested ms", async ({
|
||||
page,
|
||||
frigateApp,
|
||||
}) => {
|
||||
await mockDelay(page, "**/api/__meta_test_delay__", 300, ["delayed"]);
|
||||
await frigateApp.goto("/");
|
||||
const elapsed = await page.evaluate(async () => {
|
||||
const start = performance.now();
|
||||
await fetch("/api/__meta_test_delay__");
|
||||
return performance.now() - start;
|
||||
});
|
||||
expect(elapsed).toBeGreaterThanOrEqual(250);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user