import { expect, test } from "@playwright/test"; import type { AcceptanceResponse, ReviewListResponse, } from "../../src/api/workflow"; /** * Helper: mock all API routes needed to render the Chat view. */ function mockChatApis( page: import("@playwright/test").Page, overrides: { acceptance?: AcceptanceResponse; reviewQueue?: ReviewListResponse; } = {}, ) { const acceptance: AcceptanceResponse = overrides.acceptance ?? { can_accept: false, reasons: ["No test results recorded for the story."], warning: null, summary: { total: 0, passed: 0, failed: 0 }, missing_categories: ["unit", "integration"], }; const reviewQueue: ReviewListResponse = overrides.reviewQueue ?? { stories: [], }; return Promise.all([ page.route("**/api/projects", (route) => route.fulfill({ json: ["/tmp/test-project"] }), ), page.route("**/api/io/fs/home", (route) => route.fulfill({ json: "/tmp" })), page.route("**/api/project", (route) => { if (route.request().method() === "POST") { return route.fulfill({ json: "/tmp/test-project" }); } if (route.request().method() === "DELETE") { return route.fulfill({ json: true }); } return route.fulfill({ json: null }); }), page.route("**/api/ollama/models**", (route) => route.fulfill({ json: ["llama3.1"] }), ), page.route("**/api/anthropic/key/exists", (route) => route.fulfill({ json: false }), ), page.route("**/api/anthropic/models", (route) => route.fulfill({ json: [] }), ), page.route("**/api/model", (route) => { if (route.request().method() === "POST") { return route.fulfill({ json: true }); } return route.fulfill({ json: null }); }), page.route("**/api/workflow/acceptance", (route) => { if (route.request().url().includes("/ensure")) return route.fallback(); return route.fulfill({ json: acceptance }); }), page.route("**/api/workflow/review/all", (route) => route.fulfill({ json: reviewQueue }), ), page.route("**/api/workflow/acceptance/ensure", (route) => route.fulfill({ json: true }), ), page.route("**/api/io/fs/list/absolute**", (route) => route.fulfill({ json: [] }), ), ]); } async function openProject(page: import("@playwright/test").Page) { await page.goto("/"); await page.getByPlaceholder("/path/to/project").fill("/tmp/test-project"); await page.getByRole("button", { name: "Open Project" }).click(); await expect(page.getByText("Workflow Gates", { exact: true })).toBeVisible(); } test.describe("Coverage threshold (AC1)", () => { test("shows blocked status when coverage is below threshold", async ({ page, }) => { await mockChatApis(page, { acceptance: { can_accept: false, reasons: ["Coverage below threshold (55.0% < 80.0%)."], warning: null, summary: { total: 3, passed: 3, failed: 0 }, missing_categories: [], coverage_report: { current_percent: 55.0, threshold_percent: 80.0, baseline_percent: null, }, }, }); await openProject(page); await expect(page.getByText("Blocked").first()).toBeVisible(); await expect(page.getByText(/Coverage: 55\.0%/)).toBeVisible(); await expect(page.getByText(/threshold: 80\.0%/)).toBeVisible(); await expect( page.getByText("Coverage below threshold (55.0% < 80.0%)."), ).toBeVisible(); }); test("shows green coverage when above threshold", async ({ page }) => { await mockChatApis(page, { acceptance: { can_accept: true, reasons: [], warning: null, summary: { total: 5, passed: 5, failed: 0 }, missing_categories: [], coverage_report: { current_percent: 92.0, threshold_percent: 80.0, baseline_percent: 90.0, }, }, }); await openProject(page); await expect(page.getByText("Ready to accept")).toBeVisible(); await expect(page.getByText(/Coverage: 92\.0%/)).toBeVisible(); }); }); test.describe("Coverage regression reporting (AC2)", () => { test("review panel shows coverage regression reason", async ({ page }) => { await mockChatApis(page, { reviewQueue: { stories: [ { story_id: "27_protect_tests_and_coverage", can_accept: false, reasons: ["Coverage regression: 90.0% → 82.0% (threshold: 80.0%)."], warning: null, summary: { total: 4, passed: 4, failed: 0 }, missing_categories: [], coverage_report: { current_percent: 82.0, threshold_percent: 80.0, baseline_percent: 90.0, }, }, ], }, }); await openProject(page); await expect( page.getByText(/Coverage regression.*90\.0%.*82\.0%/), ).toBeVisible(); await expect(page.getByText(/Coverage: 82\.0%/)).toBeVisible(); const blockedButton = page.getByRole("button", { name: "Blocked" }); await expect(blockedButton).toBeDisabled(); }); test("blocked acceptance with coverage and test failures combined", async ({ page, }) => { await mockChatApis(page, { acceptance: { can_accept: false, reasons: [ "1 test(s) are failing; acceptance is blocked.", "Coverage below threshold (65.0% < 80.0%).", ], warning: null, summary: { total: 4, passed: 3, failed: 1 }, missing_categories: [], coverage_report: { current_percent: 65.0, threshold_percent: 80.0, baseline_percent: null, }, }, }); await openProject(page); await expect(page.getByText("Blocked").first()).toBeVisible(); await expect( page.getByText("Coverage below threshold (65.0% < 80.0%)."), ).toBeVisible(); await expect(page.getByText(/Coverage: 65\.0%/)).toBeVisible(); await expect(page.getByText(/1 test\(s\) are failing/)).toBeVisible(); }); }); test.describe("Coverage collection (E2E)", () => { test("collect coverage button triggers collection and displays result", async ({ page, }) => { await mockChatApis(page); // Mock the collect coverage endpoint await page.route("**/api/workflow/coverage/collect", (route) => route.fulfill({ json: { current_percent: 85.0, threshold_percent: 80.0, baseline_percent: null, }, }), ); await openProject(page); // Click the Collect Coverage button const collectButton = page.getByRole("button", { name: "Collect Coverage", }); await expect(collectButton).toBeVisible(); // Override acceptance to return coverage data after collection await page.unroute("**/api/workflow/acceptance"); await page.route("**/api/workflow/acceptance", (route) => { if (route.request().url().includes("/ensure")) return route.fallback(); return route.fulfill({ json: { can_accept: true, reasons: [], warning: null, summary: { total: 5, passed: 5, failed: 0 }, missing_categories: [], coverage_report: { current_percent: 85.0, threshold_percent: 80.0, baseline_percent: null, }, }, }); }); await collectButton.click(); await expect(page.getByText(/Coverage: 85\.0%/)).toBeVisible(); }); });