import { test, expect } from "@playwright/test"; import { execFileSync } from "node:child_process"; import { copyFileSync, existsSync, mkdirSync, readFileSync } from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; // --------------------------------------------------------------------------- // The clip bench. // // The fixture is built so every answer here is exact rather than plausible: // // vid1 is PUNCTUATED and carries a run-on cue at 3-6s. So c01 (3.00-6.00) // ends mid-sentence, and widen() must carry its end to 9.00 -- the next cue // that closes one. c02 (9.00-12.00) ends on a full stop. c04 ends inside a // run-on cue too but sets lockEnd, which is the author saying "I meant to cut // here" and must silence the warning. // // vid2 has no terminator anywhere, which is the real degradation in this // corpus. c03's edges cannot be judged against sentences at all, and the // bench has to SAY that rather than quietly claiming the cut is fine. // // vid1_0.00-9.00.mp4 is tone / silence / tone / silence / tone with the // silences centred on 3.0s and 6.0s. Verified in the file itself: // silencedetect reports 2.90-3.11 and 5.92-6.11. // // vid1 has THREE cached files, because clips-raw is keyed by video and a // report cites one stream more than once. c04 (15.00-18.00) is held only by // the NARROWEST of them, which is the whole point -- see the window-choice // test at the bottom. // --------------------------------------------------------------------------- const HERE = path.dirname(fileURLToPath(import.meta.url)); const UMTOOL = path.join(HERE, ".."); const FIXTURE = path.join(UMTOOL, ".e2e-song"); // bench-fixture, NOT report-fixture. Every test in this file WRITES, and the // index and decision specs assert what report-fixture's windows are -- sharing // one project made the suite pass or fail on which spec file ran first. const PROJECT = "reports/bench-fixture"; // The WALK's fixture, and read-only: see the block below, and the comment on // walk-fixture in make-fixture.mjs. const WALK = "reports/walk-fixture"; const MANIFEST = path.join(FIXTURE, "reports", "bench-fixture", "video.manifest.json"); const bench = (clip: string) => `/browse/${PROJECT}/clip/${clip}`; const walkBench = (clip: string) => `/browse/${WALK}/clip/${clip}`; const readClipIn = (project: string, id: string) => { const file = path.join(FIXTURE, ...project.split("/"), "video.manifest.json"); const m = JSON.parse(readFileSync(file, "utf8")) as { timeline: { id: string; start: number; end: number; lockEnd?: boolean; title?: string; date?: string; correction?: string; verdict?: string; cutStart?: number; cutEnd?: number; muteFrom?: number; }[]; }; return m.timeline.find((e) => e.id === id)!; }; const readClip = (id: string) => readClipIn(PROJECT, id); /** * Type into an attribution field and let it save the way a blur does. * * The fill is CHECKED before the blur, and that is not belt and braces. * `fill("")` is not one operation: playwright's injected script selects the * existing text and returns `needsinput`, and the driver then presses Delete in * a second round trip (playwright-core/lib/server/dom.js). A React re-render * landing between the two -- hydration, on a page that was just navigated to -- * reassigns the controlled value and COLLAPSES the selection to offset 0, so * Delete eats one character forward instead of the selection. The blur then * faithfully saves `value.slice(1)`, and the bench is innocent: it saved exactly * what the box held. */ const setField = async ( page: import("@playwright/test").Page, field: string, value: string, ) => { const input = page.locator(`[data-attrib-field=${field}]`); await expect(async () => { await input.fill(value); await expect(input).toHaveValue(value, { timeout: 1000 }); }).toPass({ timeout: 10_000 }); await input.blur(); }; /** * Wait for the bench's keyboard to be LIVE, and prove it with the harmless key. * * The listener is attached on hydration and a press that lands before it is * simply lost -- `page.goto` resolves long before React has run. `x` is the one * key that changes nothing on disk (it moves the cursor into the correction * box), so it doubles as the readiness probe for the keys that do. */ const keyboardLive = async (page: import("@playwright/test").Page) => { const box = page.locator("[data-attrib-field=correction]"); await expect(async () => { await page.locator("body").press("x"); await expect(box).toBeFocused({ timeout: 500 }); }).toPass({ timeout: 10_000 }); await box.blur(); }; const token = async ( request: { get: (u: string) => Promise<{ json: () => Promise }> }, clip: string, project = PROJECT, ) => { const r = await request.get(`/api/report/clip?project=${encodeURIComponent(project)}&clip=${clip}`); return (await r.json()) as { token: string }; }; test("the bench opens on the cached window, with the clip inside it", async ({ page }) => { await page.goto(bench("c01")); await expect(page.locator("[data-bench=c01]")).toBeVisible(); await expect(page.getByTestId("waveform")).toBeVisible(); // The