import { expect, test, type Page } from "@playwright/test"; import { CHANNEL_SLUG, VIDEO_CHAT_SMALL, VIDEO_TRANSCRIPT_ONLY, borrowedDigestPage, channelDigestsManifest, digestPage, } from "./fixtures/data"; import { expectModalOpen, installRoutes, urlParams } from "./helpers"; test.use({ permissions: ["clipboard-read", "clipboard-write"], }); // Register the digest tree AFTER installRoutes so these take precedence over // its default 404s (the duplicates.json idiom). async function installDigestRoutes( page: Page, page0: unknown = digestPage(), ): Promise { await page.route(/\/digests\/[^/]+\/manifest\.json$/, async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify(channelDigestsManifest()), }); }); await page.route(/\/digests\/[^/]+\/page-\d+\.json$/, async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify(page0), }); }); } test.describe("video modal — AI digest panel", () => { test.beforeEach(async ({ page }) => { await installRoutes(page); }); test("control is hidden on a video with no digest", async ({ page }) => { await installDigestRoutes(page); // VIDEO_CHAT_SMALL is absent from the manifest's slugToPage. await page.goto(`/?v=${CHANNEL_SLUG}/${VIDEO_CHAT_SMALL}`); await expectModalOpen(page); await expect(page.getByText(/alpha line/)).toBeVisible(); await expect( page.getByRole("button", { name: "Show AI chapters" }), ).toHaveCount(0); }); test("control is hidden when the channel ships no digests at all", async ({ page, }) => { // No installDigestRoutes → the manifest 404s, as for most of the corpus. await page.goto(`/?v=${CHANNEL_SLUG}/${VIDEO_TRANSCRIPT_ONLY}`); await expectModalOpen(page); await expect( page.getByRole("button", { name: "Show AI chapters" }), ).toHaveCount(0); }); test("toggle sets vm=digest and back, touching nothing else", async ({ page, }) => { await installDigestRoutes(page); await page.goto( `/?m=subs&tk=foo&v=${CHANNEL_SLUG}/${VIDEO_TRANSCRIPT_ONLY}`, ); await expectModalOpen(page); const toggle = page.getByRole("button", { name: "Show AI chapters" }); await expect(toggle).toBeVisible(); await toggle.click(); await expect(page.getByText("The main argument")).toBeVisible(); let params = await urlParams(page); expect(params.get("vm")).toBe("digest"); expect(params.get("m")).toBe("subs"); expect(params.get("tk")).toBe("foo"); // Back to the transcript — vm is DELETED, not set to "transcript". await page.getByRole("button", { name: "Show transcript" }).click(); await expect(page.getByText(/alpha line/)).toBeVisible(); params = await urlParams(page); expect(params.get("vm")).toBeNull(); }); test("deep link with ?vm=digest opens straight into the panel", async ({ page, }) => { await installDigestRoutes(page); await page.goto( `/?v=${CHANNEL_SLUG}/${VIDEO_TRANSCRIPT_ONLY}&vm=digest`, ); await expectModalOpen(page); await expect(page.getByText("Opening remarks")).toBeVisible(); await expect(page.getByText("The main argument")).toBeVisible(); // Topic tags and the provenance line ship alongside the chapters. // Exact, because the provenance sentence also contains the word "topics". await expect(page.getByText("Topics", { exact: true })).toBeVisible(); await expect(page.getByText(/qwen2\.5:7b/)).toBeVisible(); }); test("a chapter click seeks the player to that moment", async ({ page }) => { await installDigestRoutes(page); await page.goto(`/?v=${CHANNEL_SLUG}/${VIDEO_TRANSCRIPT_ONLY}&vm=digest`); await expectModalOpen(page); // The playhead starts at 0, before the first chapter's 00:05 — so nothing // is active yet. (A chapter list need not start at zero.) await expect( page.getByRole("button", { name: /Opening remarks/ }), ).not.toHaveAttribute("aria-current", "true"); // Click the 00:50 chapter; the active-chapter marker follows the playhead, // which is what proves the seek took effect. await page.getByRole("button", { name: /The main argument/ }).click(); await expect( page.getByRole("button", { name: /The main argument/ }), ).toHaveAttribute("aria-current", "true"); // And back — the marker tracks the player, it isn't just a click highlight. await page.getByRole("button", { name: /Opening remarks/ }).click(); await expect( page.getByRole("button", { name: /Opening remarks/ }), ).toHaveAttribute("aria-current", "true"); await expect( page.getByRole("button", { name: /The main argument/ }), ).not.toHaveAttribute("aria-current", "true"); }); test("share link carries the digest panel", async ({ page, context }) => { await context.grantPermissions(["clipboard-read", "clipboard-write"]); await installDigestRoutes(page); await page.goto( `/?v=${CHANNEL_SLUG}/${VIDEO_TRANSCRIPT_ONLY}&vm=digest&t=50`, ); await expectModalOpen(page); await expect(page.getByText("The main argument")).toBeVisible(); await page .getByRole("button", { name: "Copy share link at current time" }) .click(); const url = new URL( await page.evaluate(() => navigator.clipboard.readText()), ); expect(url.searchParams.get("vm")).toBe("digest"); expect(url.searchParams.get("t")).toBe("50"); }); test("warnings never reach the viewer", async ({ page }) => { await installDigestRoutes(page); await page.goto(`/?v=${CHANNEL_SLUG}/${VIDEO_TRANSCRIPT_ONLY}&vm=digest`); await expectModalOpen(page); await expect(page.getByText("Opening remarks")).toBeVisible(); // Operator telemetry (out-of-range clamps etc.) is for the editor panel and // the review queue, not for readers. await expect(page.getByText(/out-of-range/)).toHaveCount(0); }); test("a borrowed digest is presented as borrowed", async ({ page }) => { await installDigestRoutes(page, borrowedDigestPage()); await page.goto(`/?v=${CHANNEL_SLUG}/${VIDEO_TRANSCRIPT_ONLY}&vm=digest`); await expectModalOpen(page); // The failure mode this guards against looks like success: plausible // chapters that describe a different upload. await expect(page.getByText("Borrowed from a duplicate upload")).toBeVisible(); await expect(page.getByText(/other-channel\/vid-canonical/)).toBeVisible(); }); test("snaps back to the transcript when the page is unreachable", async ({ page, }) => { // Manifest says the video is digested, but the page fetch fails. await page.route(/\/digests\/[^/]+\/manifest\.json$/, async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify(channelDigestsManifest()), }); }); await page.route(/\/digests\/[^/]+\/page-\d+\.json$/, async (route) => { await route.fulfill({ status: 500, body: "boom" }); }); await page.goto(`/?v=${CHANNEL_SLUG}/${VIDEO_TRANSCRIPT_ONLY}&vm=digest`); await expectModalOpen(page); await expect(page.getByRole("status")).toContainText( /Couldn't load the AI digest/, ); await expect(page.getByText(/alpha line/)).toBeVisible(); const params = await urlParams(page); expect(params.get("vm")).toBeNull(); }); });