import AxeBuilder from "@axe-core/playwright"; import { expect, test as base, type APIRequestContext, type Page, type TestInfo } from "@playwright/test"; import { translations } from "../src/i18n/translations"; const apiUrl = "http://localhost:5302/api"; const password = "E2ePassword123!"; async function login(api: APIRequestContext, email: string) { expect((await api.post(`${apiUrl}/auth/login`, { data: { email, password } })).status()).toBe(200); const me = await (await api.get(`${apiUrl}/auth/me`)).json(); const csrf = (await api.storageState()).cookies.find(c => c.name === "XSRF-TOKEN")!.value; return { "X-CSRF-TOKEN": csrf, "X-Session-Context": me.sessionContext as string }; } const test = base.extend<{ owner: { headers: Record; companyId: number; jobId: number; cvId: number } }>({ owner: async ({ playwright, page, context }, provide) => { const admin = await playwright.request.newContext(); try { const adminHeaders = await login(admin, "e2e@example.test"); const email = `p402-${Date.now()}-${Math.random().toString(36).slice(2)}@example.test`; expect((await admin.post(`${apiUrl}/users`, { headers: adminHeaders, data: { email, password, roles: [] } })).status()).toBe(200); const headers = await login(page.request, email); await context.route("**/*", route => { const url = new URL(route.request().url()); return ["localhost", "127.0.0.1"].includes(url.hostname) || ["data:", "blob:", "about:"].includes(url.protocol) ? route.continue() : route.abort(); }); expect((await page.request.put(`${apiUrl}/career/profile`, { headers, data: { profile: { version: "1", contact: { fullName: "Åse Eksempel" }, skills: ["Norsk", "C#"] } } })).status()).toBe(200); const company = await page.request.post(`${apiUrl}/companies`, { headers, data: { name: "Nordlys æøå AS" } }); expect(company.status()).toBe(201); const companyId = (await company.json()).id; const job = await page.request.post(`${apiUrl}/jobapplications`, { headers, data: { companyId, jobTitle: "Engineer", status: "Saved" } }); expect(job.ok()).toBe(true); const jobId = (await job.json()).id; const cv = await page.request.post(`${apiUrl}/cv/variants`, { headers, data: { name: "Named controls CV", jobApplicationId: jobId } }); expect(cv.status()).toBe(200); const cvId = (await cv.json()).id; expect((await page.request.put(`${apiUrl}/jobapplications/${jobId}/cv`, { headers, data: { variantId: cvId } })).status()).toBe(200); expect((await page.request.post(`${apiUrl}/attachments`, { headers, multipart: { jobId: String(jobId), files: { name: "evidence.txt", mimeType: "text/plain", buffer: Buffer.from("Synthetic æøå\n") } } })).ok()).toBe(true); await provide({ headers, companyId, jobId, cvId }); } finally { await admin.dispose(); } }, }); async function capture(page: Page, info: TestInfo, name: string) { const path = process.env.P4_02_CAPTURE === "true" ? `../docs/redesign/phase-4/p4-02/visual/${name}.png` : info.outputPath(`${name}.png`); await page.evaluate(() => new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)))); await page.screenshot({ path }); await info.attach(name, { path, contentType: "image/png" }); } async function scan(page: Page, info: TestInfo, name: string, dialog = false) { let axe = new AxeBuilder({ page }).withTags(["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"]); if (dialog) axe = axe.include('[role="dialog"]'); const result = await axe.analyze(); await info.attach(`${name}-axe`, { body: JSON.stringify({ violations: result.violations, incomplete: result.incomplete }, null, 2), contentType: "application/json" }); // P4-02-D01: unchanged command List renders button divs directly under a ul. // Record the full result; only this precise existing structural defect is deferred. const unexpected = result.violations.filter(v => !(name === "command" && v.id === "list" && v.nodes.every(n => n.html.startsWith("