// /jobs: a job holding a running scheduler slot that has gone quiet // ("possibly-stalled") is surfaced as STUCK on its own row — the registry-status // filter the live list used to apply would otherwise hide it — and Force-release // frees the slot. The stuck slot is fabricated deterministically via the // /api/test harness (see app/api/test/stuck-job) rather than racing a real // wedged child. import { test, expect } from "@playwright/test"; import { resetData } from "./helpers"; import { baseUrl } from "./baseUrl"; async function fabricateStuckJob(queueKey: string): Promise { const res = await fetch( `${baseUrl}/api/test/stuck-job?queue=${encodeURIComponent(queueKey)}`, ); const body = (await res.json()) as { id: string }; return body.id; } test("a stuck slot shows on /jobs and Force-release clears it", async ({ page, }) => { await resetData(null); const id = await fabricateStuckJob("qStuck"); await page.goto("/jobs"); // Health line renders with the stuck count emphasized. await expect(page.getByRole("heading", { name: "Jobs", level: 1 })).toBeVisible(); await expect(page.getByText("stuck", { exact: true })).toBeVisible(); // The fabricated slot has its own row, flagged stuck. const row = page.locator(`tr[data-job-id="${id}"]`); await expect(row).toBeVisible(); await expect(row.getByText(/possibly-stalled/)).toBeVisible(); await expect(page.locator('[data-stat="stuck"]')).toContainText("1"); // Force-release it (accept the confirm) — the slot is freed and the badge goes. page.on("dialog", (d) => d.accept()); await row.getByRole("button", { name: /^Force-release$/ }).click(); // forceRelease marks the record cancelled and stamps endedAt, so the row stays // in the live head as a freshly-finished job for another half-minute. await expect(row.getByText(/possibly-stalled/)).toHaveCount(0, { timeout: 10_000, }); await expect(row).toContainText("cancelled"); await expect(page.locator('[data-stat="stuck"]')).toContainText("0"); }); test("loads with an empty queue and renders the health line", async ({ page, }) => { await resetData(null); await page.goto("/jobs"); await expect(page.getByRole("heading", { name: "Jobs", level: 1 })).toBeVisible(); await expect(page.getByText("active queues", { exact: true })).toBeVisible(); await expect(page.locator('[data-stat="queues"]')).toContainText("0"); await expect(page.getByText("No jobs have run yet.")).toBeVisible(); });