import { test, expect } from "@playwright/test"; import { channelStage, generateReport, readJson, resetData, } from "./helpers"; test("treats yt-dlp exit code 101 as success and updates lastSyncedAt", async ({ page, }) => { await resetData("sync-already-archived"); await generateReport(page, "archived-channel"); await page.goto(channelStage("archived-channel", "playlist")); await page.getByRole("button", { name: "Sync" }).click(); await expect(page.getByLabel("Sync output")).toContainText("exit 101", { timeout: 15_000, }); // Run button re-enabled with original label, no Cancel/error visible. const syncButton = page.getByRole("button", { name: /^Sync$/ }); await expect(syncButton).toBeEnabled(); await expect( page.getByRole("button", { name: "Cancel Sync" }), ).not.toBeVisible(); await expect(page.getByLabel("Sync error")).not.toBeVisible(); // Config now has lastSyncedAt set to a parseable ISO string. const config = await readJson<{ lastSyncedAt?: string }>( "test-transcripts/channels/archived-channel/config.json", ); expect(typeof config.lastSyncedAt).toBe("string"); expect(Number.isNaN(Date.parse(config.lastSyncedAt!))).toBe(false); // The job lands as `done`, not `failed`. await page.goto("/jobs"); const syncRow = page .getByRole("row") .filter({ hasText: "archived-channel" }) .first(); // data-kind, not the row text: the cell renders the label "Sync", so // toContainText("sync") is a case-sensitive miss — a deterministic failure // that was long mis-recorded as an order-dependent flake. await expect(syncRow).toHaveAttribute("data-kind", "sync"); await expect(syncRow).toContainText("done"); });