Files
itworx-knowledge-hub-Public/tests/e2e/specs/v2-m5-workspace-fidelity.spec.ts
T

135 lines
7.3 KiB
TypeScript

import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import AxeBuilder from "@axe-core/playwright";
import { expect, test, type Page } from "@playwright/test";
import { authenticatedStatePath } from "../global-setup";
import { expectAuthenticatedShell } from "./support/v2";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
/**
* V2-M5: services and project workspaces.
*
* V2-M2's fidelity pass (docs/v2/V2_EVIDENCE.md "V2-M2") deliberately covered the
* *list/overview* screens only (dashboard, infrastructure overview, projects overview,
* knowledge tree) and narrowed the full per-route implementation-fidelity-checklist
* sweep to V2-M8. This spec adds the missing spot-check for the *detail/workspace*
* screens this milestone is actually about — project workspace (screen 10), service
* detail (screen 7) and runbook detail (screen 13) — captured with real, if modest,
* seeded content rather than an empty state, since Stitch's own references show
* populated data. Concrete match/divergence notes are recorded in V2_EVIDENCE.md
* "V2-M5", not just "screenshot exists".
*/
test.use({ storageState: authenticatedStatePath });
const screenshotDir = path.resolve(__dirname, "../../../artifacts/v2-m5/screenshots");
fs.mkdirSync(screenshotDir, { recursive: true });
async function postWithCsrf(page: Page, url: string, data: unknown) {
const csrfResponse = await page.request.get("/api/v1/auth/csrf");
const csrf = (await csrfResponse.json()) as { token: string; headerName: string };
return page.request.post(url, { headers: { [csrf.headerName]: csrf.token }, data });
}
test.describe("V2-M5 workspace fidelity spot-check", () => {
test("project, service and runbook detail workspaces are captured with real content for a fidelity comparison", async ({ page }) => {
const stamp = Date.now();
// --- Project workspace, with a milestone/decision/update so the panels are not
// all in their empty state, matching what screen 10 (project detail) shows.
const projectName = `Facturatieplatform ${stamp}`;
await page.goto("/projects");
await expectAuthenticatedShell(page);
await page.getByRole("button", { name: "Nieuw project" }).click();
await page.getByLabel("Naam").fill(projectName);
await page.getByLabel("Samenvatting").fill("Vernieuwing van het interne facturatieplatform.");
await page.getByRole("button", { name: "Project aanmaken" }).click();
await expect(page.getByRole("heading", { name: projectName })).toBeVisible();
await page.getByRole("button", { name: "Mijlpaal" }).click();
await page.getByLabel("Naam").fill("Bèta-release");
await page.getByRole("button", { name: "Mijlpaal toevoegen" }).click();
await expect(page.getByText("Bèta-release")).toBeVisible();
await page.getByRole("button", { name: "Update", exact: true }).click();
await page.getByLabel("Titel").fill("Databasemigratie afgerond");
await page.getByRole("button", { name: "Update toevoegen" }).click();
await expect(page.getByText("Databasemigratie afgerond")).toBeVisible();
await page.getByRole("button", { name: "Beslissing", exact: true }).click();
await page.getByLabel("Titel").fill("Kies event-driven facturatie");
await page.getByLabel("Samenvatting").fill("Ontkoppelt facturatie van het bestelproces.");
await page.getByRole("button", { name: "Beslissing aanmaken" }).click();
await expect(page.getByRole("heading", { name: "Kies event-driven facturatie" })).toBeVisible();
await page.goBack();
await expect(page.getByRole("heading", { name: projectName })).toBeVisible();
// A history navigation can still be resolving the route's lazy chunk while the
// heading assertion already passes against the outgoing render. Scanning at that
// moment reports the transient empty document -- including "page must have a
// level-one heading" -- rather than the workspace under test. Seen once as a
// 35-violation failure between two clean runs.
await page.waitForLoadState("networkidle");
const projectAccessibility = await new AxeBuilder({ page }).analyze();
expect(projectAccessibility.violations).toEqual([]);
await page.screenshot({ path: path.join(screenshotDir, "10-project-workspace-populated.png"), fullPage: true });
// --- Runbook detail, validated (so it is not stuck in its initial stale-only
// state), matching screen 13 (runbook detail).
await page.goto("/runbooks");
await page.getByRole("button", { name: "Nieuw runbook", exact: true }).click();
await page.getByLabel("Titel").fill(`PostgreSQL failover ${stamp}`);
await page.getByRole("button", { name: "Runbook aanmaken" }).click();
await expect(page.getByRole("heading", { name: `PostgreSQL failover ${stamp}` })).toBeVisible();
await page.getByRole("button", { name: "Valideren tegen huidige revisie" }).click();
await expect(page.getByText(/Laatst gevalideerd op/)).toBeVisible();
const runbookAccessibility = await new AxeBuilder({ page }).analyze();
expect(runbookAccessibility.violations).toEqual([]);
await page.screenshot({ path: path.join(screenshotDir, "13-runbook-detail-validated.png"), fullPage: true });
// --- Service detail, accepted from a seeded proposal (same mechanism as
// v2-infrastructure-review.spec.ts), matching screen 7 (service detail).
const sourceKey = `docker:e2e-fidelity-${stamp}`;
const detectedName = `facturatie-api-${stamp}`;
const snapshotResponse = await postWithCsrf(page, "/api/v1/infrastructure/inventory/snapshots", {
sourceKey: "inventory-e2e",
sourceHash: `hash-fidelity-${stamp}`,
capturedAt: new Date().toISOString(),
hosts: [
{
sourceKey: "host:e2e-fidelity", hostname: "E2E-Fidelity-Host", operatingSystem: "Linux",
kernelVersion: null, architecture: "x86_64", logicalCpuCount: 8, totalMemoryBytes: 16_000_000_000
}
],
services: [
{
sourceKey, hostSourceKey: "host:e2e-fidelity", detectedName, runtimeKind: "container",
runtimeStatus: "running", health: "Healthy", imageReference: "itworx/facturatie-api:2.3.0",
imageDigest: null, runtimeUser: "app", readOnlyRootFilesystem: true, noNewPrivileges: true,
privileged: false, confidence: "High", components: null, endpoints: null, networks: null,
volumes: null, databases: null, dependencies: null, healthObservations: null, deployments: null,
backups: null
}
]
});
expect(snapshotResponse.ok(), await snapshotResponse.text()).toBeTruthy();
await page.goto("/admin/inventory");
const proposalCard = page.locator(".inventory-proposals article", { hasText: detectedName });
await expect(proposalCard).toBeVisible();
await proposalCard.getByRole("button", { name: "Bevestigen" }).click();
await expect(proposalCard).toBeHidden();
await page.goto("/infrastructure/services");
await page.getByRole("link", { name: new RegExp(detectedName) }).click();
await expect(page.getByRole("heading", { name: detectedName })).toBeVisible();
const serviceAccessibility = await new AxeBuilder({ page }).analyze();
expect(serviceAccessibility.violations).toEqual([]);
await page.screenshot({ path: path.join(screenshotDir, "07-service-detail-populated.png"), fullPage: true });
});
});