Files
projectbrain-Public/e2e/platform-state.spec.ts
T

103 lines
5.6 KiB
TypeScript

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<void>((resolve) => { releaseResponse = resolve; });
let requestStarted!: () => void;
const started = new Promise<void>((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();
});