import { readFile } from "node:fs/promises"; import { fileURLToPath } from "node:url"; import path from "node:path"; import { test, expect } from "@playwright/test"; import { channelStage, generateReport, pathExists, readJson, resetData, writeSettings, } from "./helpers"; const CHANNEL = "test-livechat"; const ROOT = `test-transcripts/channels/${CHANNEL}`; const here = path.dirname(fileURLToPath(import.meta.url)); type DownloadOutcome = { status: string; attempts: Array<{ kind: string; handling: string }>; fellBackToTranscribe?: boolean; }; async function defaultSettings(): Promise> { const raw = await readFile( path.join(here, "fixtures", "test-settings.default.json"), "utf8", ); return JSON.parse(raw) as Record; } test("default off: live_chat-only video downloads audio but defers whisper", async ({ page, }) => { test.setTimeout(120_000); await resetData("livechat-fallback-channel"); await generateReport(page, CHANNEL); await page.goto(channelStage(CHANNEL, "download")); await page.getByRole("button", { name: "Download videos" }).click(); const log = page.getByLabel("Download videos output"); await expect(log).toContainText( "No subs available for livechatonly1; falling back to audio download + whisper.", { timeout: 60_000 }, ); await expect(log).toContainText( "Audio downloaded for livechatonly1; skipping inline whisper", { timeout: 60_000 }, ); await expect(log).toContainText("Managed download complete", { timeout: 60_000, }); expect( await pathExists( `${ROOT}/data/livechatonly1/transcript.live_chat.json`, ), ).toBe(true); expect(await pathExists(`${ROOT}/data/livechatonly1/audio.mp3`)).toBe(true); expect( await pathExists(`${ROOT}/data/livechatonly1/transcript.json`), ).toBe(false); const outcome = await readJson( `${ROOT}/data/livechatonly1/download-outcome.json`, ); expect(outcome.status).toBe("ok"); expect(outcome.fellBackToTranscribe).toBe(true); expect( outcome.attempts.some( (a) => a.kind === "no-subs-fallback" && a.handling === "transcribe", ), ).toBe(true); // The fallback should reuse the metadata.info.json the primary attempt // wrote, not re-query the extractor. const invocations = await readFile( path.join(here, "..", ROOT, "fake-ytdlp.invocations"), "utf8", ); expect(invocations).toMatch( /load-info-json:.*data\/livechatonly1\/metadata\.info\.json/, ); }); test("default off: no-captions video downloads audio but defers whisper", async ({ page, }) => { test.setTimeout(120_000); await resetData("livechat-fallback-channel"); await generateReport(page, CHANNEL); await page.goto(channelStage(CHANNEL, "download")); await page.getByRole("button", { name: "Download videos" }).click(); const log = page.getByLabel("Download videos output"); await expect(log).toContainText( "No subs available for nocaptions001; falling back to audio download + whisper.", { timeout: 60_000 }, ); await expect(log).toContainText( "Audio downloaded for nocaptions001; skipping inline whisper", { timeout: 60_000 }, ); expect(await pathExists(`${ROOT}/data/nocaptions001/audio.mp3`)).toBe(true); expect( await pathExists(`${ROOT}/data/nocaptions001/transcript.json`), ).toBe(false); const outcome = await readJson( `${ROOT}/data/nocaptions001/download-outcome.json`, ); expect(outcome.status).toBe("ok"); expect(outcome.fellBackToTranscribe).toBe(true); }); test("video with real auto-subs does NOT trigger fallback (regression)", async ({ page, }) => { test.setTimeout(120_000); await resetData("livechat-fallback-channel"); await generateReport(page, CHANNEL); await page.goto(channelStage(CHANNEL, "download")); await page.getByRole("button", { name: "Download videos" }).click(); const log = page.getByLabel("Download videos output"); await expect(log).toContainText("Managed download complete", { timeout: 60_000, }); await expect(log).not.toContainText("No subs available for normalvideo1"); expect( await pathExists(`${ROOT}/data/normalvideo1/transcript.en.vtt`), ).toBe(true); expect(await pathExists(`${ROOT}/data/normalvideo1/audio.mp3`)).toBe(false); }); test("inlineTranscribeOnFallback=true runs whisper inline after the fallback", async ({ page, }) => { test.setTimeout(120_000); await resetData("livechat-fallback-channel"); await writeSettings({ ...(await defaultSettings()), inlineTranscribeOnFallback: true, }); await generateReport(page, CHANNEL); await page.goto(channelStage(CHANNEL, "download")); await page.getByRole("button", { name: "Download videos" }).click(); const log = page.getByLabel("Download videos output"); await expect(log).toContainText( "No subs available for livechatonly1; falling back to audio download + whisper.", { timeout: 60_000 }, ); await expect(log).not.toContainText( "Audio downloaded for livechatonly1; skipping inline whisper", ); await expect(log).toContainText("Managed download complete", { timeout: 60_000, }); expect(await pathExists(`${ROOT}/data/livechatonly1/audio.mp3`)).toBe(true); expect( await pathExists(`${ROOT}/data/livechatonly1/transcript.json`), ).toBe(true); const outcome = await readJson( `${ROOT}/data/livechatonly1/download-outcome.json`, ); expect(outcome.status).toBe("ok-auto-transcribed"); expect(outcome.fellBackToTranscribe).toBe(true); });