import { test, expect, type APIRequestContext, type Page } from "@playwright/test"; import { readFileSync } from "node:fs"; import path from "node:path"; import { caps, NO_ASR_CORPUS } from "./capabilities"; // THE WHOLE FILE SKIPS WITHOUT THE CORPUS. Everything below derives its // expected answer from what the corpus actually returned, so with no `asr/` // there is nothing to derive from: the console is empty and every assertion is // either vacuous or red. File-scope `test.skip` rather than ten per-test ones, // because "this file needs the ASR corpus" is one fact, and the header below // already says it. test.skip(!caps.asr, NO_ASR_CORPUS); // The phrase console, against the REAL ASR corpus -- the suite symlinks asr/ // rather than synthesising one, because 300 files of genuine parakeet output is // the only thing that exercises the chunk seams, the multi-word tokens and the // confidence spread this page is built around. // // That means no assertion here may hardcode a count. Every one is a SHAPE, a // MECHANISM, or an INVARIANT with a true answer whatever the corpus holds -- // derived at runtime from what the corpus actually returned. A test that said // `the` is 23,081 hits would go red the first time an episode was re-cut, and // would have been testing the corpus rather than the code. const CHUNK_STEP = 237; const CHUNK_OVERLAP = 3.5; const LEAD_PAD = 0.35; type Hit = { id: string; video: string; start: number; from: number; suspect: boolean; after: string; text: string; confMin: number | null; }; type Result = { hits: Hit[]; total: number; collapsed: number; suppressed: number; }; const find = async (request: APIRequestContext, qs: string): Promise => { const r = await request.get(`/api/phrases?${qs}`); expect(r.ok(), `GET /api/phrases?${qs} -> ${r.status()}`).toBe(true); return (await r.json()) as Result; }; /** The word the whole suite hangs off. Common enough to page, in every episode. */ const COMMON = "the"; const attr = async (page: Page, sel: string, name: string): Promise => (await page.locator(sel).first().getAttribute(name)) ?? ""; // --------------------------------------------------------------------------- // 1. The route itself. // --------------------------------------------------------------------------- // A STATIC segment inside app/browse/[song]/'s territory. When this regresses // the page still renders perfectly on its own and only the URL stops working, // which is the failure mode that goes unnoticed longest. test("/browse/find is not swallowed by the [song] route", async ({ page }) => { const res = await page.goto("/browse/find"); expect(res?.status()).toBe(200); await expect(page.getByRole("navigation", { name: "Breadcrumb" })).toContainText("find"); await expect(page.locator("#phrase-q")).toBeVisible(); }); // --------------------------------------------------------------------------- // 2. No query builds no index. // --------------------------------------------------------------------------- // Building 105 MB of blob to draw an empty search box would be the whole cost // of the feature paid for nothing. This reads what THIS RENDER built, not what // happens to be in memory -- otherwise it would pass or fail on test order. test("an empty console does not build the index", async ({ page }) => { await page.goto("/browse/find"); expect(await attr(page, "[data-find]", "data-corpus-built")).toBe("0"); await page.goto(`/browse/find?q=${COMMON}`); expect(Number(await attr(page, "[data-find]", "data-corpus-built"))).toBeGreaterThan(0); }); // --------------------------------------------------------------------------- // 3-5. What a page of results is, and what a row promises. // --------------------------------------------------------------------------- test("paging is honest: same total, disjoint pages, more held than shown", async ({ page }) => { await page.goto(`/browse/find?q=${COMMON}`); const total = await attr(page, "[data-find]", "data-total"); const shown = await attr(page, "[data-find]", "data-shown"); expect(Number(total)).toBeGreaterThan(Number(shown)); const ids1 = await page.locator("[data-hit]").evaluateAll((els) => els.map((e) => e.getAttribute("data-hit")), ); await page.goto(`/browse/find?q=${COMMON}&page=2`); expect(await attr(page, "[data-find]", "data-total")).toBe(total); const ids2 = await page.locator("[data-hit]").evaluateAll((els) => els.map((e) => e.getAttribute("data-hit")), ); expect(ids2.length).toBeGreaterThan(0); const first = new Set(ids1); expect(ids2.filter((id) => first.has(id))).toEqual([]); }); // A hit's identity is `