Publish curated Knowledge Hub source from 2ed833f290b439a7bc91e5fd19b042c269e3bee9

This commit is contained in:
Public Source Publisher committed 2026-10-03 17:07:21 +00:00
1 parent 10f2edcd29
commit 59f15b2d08
218 files changed
+23995 -1849

No files matched your search

+1 -1
View File
@@ -6,7 +6,7 @@
"scripts": {
"test": "playwright test",
"test:headed": "playwright test --headed",
"install:browsers": "playwright install --with-deps chromium"
"install:browsers": "playwright install --with-deps chromium firefox webkit"
},
"devDependencies": {
"@axe-core/playwright": "^4.10.2",
+12
View File
@@ -27,6 +27,18 @@ export default defineConfig({
{
name: "chromium-mobile",
use: { ...devices["Pixel 7"] }
},
{
name: "firefox-desktop",
// Keep the broad historical suite on its established projects. This
// cross-browser smoke covers the new platform workflows on each release.
testMatch: "**/platform-productization.spec.ts",
use: { ...devices["Desktop Firefox"] }
},
{
name: "webkit-mobile",
testMatch: "**/platform-productization.spec.ts",
use: { ...devices["iPhone 13"] }
}
],
expect: {
+12 -4
View File
@@ -43,18 +43,26 @@ test.describe("discovery: search, revisions, backlinks and tags", () => {
// Scoped to the search results list specifically: on a desktop viewport the
// permanent sidebar tree also shows a same-named entry once the page exists, so an
// unscoped locator would be ambiguous.
const searchResults = page.getByRole("list", { name: "Zoekresultaten" });
const searchResults = page.getByRole("region", { name: "Zoekresultaten" });
await page.getByLabel("Zoekterm").fill("zoekbaarheidsproef");
await page.getByRole("button", { name: "Zoekopdracht uitvoeren" }).click();
await expect(searchResults.getByRole("button", { name: new RegExp(uniqueTitle) })).toBeVisible();
await expect(searchResults.getByRole("link", { name: new RegExp(uniqueTitle) })).toBeVisible();
// Tag filter narrows results to just the tagged page.
await page.getByLabel("Label").fill(tag);
await page.getByRole("button", { name: "Zoekopdracht uitvoeren" }).click();
await expect(searchResults.getByRole("button", { name: new RegExp(uniqueTitle) })).toBeVisible();
await expect(searchResults.getByRole("link", { name: new RegExp(uniqueTitle) })).toBeVisible();
await searchResults.getByRole("button", { name: new RegExp(uniqueTitle) }).click();
await page.getByLabel("Label", { exact: true }).fill(`${tag}-absent`);
await page.getByRole("button", { name: "Zoekopdracht uitvoeren" }).click();
await expect(searchResults.getByText("Geen resultaten voor deze zoekterm.")).toBeVisible();
await page.getByLabel("Zoekterm").fill("");
await page.getByLabel("Label", { exact: true }).fill(tag);
await page.getByRole("button", { name: "Zoekopdracht uitvoeren" }).click();
await expect(searchResults.getByRole("link", { name: new RegExp(uniqueTitle) })).toBeVisible();
await searchResults.getByRole("link", { name: new RegExp(uniqueTitle) }).click();
await expect(page.getByRole("heading", { name: uniqueTitle })).toBeVisible();
// Scoped to the backlinks panel: on a desktop viewport the permanent sidebar tree
+5 -4
View File
@@ -1,3 +1,4 @@
import { randomUUID } from "node:crypto";
import AxeBuilder from "@axe-core/playwright";
import { expect, test, type Page } from "@playwright/test";
import { authenticatedStatePath } from "../global-setup";
@@ -5,17 +6,17 @@ import { authenticatedStatePath } from "../global-setup";
test.use({ storageState: authenticatedStatePath });
async function createProposal(page: Page): Promise<string> {
return page.evaluate(async () => {
return page.evaluate(async ({ idempotencyKey, correlationId }) => {
const csrfResponse = await fetch("/api/v1/auth/csrf");
const csrf = await csrfResponse.json() as { token: string; headerName: string };
const response = await fetch("/api/v1/ingestion-proposals", {
method: "POST",
headers: { "content-type": "application/json", [csrf.headerName]: csrf.token, "Idempotency-Key": crypto.randomUUID() },
body: JSON.stringify({ manifest: "# E2E mobiele beoordeling", correlationId: `e2e-review-${crypto.randomUUID()}` }),
headers: { "content-type": "application/json", [csrf.headerName]: csrf.token, "Idempotency-Key": idempotencyKey },
body: JSON.stringify({ manifest: "# E2E mobiele beoordeling", correlationId }),
});
if (!response.ok) throw new Error(`Voorstel maken mislukte (${response.status}): ${await response.text()}`);
return (await response.json() as { id: string }).id;
});
}, { idempotencyKey: randomUUID(), correlationId: `e2e-review-${randomUUID()}` });
}
test.describe("intakebeoordeling: responsieve, toegankelijke werkruimte", () => {
@@ -0,0 +1,119 @@
import AxeBuilder from "@axe-core/playwright";
import { expect, test } from "@playwright/test";
import { authenticatedStatePath } from "../global-setup";
import { expectAuthenticatedShell } from "./support/v2";
test.describe("platform workflows at supported widths", () => {
test.use({ storageState: authenticatedStatePath });
for (const width of [320, 360, 390, 768, 1280]) {
test(`search and administration reflow without page overflow at ${width}px`, async ({ page }) => {
await page.setViewportSize({ width, height: 850 });
for (const [path, heading] of [
["/search?q=knowledge", "Zoeken"],
["/admin/jobs", "Achtergrondtaken"],
["/admin/audit", "Beheeraudit"],
["/admin/platform", "Platforminstellingen"]
] as const) {
await page.goto(path);
await expect(page.getByRole("heading", { name: heading, level: 1 })).toBeVisible();
await expectAuthenticatedShell(page);
const overflow = await page.evaluate(
() => document.documentElement.scrollWidth - document.documentElement.clientWidth
);
expect(overflow, `${path} overflowed at ${width}px`).toBeLessThanOrEqual(1);
}
});
}
test("keyboard search and admin navigation remain accessible", async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
await page.emulateMedia({ reducedMotion: "reduce" });
await page.goto("/search?q=knowledge");
await expect(page.getByRole("heading", { name: "Zoeken", level: 1 })).toBeVisible();
expect((await new AxeBuilder({ page }).analyze()).violations).toEqual([]);
await page.getByRole("button", { name: "Opdrachten en zoeken openen" }).click();
await expect(page.getByRole("dialog")).toBeVisible();
await page.keyboard.press("Tab");
expect(await page.evaluate(() => {
const dialog = document.querySelector("[role=dialog]");
return dialog?.contains(document.activeElement) ?? false;
})).toBeTruthy();
const reducedDuration = await page.getByRole("dialog").evaluate(
(dialog) => parseFloat(getComputedStyle(dialog).transitionDuration)
);
expect(reducedDuration).toBeLessThanOrEqual(0.01);
await page.keyboard.press("Escape");
await expect(page.getByRole("dialog")).toBeHidden();
await page.goto("/admin/jobs");
await expect(page.getByRole("heading", { name: "Achtergrondtaken", level: 1 })).toBeVisible();
expect((await new AxeBuilder({ page }).analyze()).violations).toEqual([]);
});
test("narrow mobile controls stay touchable with a short virtual-keyboard viewport", async ({ page }) => {
await page.setViewportSize({ width: 320, height: 430 });
await page.goto("/admin/jobs");
await expect(page.getByRole("heading", { name: "Achtergrondtaken", level: 1 })).toBeVisible();
for (const name of ["Navigatie openen", "Opdrachten en zoeken openen", "Accountmenu"]) {
const box = await page.getByRole("button", { name }).boundingBox();
// Browser layout rounds CSS's 44px touch target to fractional device pixels.
expect(box?.width ?? 0, `${name} width`).toBeGreaterThanOrEqual(43.9);
expect(box?.height ?? 0, `${name} height`).toBeGreaterThanOrEqual(43.9);
}
await page.getByRole("button", { name: "Navigatie openen" }).click();
await expect(page.getByRole("navigation", { name: "Hoofdnavigatie" })).toBeVisible();
expect((await new AxeBuilder({ page }).analyze()).violations).toEqual([]);
});
test("server preferences survive a reload and can be restored", async ({ page }) => {
await page.goto("/settings");
await expect(page.getByRole("heading", { name: "Instellingen", level: 1 })).toBeVisible();
await expect(page.locator(".preference-sync-status")).toContainText("Voorkeuren gesynchroniseerd.");
await page.getByRole("button", { name: "Donker" }).click();
await expect.poll(async () => {
const response = await page.request.get("/api/v1/me/preferences");
return (await response.json() as { theme: string }).theme;
}).toBe("dark");
await page.reload();
await expect(page.getByRole("button", { name: "Donker" })).toHaveClass(/selected/);
await page.getByRole("button", { name: "Systeem" }).click();
await expect.poll(async () => {
const response = await page.request.get("/api/v1/me/preferences");
return (await response.json() as { theme: string }).theme;
}).toBe("system");
});
test("administrator can register an external source and record an observation", async ({ page }) => {
const sourceKey = `e2e-source-${crypto.randomUUID()}`;
const displayName = `E2E bron ${sourceKey.slice(-8)}`;
await page.goto("/admin/integrations");
await expect(page.getByRole("heading", { name: "Externe gegevensbronnen", level: 1 })).toBeVisible();
await page.getByRole("button", { name: "Bron toevoegen" }).click();
await page.getByRole("textbox", { name: "Unieke broncode" }).fill(sourceKey);
await page.getByRole("textbox", { name: "Weergavenaam" }).fill(displayName);
await page.getByRole("button", { name: "Bron registreren" }).click();
await expect(page.locator(".form-success")).toContainText("Externe gegevensbron geregistreerd.");
const row = page.locator(".data-source-row", { hasText: sourceKey });
await expect(row).toBeVisible();
await row.getByText("Operatorwaarneming registreren").click();
await row.getByRole("spinbutton", { name: "Waargenomen recordaantal" }).fill("42");
await row.getByRole("button", { name: "Waarneming opslaan" }).click();
await expect(page.locator(".form-success")).toContainText(`Operatorwaarneming voor ${displayName} geregistreerd.`);
await page.reload();
await expect(page.locator(".data-source-row", { hasText: sourceKey })).toContainText("42 records");
});
test("administrator sees durable backup job progress after queuing", async ({ page }) => {
await page.goto("/admin/jobs");
await expect(page.getByRole("heading", { name: "Achtergrondtaken", level: 1 })).toBeVisible();
await page.getByRole("button", { name: "Back-uptaak inplannen" }).click();
await expect(page.locator(".form-success")).toContainText("Back-uptaak is ingepland.");
await expect(page).toHaveURL(/jobId=/);
await expect(page.getByRole("heading", { name: /Back-up · / })).toBeVisible();
await expect(page.locator(".admin-job-detail").getByRole("progressbar", { name: /Back-up/ })).toBeVisible();
await page.reload();
await expect(page.getByRole("heading", { name: "Back-up · Geslaagd" })).toBeVisible({ timeout: 20_000 });
});
});
@@ -66,8 +66,18 @@ test.describe("V2-M3 knowledge editor: LeafWiki-floor proofs", () => {
const editor = markdownEditor(page);
await editor.click();
const savedSeparator = page.waitForResponse((response) => {
const request = response.request();
return request.method() === "PUT"
&& request.url().includes("/api/v1/drafts/")
&& (request.postDataJSON() as { markdown?: string }).markdown === "Voor de afbeelding: ";
});
await page.keyboard.type("Voor de afbeelding: ");
const draftResponse = await savedSeparator;
expect(draftResponse.status()).toBe(200);
await expect.poll(() => readMarkdown(page)).toBe("Voor de afbeelding: ");
const accessibility = await new AxeBuilder({ page }).analyze();
expect(accessibility.violations).toEqual([]);
@@ -86,7 +96,7 @@ test.describe("V2-M3 knowledge editor: LeafWiki-floor proofs", () => {
// placeholder "pending-attachment:" token exists anymore, so the same reference
// inserted at the cursor is already correct even before publish.
await expect.poll(() => readMarkdown(page)).toContain("toolbar-inline.png");
expect(await readMarkdown(page)).toMatch(/Voor de afbeelding: !?\[toolbar-inline\.png\]\(\/api\/v1\/attachments\//);
expect(await readMarkdown(page)).toMatch(/^Voor de afbeelding: !?\[toolbar-inline\.png\]\(\/api\/v1\/attachments\/[^)]+\)$/);
await expect(page.getByText("1 bijlage klaar — nog niet gepubliceerd")).toBeVisible();
await page.getByRole("button", { name: "Publiceren", exact: true }).click();
@@ -189,13 +199,13 @@ test.describe("V2-M3 knowledge editor: LeafWiki-floor proofs", () => {
await expect(page.getByRole("dialog")).toBeVisible();
await page.getByLabel("Zoeken en opdrachten").fill(uniqueTitle.slice(0, 22));
const knowledgeGroup = page.getByRole("group", { name: "Kennisbank" });
await expect(knowledgeGroup.getByRole("option", { name: new RegExp(uniqueTitle) })).toBeVisible();
const searchResultsGroup = page.getByRole("group", { name: "Zoekresultaten" });
await expect(searchResultsGroup.getByRole("option", { name: new RegExp(uniqueTitle) })).toBeVisible();
const accessibility = await new AxeBuilder({ page }).analyze();
expect(accessibility.violations).toEqual([]);
await knowledgeGroup.getByRole("option", { name: new RegExp(uniqueTitle) }).click();
await searchResultsGroup.getByRole("option", { name: new RegExp(uniqueTitle) }).click();
await expect(page.getByRole("heading", { name: uniqueTitle })).toBeVisible();
await expect(page.getByRole("dialog")).toBeHidden();
});
@@ -75,7 +75,7 @@ test.describe("V2-M4 infrastructure review, editorial ownership and overrides",
const detectedName = `e2e-accept-service-${Date.now()}`;
await seedProposedService(page, sourceKey, detectedName);
await page.goto("/admin");
await page.goto("/admin/inventory");
await expectAuthenticatedShell(page);
await expect(page.getByRole("heading", { name: "Wijzigingen sinds snapshots" })).toBeVisible();
@@ -141,7 +141,7 @@ test.describe("V2-M4 infrastructure review, editorial ownership and overrides",
const detectedName = `e2e-reject-service-${Date.now()}`;
await seedProposedService(page, sourceKey, detectedName);
await page.goto("/admin");
await page.goto("/admin/inventory");
await expectAuthenticatedShell(page);
const proposalCard = page.locator(".inventory-proposals article", { hasText: detectedName });
await expect(proposalCard).toBeVisible();
@@ -117,7 +117,7 @@ test.describe("V2-M5 workspace fidelity spot-check", () => {
});
expect(snapshotResponse.ok(), await snapshotResponse.text()).toBeTruthy();
await page.goto("/admin");
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();
@@ -90,6 +90,12 @@ test.describe("V2-M6 motion, matrices and dashboard personalization", () => {
await page.goto("/settings");
await expectAuthenticatedShell(page);
await expect(page.getByRole("heading", { name: "Dashboardwidgets" })).toBeVisible();
await expect(page.locator(".preference-sync-status")).toContainText("Voorkeuren gesynchroniseerd.");
await page.getByRole("button", { name: "Widgets herstellen" }).click();
await expect.poll(async () => {
const values = await (await page.request.get("/api/v1/me/preferences")).json();
return [values.dashboardWidgetOrder[0], values.hiddenDashboardWidgets.length];
}).toEqual(["attention", 0]);
// Hide the "Projecten" widget entirely.
await page.getByRole("checkbox", { name: "Projecten tonen" }).uncheck();
@@ -100,6 +106,13 @@ test.describe("V2-M6 motion, matrices and dashboard personalization", () => {
await expect(page.getByRole("button", { name: "Operationele signalen omhoog" })).toBeDisabled();
await page.getByRole("button", { name: "Unraid-overzicht omhoog" }).click();
// A full page navigation must wait for the server save, as indicated by the
// settings UI, instead of interrupting the pending preference request.
await expect.poll(async () => {
const values = await (await page.request.get("/api/v1/me/preferences")).json();
return [values.dashboardWidgetOrder[0], values.hiddenDashboardWidgets.includes("projects")];
}).toEqual(["server", true]);
await page.goto("/");
await expect(page.getByRole("heading", { name: "Operationeel overzicht" })).toBeVisible();
await expect(page.getByRole("heading", { name: "Projecten", exact: true })).toHaveCount(0);
@@ -120,7 +133,11 @@ test.describe("V2-M6 motion, matrices and dashboard personalization", () => {
// Restore the default so this test does not leak state into any other test that
// shares the same authenticated storage state / owner account.
await page.goto("/settings");
await page.getByRole("checkbox", { name: "Projecten tonen" }).check();
await page.getByRole("button", { name: "Unraid-overzicht omlaag" }).click();
await expect(page.locator(".preference-sync-status")).toContainText("Voorkeuren gesynchroniseerd.");
await page.getByRole("button", { name: "Widgets herstellen" }).click();
await expect.poll(async () => {
const values = await (await page.request.get("/api/v1/me/preferences")).json();
return [values.dashboardWidgetOrder[0], values.hiddenDashboardWidgets.length];
}).toEqual(["attention", 0]);
});
});
+54 -27
View File
@@ -1,4 +1,6 @@
import { execFileSync } from "node:child_process";
import { mkdirSync, rmSync, writeFileSync } 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";
@@ -17,35 +19,52 @@ import { expectAuthenticatedShell } from "./support/v2";
test.use({ storageState: authenticatedStatePath });
// Keep the ordinary local-stack name as the zero-configuration default, while
// allowing clean-room/release verification to target its isolated app container.
// Without this override the fixture is written into a different healthy stack and
// the preflight correctly rejects the path as absent from its own imports mount.
const CONTAINER_NAME =
process.env.KNOWLEDGE_HUB_E2E_APP_CONTAINER ?? "itworx-knowledge-hub-app-1";
const IMPORTS_ROOT = "/var/lib/knowledge-hub/imports";
const testDirectory = path.dirname(fileURLToPath(import.meta.url));
const expectedImportsRoot = path.resolve(testDirectory, "../../../.local-appdata/e2e-imports");
const seededFixtureDirectories = new Set<string>();
function seedLeafWikiFixture(relativeDir: string, title: string) {
// Writes a minimal, synthetic Markdown fixture directly into the running app
// container's imports volume (never a real LeafWiki source — CLAUDE.md's
// read-only-source rule only ever applies to a real snapshot, which this is not).
// Uses `docker exec` from the test runner rather than a Playwright API call, since
// the import endpoint deliberately only ever reads a server-side mounted path, by
// design (docs/17-leafwiki-migration.md) — there is no upload endpoint to seed it
// through.
execFileSync("docker", [
"exec",
CONTAINER_NAME,
"sh",
"-c",
`mkdir -p '${IMPORTS_ROOT}/${relativeDir}' && printf '# ${title}\\n\\nDit is een synthetische testpagina voor de V2-M7 importproef.\\n' > '${IMPORTS_ROOT}/${relativeDir}/welkom.md'`
]);
// The import API reads its configured server-side fixture mount. Fail closed unless
// both the API and this test are explicitly pointed at this repository-local test
// directory; never discover or fall back to a Docker container, which could be a
// production deployment.
const configuredImportsRoot = process.env.KNOWLEDGE_HUB_E2E_IMPORTS_ROOT;
if (!configuredImportsRoot || path.resolve(configuredImportsRoot) !== expectedImportsRoot) {
throw new Error(
"Set KNOWLEDGE_HUB_E2E_IMPORTS_ROOT to this checkout's absolute .local-appdata/e2e-imports path before running the LeafWiki import browser test."
);
}
const fixtureDirectory = path.resolve(expectedImportsRoot, relativeDir);
if (!fixtureDirectory.startsWith(expectedImportsRoot + path.sep)) {
throw new Error("The synthetic LeafWiki fixture must stay inside .local-appdata/e2e-imports.");
}
mkdirSync(fixtureDirectory, { recursive: true });
seededFixtureDirectories.add(fixtureDirectory);
writeFileSync(
path.join(fixtureDirectory, "welkom.md"),
"# " + title + "\n\nDit is een synthetische testpagina voor de V2-M7 importproef.\n",
{ encoding: "utf8", flag: "wx" }
);
}
test.afterEach(() => {
for (const fixtureDirectory of seededFixtureDirectories) {
if (
path.dirname(fixtureDirectory) === expectedImportsRoot
&& path.basename(fixtureDirectory).startsWith("e2e-leafwiki-")
) {
rmSync(fixtureDirectory, { recursive: true, force: true });
}
seededFixtureDirectories.delete(fixtureDirectory);
}
});
test.describe("V2-M7 admin actions: recovery tokens, trash purge, LeafWiki import", () => {
test("an administrator can issue a one-time recovery token for another user from the admin UI", async ({ page }) => {
const targetEmail = `e2e-recovery-target-${Date.now()}@itworx.e2e`;
await page.goto("/admin");
await page.goto("/admin/users");
await expectAuthenticatedShell(page);
await expect(page.getByRole("heading", { name: "Gebruikers", exact: true })).toBeVisible();
@@ -72,12 +91,19 @@ test.describe("V2-M7 admin actions: recovery tokens, trash purge, LeafWiki impor
});
test("an administrator can trigger a trash purge from the admin UI, and it honestly reports nothing eligible yet", async ({ page }) => {
await page.goto("/admin");
await page.goto("/admin/backups");
await expectAuthenticatedShell(page);
await expect(page.getByRole("heading", { name: "Prullenbak" })).toBeVisible();
await page.getByRole("button", { name: "Prullenbak legen" }).click();
const purgeConfirmation = page.getByRole("group", { name: "Definitieve verwijdering bevestigen" });
await expect(purgeConfirmation).toBeVisible();
await expect(
purgeConfirmation.getByText("Dit verwijdert definitief alleen pagina's waarvan de bewaartermijn is verstreken. Deze actie kan niet ongedaan worden gemaakt.")
).toBeVisible();
await purgeConfirmation.getByRole("button", { name: "Bevestig definitieve verwijdering" }).click();
// PurgeTrashUseCase only removes pages whose retention window has already
// elapsed — a fresh test run has nothing eligible, and the UI must say so
// honestly rather than implying something was deleted.
@@ -100,7 +126,7 @@ test.describe("V2-M7 admin actions: recovery tokens, trash purge, LeafWiki impor
const pageTitle = `Welkom bij LeafWiki ${relativeDir}`;
seedLeafWikiFixture(relativeDir, pageTitle);
await page.goto("/admin");
await page.goto("/admin/knowledge");
await expectAuthenticatedShell(page);
await expect(page.getByRole("heading", { name: "LeafWiki-import" })).toBeVisible();
@@ -184,9 +210,10 @@ async function findImportedPage(page: Page, title: string) {
// finished, which a bare locator check on the title alone could not distinguish from
// "the request has not come back yet".
await expect(
page.getByRole("list", { name: "Zoekresultaten" }).or(page.getByText("Geen resultaten gevonden.")),
page.getByRole("region", { name: "Zoekresultaten", exact: true }),
).toBeVisible();
return page.getByRole("button", { name: new RegExp(escapeForRegExp(title)) });
return page.getByRole("region", { name: "Zoekresultaten", exact: true })
.getByRole("link", { name: new RegExp(escapeForRegExp(title)) });
}
function escapeForRegExp(value: string) {
@@ -153,7 +153,7 @@ test.describe("V2-M8 accessibility: previously unscanned screens and states", ()
});
expect(snapshotResponse.ok(), await snapshotResponse.text()).toBeTruthy();
await page.goto("/admin");
await page.goto("/admin/inventory");
await expectAuthenticatedShell(page);
const proposalCard = page.locator(".inventory-proposals article", { hasText: detectedName });
await expect(proposalCard).toBeVisible();
@@ -179,6 +179,8 @@ test.describe("V2-M2 accessibility (axe) on primary routes, both themes", () =>
for (const theme of ["Licht", "Donker"] as const) {
test(`primary routes have zero axe violations in ${theme === "Licht" ? "light" : "dark"} mode`, async ({ page }) => {
// Seven complete route loads and axe scans share this test's total budget.
test.slow();
await setTheme(page, theme);
for (const routePath of routes) {
await page.goto(routePath);
@@ -206,6 +208,8 @@ test.describe("V2-M2 accessibility (axe) on primary routes, both themes", () =>
test.describe("V2-M2 visual fidelity capture (compared against artifacts/stitch/v2/screens by hand)", () => {
test("capture implementation screenshots for the required routes/breakpoints/themes", async ({ page }) => {
// Eleven captures plus persisted theme changes form one visual evidence set.
test.slow();
await setTheme(page, "Donker");
await page.setViewportSize({ width: 1600, height: 1000 });
await page.goto("/");