import { expect, test, type Page } from "@playwright/test"; import type { AutoModeState } from "../src/shared/api/autonomyApi"; const stateFor = (project: string): AutoModeState => ({ version: 1, enabled: false, status: "off", autoBrainstorm: true, maxBrainstormIterations: 5, maxConsecutiveFailures: 3, leaseMinutes: 30, maxWorkItemsPerRun: 8, maxRunMinutes: 45, runs: [], events: [], updatedAt: new Date().toISOString(), updatedBy: "test", roadmap: { id: project, title: `${project} roadmap`, sourceMarkdown: `## ${project}\n- [ ] ${project} task`, status: "active", currentPhaseIndex: 0, importedAt: new Date().toISOString(), phases: [ { id: `${project}-phase`, title: project, objective: "Finish work", status: "active", brainstormIterations: 0, brainstormConverged: false, validationFailures: 0, items: [{ id: `${project}-task`, title: `${project} task`, status: "pending", attempts: 1 }] }, ] }, }); const envelope = (data: unknown) => ({ data, meta: { requestId: "platform-state", timestamp: new Date().toISOString() } }); const navigateWithoutReload = async (page: Page, path: string) => page.evaluate((next) => { window.history.pushState({}, "", next); window.dispatchEvent(new PopStateEvent("popstate")); }, path); const openAutoAdvancedSettings = async (page: Page) => { const advanced = page.getByText("Autonomiebeleid en geavanceerde instellingen", { exact: true }); if (await advanced.locator("xpath=..").getAttribute("open") === null) await advanced.click(); }; const openManualRoadmapImport = async (page: Page) => { await openAutoAdvancedSettings(page); await expect(page.getByText("Roadmap handmatig importeren", { exact: true })).toBeVisible(); }; const openAutoAdvancedLimits = async (page: Page) => { await openAutoAdvancedSettings(page); const advancedLimits = page.getByText("Geavanceerde grenzen", { exact: true }); if (await advancedLimits.locator("xpath=..").getAttribute("open") === null) await advancedLimits.click(); }; test("project switches never carry an instruction into another project", async ({ page }) => { await page.goto("/projects/widefrog/workspace"); await page.getByLabel("Jouw opdracht", { exact: true }).fill("Only change WideFrog onboarding"); await navigateWithoutReload(page, "/projects/nebula/workspace"); await expect(page.getByLabel("Jouw opdracht", { exact: true })).toHaveValue(""); await expect(page.getByRole("button", { name: "Aan ProjectBrain geven" })).toBeDisabled(); }); test("project switches discard old roadmap drafts and delayed responses", async ({ page }) => { let delayWidefrog = false; let releaseResponse!: () => void; const responseGate = new Promise((resolve) => { releaseResponse = resolve; }); let requestStarted!: () => void; const started = new Promise((resolve) => { requestStarted = resolve; }); await page.route("**/api/projects/*/autonomy", async (route) => { const project = new URL(route.request().url()).pathname.split("/")[3]; if (project === "widefrog" && delayWidefrog) { requestStarted(); await responseGate; } await route.fulfill({ json: envelope(stateFor(project)) }); }); await page.goto("/projects/widefrog/auto"); await openManualRoadmapImport(page); await expect(page.getByLabel("Roadmap", { exact: true })).toContainText("widefrog"); await page.getByLabel("Roadmap", { exact: true }).fill("## Private draft\n- Only WideFrog"); delayWidefrog = true; await page.getByRole("button", { name: "Vernieuwen" }).click(); await started; const nebulaResponse = page.waitForResponse((response) => response.url().endsWith("/projects/nebula/autonomy")); await navigateWithoutReload(page, "/projects/nebula/auto"); await nebulaResponse; await openManualRoadmapImport(page); await expect(page.getByLabel("Titel", { exact: true })).toHaveValue("nebula roadmap"); await expect(page.getByLabel("Roadmap", { exact: true })).toHaveValue("## nebula\n- [ ] nebula task"); const oldResponse = page.waitForResponse((response) => response.url().endsWith("/projects/widefrog/autonomy")); releaseResponse(); await oldResponse; await expect(page.getByLabel("Titel", { exact: true })).toHaveValue("nebula roadmap"); await expect(page.getByLabel("Roadmap", { exact: true })).toHaveValue("## nebula\n- [ ] nebula task"); await expect(page.getByText("widefrog task", { exact: true })).toHaveCount(0); }); test("reimporting an unchanged roadmap preserves work and toggling Auto preserves unsaved policy", async ({ page }) => { let state = stateFor("widefrog"); let imports = 0; await page.route("**/api/projects/widefrog/autonomy**", async (route) => { const request = route.request(); if (request.method() === "POST" && request.url().endsWith("/roadmap")) imports += 1; if (request.method() === "PATCH" && request.url().endsWith("/autonomy")) { state = { ...state, ...request.postDataJSON() }; } await route.fulfill({ json: envelope(state) }); }); await page.goto("/projects/widefrog/auto"); await openManualRoadmapImport(page); await page.getByRole("button", { name: "Roadmap bijwerken" }).click(); await expect(page.getByText("Deze roadmap is al opgeslagen. De voortgang blijft behouden.")).toBeVisible(); expect(imports).toBe(0); await openAutoAdvancedLimits(page); await page.getByLabel("Werkitems per run").fill("12"); await page.getByRole("switch", { name: "Auto", exact: true }).click(); await expect(page.getByRole("switch", { name: "Auto", exact: true })).toBeChecked(); await expect(page.getByLabel("Werkitems per run")).toHaveValue("12"); await expect(page.getByRole("button", { name: "Beleid opslaan" })).toBeEnabled(); });