import { test, expect, type APIRequestContext } from '../../../../../job-tracker-ui/node_modules/@playwright/test'; import os from 'node:os'; import { performance } from 'node:perf_hooks'; import { durationMs, paceMs, waitUntil, summarize, assertMembership, persistedCareerProfile } from './metrics.mjs'; import { snapshot, sql } from './mariadb-proof.mjs'; const api = 'http://localhost:5302/api'; const password = 'E2ePassword123!'; async function login(request: APIRequestContext, email: string) { expect((await request.post(`${api}/auth/login`, { data: { email, password } })).status()).toBe(200); const me = await (await request.get(`${api}/auth/me`)).json(); return { id: me.id as string, headers: { 'X-CSRF-TOKEN': (await request.storageState()).cookies.find(c => c.name === 'XSRF-TOKEN')!.value, 'X-Session-Context': me.sessionContext as string } }; } test('P9-REQUAL-01 real MariaDB: 15 minutes, 250 applications, two paced readers and native workspaces', async ({ page, context, playwright }, info) => { const admin = await playwright.request.newContext(); const readers = await Promise.all([playwright.request.newContext(), playwright.request.newContext()]); const cdp = await context.newCDPSession(page); const latency: { worker: number; endpoint: string; ms: number; atMs: number; bytes?: number; status?: number; error?: string }[] = []; const navigation: { index: number; route: string; atMs: number; readyMs: number; error?: string }[] = []; const preflightNavigation: typeof navigation = []; const telemetry: object[] = []; const ids: number[] = []; const evidence: Record = { fixtureJobs: 250, fixtureCareerRoles: 8, readConcurrency: 2, paceMs, requiredDurationMs: durationMs, database: info.config.metadata.mariaDbDatabase, latency, navigation, preflightNavigation, telemetry, ids, phase: 'setup', environment: { platform: os.platform(), cpus: os.cpus().length, totalMemoryBytes: os.totalmem(), browser: context.browser()?.version(), databaseProvider: 'MariaDB', frontend: 'prebuilt production static export' }, limits: 'Bounded local synthetic workload; no SLA, capacity, production, physical-device, model-inference or server CPU attribution claim. Host/Node/CDP telemetry is descriptive.' }; try { const server = await sql('SELECT VERSION()'); expect(server).toMatch(/^12\.2\.2-MariaDB(?:-|$)/); evidence.server = server; const adminOwner = await login(admin, 'e2e@example.test'); const email = `p9-requal-perf-${Date.now()}@example.test`; // A free synthetic account excludes creation-time model inference from this database/UI workload. expect((await admin.post(`${api}/users`, { headers: adminOwner.headers, data: { email, password, roles: [] } })).status()).toBe(200); const owner = await login(page.request, email); evidence.owner = owner.id; const company = await page.request.post(`${api}/companies`, { headers: owner.headers, data: { name: 'P9 requalification synthetic workload' } }); expect(company.status()).toBe(201); const companyId = (await company.json()).id; for (let index = 0; index < 250; index++) { const response = await page.request.post(`${api}/jobapplications`, { headers: owner.headers, data: { companyId, jobTitle: `P9 requalification role ${index}`, status: 'Applied', dateApplied: new Date().toISOString() } }); expect(response.status(), `seed application ${index}`).toBe(201); ids.push((await response.json()).id); } const profile = { version: '1', contact: { fullName: 'Åse Synthetic', email: 'synthetic@example.test' }, summary: ['Documented accessible workflows.'], jobs: Array.from({ length: 8 }, (_, index) => ({ id: `p9-role-${index}`, title: `Platform engineer ${index}`, company: 'Nordlys synthetic', start: '2017-01', end: '2025-12', bullets: Array.from({ length: 6 }, (_, bullet) => `Evidence ${index}.${bullet}: ${'Documented reliable workflows. Dokumenterte pålitelige arbeidsflyter. '.repeat(3).trim()}`) })) }; const initialProfile = await (await page.request.get(`${api}/career/profile`)).json(); expect((await page.request.put(`${api}/career/profile`, { headers: owner.headers, data: { expectedVersion: initialProfile.revision, profile } })).status()).toBe(200); const savedResponse = await (await page.request.get(`${api}/career/profile`)).json(); const savedProfile = savedResponse.profile; expect(savedProfile.jobs).toHaveLength(8); expect(savedProfile.jobs.map((job: { start: string; end: string; bullets: string[] }) => ({ start: job.start, end: job.end, bullets: job.bullets }))) .toEqual(profile.jobs.map(job => ({ start: job.start, end: job.end, bullets: job.bullets }))); const cv = await page.request.post(`${api}/cv/variants`, { headers: owner.headers, data: { name: 'P9 requalification long CV', settings: { themeId: 'minimal', language: 'nb' } } }); expect(cv.status()).toBe(200); const cvId = (await cv.json()).id; const before = await snapshot(owner.id); evidence.before = before; assertMembership(before.ids, ids); expect(before.ownerCount).toBe(250); expect(before.deletedCount).toBe(0); expect(before.revision).toBe(savedResponse.revision); expect(JSON.parse(before.profile)).toEqual(persistedCareerProfile(savedProfile)); await Promise.all(readers.map(reader => login(reader, email))); await page.addInitScript(() => localStorage.setItem('uiLanguage', 'en')); 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(); }); await cdp.send('Performance.enable'); const endpoints = ['/jobapplications/stats', '/jobapplications/board', '/career/profile', '/documents']; const routes = ['/dashboard', '/jobs?view=table', '/jobs?view=board', `/career/builder/${cvId}`]; evidence.phase = 'preflight'; for (const endpoint of endpoints) { const response = await page.request.get(`${api}${endpoint}`, { timeout: 10000 }); try { expect(response.status(), `preflight ${endpoint}`).toBe(200); const body = await response.json(); if (endpoint.endsWith('/board')) assertMembership(body.map((job: { id: number }) => job.id), ids); else if (endpoint.endsWith('/profile')) expect(body.profile).toEqual(savedProfile); else if (endpoint.endsWith('/stats')) expect(body.active).toBe(250); else expect(Array.isArray(body.items)).toBe(true); } finally { await response.dispose(); } } let started = performance.now(); let deadline = 0; const readWorker = async (worker: number) => { let index = 0; while (performance.now() < deadline) { const endpoint = endpoints[index++ % endpoints.length]; const begin = performance.now(); const row: typeof latency[number] = { worker, endpoint, atMs: begin - started, ms: 0 }; try { const response = await readers[worker].get(`${api}${endpoint}`, { timeout: 10000 }); try { row.status = response.status(); row.bytes = (await response.body()).length; expect(row.status, endpoint).toBe(200); if (endpoint.endsWith('/board')) assertMembership((await response.json()).map((job: { id: number }) => job.id), ids); if (endpoint.endsWith('/profile')) expect((await response.json()).profile).toEqual(savedProfile); } finally { await response.dispose(); } } catch (error) { row.error = String(error); } row.ms = performance.now() - begin; latency.push(row); // Completion-to-start pacing avoids catch-up bursts after a slow response. await waitUntil(Math.min(deadline, performance.now() + paceMs)); } }; const interact = async (preflight = false) => { for (let index = 0; index < (preflight ? 4 : 20); index++) { if (!preflight) await waitUntil(started + index * durationMs / 20); const route = routes[index % routes.length]; const begin = performance.now(); const row: typeof navigation[number] = { index, route, atMs: begin - started, readyMs: 0 }; try { await page.goto(route, { timeout: 20000 }); if (route === '/dashboard') await expect(page.getByRole('link', { name: 'Tracked jobs: 250', exact: true })).toBeVisible(); else if (route.endsWith('board')) { await expect(page.locator('[data-kanban-card-id]')).toHaveCount(250); assertMembership(await page.locator('[data-kanban-card-id]').evaluateAll(elements => elements.map(element => Number(element.getAttribute('data-kanban-card-id')))), ids); } else if (route.endsWith('table')) await expect(page.locator('[data-job-row-id]').first()).toBeVisible(); else { await expect(page.getByRole('region', { name: 'Live preview', exact: true })).toHaveAttribute('aria-busy', 'false'); const measured = await page.getByTitle('CV preview', { exact: true }).evaluate(element => { const doc = (element as HTMLIFrameElement).contentDocument!; const pages = Number(doc.documentElement.dataset.cvPreviewPages); const width = Number(doc.documentElement.dataset.cvPreviewWidth); const walker = doc.createTreeWalker(doc.body, NodeFilter.SHOW_TEXT); let node; let finalMarkerPage = 0; while ((node = walker.nextNode())) { const index = node.textContent?.indexOf('Evidence 7.5:') ?? -1; if (index < 0) continue; const range = doc.createRange(); range.setStart(node, index); range.setEnd(node, index + 'Evidence 7.5:'.length); finalMarkerPage = Math.floor(range.getClientRects()[0].x / width) + 1; } return { pages, finalMarkerPage, ready: doc.documentElement.dataset.cvPreviewReady }; }); expect(measured.ready).toBe('true'); expect(measured.pages).toBeGreaterThan(1); expect(measured.finalMarkerPage).toBe(measured.pages); await expect(page.locator('[data-cv-page]')).toHaveCount(measured.pages); await expect(page.getByTitle('CV preview page 1', { exact: true })).toBeVisible(); } telemetry.push({ phase: preflight ? 'preflight' : 'workload', atMs: performance.now() - started, metrics: (await cdp.send('Performance.getMetrics')).metrics, domElements: await page.locator('*').count(), hostFreeMemoryBytes: os.freemem(), hostLoadAverage: os.loadavg(), nodeMemoryBytes: process.memoryUsage(), nodeCpuMicroseconds: process.cpuUsage() }); } catch (error) { row.error = String(error); } row.readyMs = performance.now() - begin; (preflight ? preflightNavigation : navigation).push(row); if (preflight) expect(row.error, `preflight ${route}`).toBeUndefined(); } }; await interact(true); evidence.preflightPassed = true; evidence.phase = 'workload'; started = performance.now(); deadline = started + durationMs; evidence.startedUtc = new Date().toISOString(); await Promise.all([readWorker(0), readWorker(1), interact()]); evidence.actualDurationMs = performance.now() - started; evidence.finishedUtc = new Date().toISOString(); evidence.phase = 'preservation'; expect(evidence.actualDurationMs).toBeGreaterThanOrEqual(durationMs); const after = await snapshot(owner.id); evidence.after = after; assertMembership(after.ids, ids); expect(after).toEqual(before); const finalBoard = await page.request.get(`${api}/jobapplications/board`); expect(finalBoard.status()).toBe(200); assertMembership((await finalBoard.json()).map((job: { id: number }) => job.id), ids); expect((await (await page.request.get(`${api}/career/profile`)).json()).profile).toEqual(savedProfile); expect(summarize(latency).failures).toBe(0); expect(navigation).toHaveLength(20); expect(navigation.filter(row => row.error)).toEqual([]); for (const worker of [0, 1]) { const rows = latency.filter(row => row.worker === worker); expect(rows.length).toBeGreaterThan(0); for (let index = 1; index < rows.length; index++) expect(rows[index].atMs - rows[index - 1].atMs).toBeGreaterThanOrEqual(paceMs); expect(rows.at(-1)!.atMs).toBeGreaterThan(durationMs - 15000); } evidence.phase = 'complete'; } finally { evidence.summary = summarize(latency); evidence.byEndpoint = Object.fromEntries([...new Set(latency.map(row => row.endpoint))].map(endpoint => [endpoint, summarize(latency.filter(row => row.endpoint === endpoint))])); evidence.navigationFailures = navigation.filter(row => row.error).length; await info.attach('p9-requal-01-mariadb-performance', { body: JSON.stringify(evidence, null, 2), contentType: 'application/json' }); await cdp.detach(); await admin.dispose(); await Promise.all(readers.map(reader => reader.dispose())); } });