test(qa): qualify route matrix and document stale-write failure

This commit is contained in:
cesnimda
2026-10-22 22:27:51 +02:00
parent 322b025350
commit 82be698dcf
69 changed files with 41364 additions and 1 deletions
@@ -0,0 +1,74 @@
import { test, expect } from '../../../../../job-tracker-ui/node_modules/@playwright/test';
import AxeBuilder from '../../../../../job-tracker-ui/node_modules/@axe-core/playwright';
import { translations } from '../../../../../job-tracker-ui/src/i18n/translations';
const api = 'http://localhost:5302/api';
for (const language of ['en', 'nb'] as const) for (const theme of ['light', 'dark'] as const) {
test(`P9 settled badge drawer and editor triage ${language} ${theme}`, async ({ page, context, playwright }, info) => {
test.setTimeout(120000);
const t = translations[language];
await page.setViewportSize({ width: 320, height: 900 });
await page.emulateMedia({ reducedMotion: 'reduce' });
await context.addInitScript(({ language, theme }) => {
localStorage.setItem('uiLanguage', language); localStorage.setItem('jobtracker.themeMode', theme);
}, { language, theme });
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();
});
const admin = await playwright.request.newContext();
try {
const password = 'E2ePassword123!';
expect((await admin.post(`${api}/auth/login`, { data: { email: 'e2e@example.test', password } })).status()).toBe(200);
const adminMe = await (await admin.get(`${api}/auth/me`)).json();
const csrf = (await admin.storageState()).cookies.find(cookie => cookie.name === 'XSRF-TOKEN')!.value;
const email = `p9-axe-${language}-${theme}-${Date.now()}@example.test`;
expect((await admin.post(`${api}/users`, { headers: { 'X-CSRF-TOKEN': csrf, 'X-Session-Context': adminMe.sessionContext }, data: { email, password, roles: [] } })).status()).toBe(200);
expect((await page.request.post(`${api}/auth/login`, { data: { email, password } })).status()).toBe(200);
const me = await (await page.request.get(`${api}/auth/me`)).json();
const ownerCsrf = (await page.request.storageState()).cookies.find(cookie => cookie.name === 'XSRF-TOKEN')!.value;
expect((await page.request.put(`${api}/auth/preferences/language`, { headers: { 'X-CSRF-TOKEN': ownerCsrf, 'X-Session-Context': me.sessionContext }, data: { language: language === 'nb' ? 'nb-NO' : 'en' } })).status()).toBe(204);
await page.goto('/dashboard');
await expect(page.getByRole('button', { name: t.dashboardCustomize, exact: true })).toBeVisible();
const badge = await page.locator('.MuiBadge-invisible').evaluateAll(nodes => nodes.map(node => ({
ariaHidden: node.getAttribute('aria-hidden'), text: node.textContent, width: node.getBoundingClientRect().width,
height: node.getBoundingClientRect().height, visibility: getComputedStyle(node).visibility, transform: getComputedStyle(node).transform })));
expect(badge.length).toBeGreaterThan(0);
expect(badge.every(node => node.ariaHidden === 'true' && node.width * node.height === 0)).toBe(true);
const scans: object[] = [];
const scan = async (state: string) => {
const result = await new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa']).analyze();
scans.push({ state, violations: result.violations, incomplete: result.incomplete });
expect.soft(result.violations, state).toEqual([]);
};
await scan('dashboard-settled');
const trigger = page.getByRole('button', { name: t.openNavigation, exact: true });
await trigger.focus(); await trigger.press('Enter');
const drawer = page.getByRole('dialog', { name: t.navigation, exact: true });
await expect(drawer.getByRole('button', { name: t.dashboard, exact: true })).toBeFocused();
await scan('drawer-open');
await page.keyboard.press('Escape');
await expect(drawer).not.toBeVisible(); await expect(trigger).toBeFocused();
// Backdrop/root exit can precede Slide exit; await the complete settled predicate.
const inspectDrawer = () => page.locator('.MuiDrawer-modal').evaluateAll(nodes => nodes.map(node => ({
ariaHidden: node.getAttribute('aria-hidden'), visibility: getComputedStyle(node).visibility,
containsActive: node.contains(document.activeElement),
visibleTabbables: [...node.querySelectorAll('[tabindex="0"],button,a[href],input,select')].filter(element => getComputedStyle(element).visibility !== 'hidden' && element.getClientRects().length > 0).length })));
const drawerAfterEscape = await inspectDrawer();
await expect.poll(async () => (await inspectDrawer()).every(node => node.ariaHidden === 'true' && node.visibility === 'hidden' && !node.containsActive && node.visibleTabbables === 0)).toBe(true);
const hiddenDrawer = await inspectDrawer();
expect(hiddenDrawer.every(node => node.ariaHidden === 'true' && !node.containsActive && node.visibleTabbables === 0)).toBe(true);
for (let index = 0; index < 24; index++) {
await page.keyboard.press('Tab');
expect(await page.evaluate(() => !!document.activeElement?.closest('.MuiDrawer-modal[aria-hidden="true"]'))).toBe(false);
}
await scan('drawer-closed-settled');
await page.getByRole('button', { name: t.dashboardCustomize, exact: true }).click();
const editor = page.getByRole('region', { name: t.dashboardEditing, exact: true });
await expect(editor.getByRole('combobox')).toHaveCount(9);
await scan('editor-settled');
await info.attach('phase9-settled-axe', { body: JSON.stringify({ language, theme, badge, drawerAfterEscape, hiddenDrawer, scans,
limits: 'Finite native DOM/keyboard evidence; remaining overlapping background contrast and actual screen-reader perception still require human assessment.' }, null, 2), contentType: 'application/json' });
} finally { await admin.dispose(); }
});
}
@@ -0,0 +1,49 @@
import { test, expect, type APIRequestContext } from '../../../../../job-tracker-ui/node_modules/@playwright/test';
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();
const csrf = (await request.storageState()).cookies.find(cookie => cookie.name === 'XSRF-TOKEN')!.value;
return { 'X-CSRF-TOKEN': csrf, 'X-Session-Context': me.sessionContext as string };
}
test('P9 characterize retained RD21 stale writes separately from CV revision safety', async ({ playwright }, info) => {
const admin = await playwright.request.newContext();
const first = await playwright.request.newContext();
const second = await playwright.request.newContext();
try {
const adminHeaders = await login(admin, 'e2e@example.test');
const email = `p9-stale-${Date.now()}@example.test`;
expect((await admin.post(`${api}/users`, { headers: adminHeaders, data: { email, password, roles: [] } })).status()).toBe(200);
const a = await login(first, email); const b = await login(second, email);
const submitted = { version: '1', contact: { fullName: 'Synthetic concurrent owner' }, summary: ['Initial summary'] };
expect((await first.put(`${api}/career/profile`, { headers: a, data: { profile: submitted } })).status()).toBe(200);
const careerA = (await (await first.get(`${api}/career/profile`)).json()).profile;
const careerB = (await (await second.get(`${api}/career/profile`)).json()).profile;
careerA.summary = ['First editor committed summary']; careerB.contact.headline = 'Second editor headline';
expect((await first.put(`${api}/career/profile`, { headers: a, data: { profile: careerA } })).status()).toBe(200);
const staleCareer = await second.put(`${api}/career/profile`, { headers: b, data: { profile: careerB } });
expect([200, 409]).toContain(staleCareer.status());
const careerAfter = (await (await first.get(`${api}/career/profile`)).json()).profile;
const company = await first.post(`${api}/companies`, { headers: a, data: { name: 'Synthetic stale job fixture' } });
expect(company.status()).toBe(201);
const created = await first.post(`${api}/jobapplications`, { headers: a, data: { companyId: (await company.json()).id, jobTitle: 'Original job title', status: 'Saved' } });
expect(created.status()).toBe(201); const jobId = (await created.json()).id;
const jobA = await (await first.get(`${api}/jobapplications/${jobId}`)).json();
const jobB = await (await second.get(`${api}/jobapplications/${jobId}`)).json();
jobA.jobTitle = 'First editor committed title'; jobB.notes = 'Second editor notes';
expect([200, 204]).toContain((await first.put(`${api}/jobapplications/${jobId}`, { headers: a, data: jobA })).status());
const staleJob = await second.put(`${api}/jobapplications/${jobId}`, { headers: b, data: jobB });
expect([200, 204, 409]).toContain(staleJob.status());
const jobAfter = await (await first.get(`${api}/jobapplications/${jobId}`)).json();
const careerLostFirstEdit = careerAfter.summary[0] !== careerA.summary[0];
const jobLostFirstEdit = jobAfter.jobTitle !== jobA.jobTitle;
await info.attach('phase9-rd21-characterization', { body: JSON.stringify({ id: 'RD21',
career: { staleStatus: staleCareer.status(), expectedFirstSummary: careerA.summary, actualSummary: careerAfter.summary, lostFirstEdit: careerLostFirstEdit },
job: { staleStatus: staleJob.status(), expectedFirstTitle: jobA.jobTitle, actualTitle: jobAfter.jobTitle, lostFirstEdit: jobLostFirstEdit },
safetyQualification: careerLostFirstEdit || jobLostFirstEdit ? 'FAIL: stale writes can overwrite acknowledged edits' : 'Finite scenario retained prior edits',
limitation: 'This passing characterization records observed behavior; it does not accept unsafe stale writes or provide a new revision contract. Creation-intent idempotency remains separate.' }, null, 2), contentType: 'application/json' });
} finally { await second.dispose(); await first.dispose(); await admin.dispose(); }
});
@@ -0,0 +1,121 @@
import { test, expect, type APIRequestContext } from '../../../../../job-tracker-ui/node_modules/@playwright/test';
import { translations, type TranslationKey } from '../../../../../job-tracker-ui/src/i18n/translations';
import fs from 'node:fs/promises';
import path from 'node:path';
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();
const csrf = (await request.storageState()).cookies.find(cookie => cookie.name === 'XSRF-TOKEN')!.value;
return { 'X-CSRF-TOKEN': csrf, 'X-Session-Context': me.sessionContext as string };
}
const publicRoutes: [string, TranslationKey | null][] = [
['/', null], ['/login', 'signInTitle'], ['/register', 'createAccount'],
['/forgot-password', 'forgotPasswordTitle'], ['/reset-password', 'resetPasswordTitle'],
['/verify-email', 'verifyEmailTitle'], ['/confirm-email-change', 'confirmEmailChangeTitle'],
['/microsoft-legacy-relink', 'microsoftLegacyRelinkTitle'], ['/cv/:slug', 'publicCvTitle'],
];
const privateRoutes: [string, TranslationKey, string][] = [
['/dashboard', 'dashboard', '/jobapplications/stats'], ['/analytics', 'analytics', '/jobapplications/stats'],
['/discover', 'discoverJobs', '/job-discovery/saved-searches'], ['/jobs', 'jobApplications', '/jobapplications'],
['/jobs/:id', 'jobDetails', '/jobapplications/:id/workspace'], ['/reminders', 'reminders', '/jobapplications/reminders'],
['/operations', 'operations', '/operations'], ['/kanban', 'jobApplications', '/jobapplications/board'],
['/companies', 'companies', '/companies'], ['/offers', 'offerComparisonTitle', '/jobapplications'],
['/correspondence', 'correspondenceInboxTitle', '/correspondence/page'],
['/correspondence/review', 'correspondenceInboxTitle', '/gmail/review-candidates'],
['/profile', 'profile', '/auth/me'], ['/applications/:id', 'jobDetails', '/jobapplications/:id/workspace'],
['/career', 'careerWorkspace', '/career/profile'], ['/career/builder', 'cvBuilder', '/cv/variants'],
['/career/builder/:id', 'cvBuilder', '/cv/variants/:cv'], ['/documents', 'documentsTitle', '/documents'],
['/admin/audit', 'auditLog', '/admin/audit'], ['/admin/users', 'users', '/users'],
['/admin/system', 'systemStatus', '/admin/system'], ['/trash', 'trash', '/jobapplications'],
['/settings', 'settings', '/notification-settings'], ['/settings/connected-accounts', 'connectedAccounts', '/gmail/status'],
];
for (const width of [320, 768, 1440]) for (const language of ['en', 'nb'] as const) for (const theme of ['light', 'dark'] as const) {
test(`P9 all 33 routes ${width} ${language} ${theme}`, async ({ page, context, playwright }, info) => {
test.setTimeout(360000);
const t = translations[language];
const census = JSON.parse(await fs.readFile(path.resolve(__dirname, '../../p9-00/inventory.json'), 'utf8'));
expect([...publicRoutes.map(row => row[0]), ...privateRoutes.map(row => row[0])].sort()).toEqual(Object.keys(census.routes).sort());
await page.setViewportSize({ width, height: 900 });
await context.addInitScript(({ language, theme }) => {
localStorage.setItem('uiLanguage', language);
localStorage.setItem('jobtracker.themeMode', theme);
}, { language, theme });
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();
});
const samples: object[] = [];
const capture = async (pattern: string, index: number, status?: number) => {
await page.evaluate(async () => { await document.fonts.ready; await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve))); });
const state = await page.evaluate(() => ({ width: innerWidth, scrollWidth: document.documentElement.scrollWidth,
title: document.title, headings: [...document.querySelectorAll('h1')].map(node => node.textContent),
lang: document.documentElement.lang, effectiveTheme: document.documentElement.getAttribute('data-color-scheme'),
bodyText: document.body.innerText, domElements: document.querySelectorAll('*').length }));
const visibleLines = new Set(state.bodyText.split('\n').map(line => line.trim()));
const englishLeakCandidates = language === 'nb' ? Object.entries(translations.en)
.filter(([key, value]) => value.length > 6 && translations.nb[key as TranslationKey] !== value && visibleLines.has(value))
.map(([key, value]) => ({ key, value })) : [];
samples.push({ pattern, viewport: width, language, theme, status, ...state, englishLeakCandidates });
expect.soft(state.effectiveTheme, `${pattern} applied theme`).toBe(theme);
expect.soft(state.lang, `${pattern} applied locale`).toBe(language === 'nb' ? 'nb-NO' : 'en');
expect.soft(state.scrollWidth, `${pattern} root overflow`).toBeLessThanOrEqual(width + 1);
expect.soft(state.bodyText, `${pattern} raw translation key`).not.toMatch(/\b(?:dashboardCustomize|documentsPageSubtitle|careerWorkspaceSubtitle)\b/);
const screenshot = info.outputPath(`route-${String(index).padStart(2, '0')}-${width}-${language}-${theme}.png`);
await page.screenshot({ path: screenshot, fullPage: true, animations: 'disabled' });
await info.attach(`route-${index}-${pattern}`, { path: screenshot, contentType: 'image/png' });
};
const admin = await playwright.request.newContext();
let createdId: string | undefined;
let adminHeaders: Record<string, string> | undefined;
try {
for (const [index, [pattern, key]] of publicRoutes.entries()) {
await page.goto(pattern.replace(':slug', 'phase9-unpublished'));
if (key) await expect(page.getByRole('heading', { name: t[key], exact: true })).toBeVisible();
else await expect(page.getByRole('heading', { level: 1 }).first()).toBeVisible();
if (pattern === '/cv/:slug') await expect(page.getByText(t.publicCvUnavailableBody, { exact: true })).toBeVisible();
await capture(pattern, index);
}
adminHeaders = await login(admin, 'e2e@example.test');
const email = `phase9-matrix-${width}-${language}-${theme}-${Date.now()}@example.test`;
const created = await admin.post(`${api}/users`, { headers: adminHeaders, data: { email, password, roles: ['Admin'] } });
expect(created.status()).toBe(200); createdId = (await created.json()).id;
const headers = await login(page.request, email);
expect((await page.request.put(`${api}/auth/preferences/language`, { headers, data: { language: language === 'nb' ? 'nb-NO' : 'en' } })).status()).toBe(204);
const company = await page.request.post(`${api}/companies`, { headers, data: { name: 'Phase 9 — Nordlys dokumenterte arbeidsflyter æøå' } });
expect(company.status()).toBe(201);
const job = await page.request.post(`${api}/jobapplications`, { headers, data: { companyId: (await company.json()).id, jobTitle: 'Phase 9 reliable workflow engineer — dokumentert erfaring', status: 'Saved' } });
expect(job.status()).toBe(201); const jobId = (await job.json()).id;
const cv = await page.request.post(`${api}/cv/variants`, { headers, data: { name: 'Phase 9 synthetic CV' } });
expect(cv.status()).toBe(200); const cvId = (await cv.json()).id;
for (const [index, [pattern, key, endpoint]] of privateRoutes.entries()) {
const url = pattern.replace(':id', pattern.startsWith('/career/builder/') ? String(cvId) : String(jobId));
const apiPath = `/api${endpoint.replace(':id', String(jobId)).replace(':cv', String(cvId))}`;
const response = page.waitForResponse(response => new URL(response.url()).pathname === apiPath && response.request().method() === 'GET');
await page.goto(url);
const status = (await response).status();
expect.soft(status, pattern).toBe(pattern === '/correspondence/review' ? 409 : 200);
await expect(page.getByRole('heading', { level: 1, name: t[key], exact: true })).toBeVisible();
if (pattern === '/career/builder/:id') {
if (width < 900) await page.getByRole('button', { name: t.cvEditorPreview, exact: true }).click();
await expect(page.getByRole('region', { name: t.cvEditorLivePreview, exact: true })).toHaveAttribute('aria-busy', 'false');
await expect(page.getByTitle(t.cvEditorPreviewPage.replace('{page}', '1'), { exact: true })).toBeVisible();
}
if (pattern === '/dashboard') await expect(page.locator('[data-dashboard-widget]')).toHaveCount(9);
if (pattern === '/reminders') await expect(page.getByRole('region', { name: t.remindersTitle, exact: true })).toHaveAttribute('aria-busy', 'false');
if (pattern === '/profile') await expect(page.getByLabel(t.profileUsername, { exact: true })).toHaveValue(email);
if (pattern === '/admin/users' && width >= 768) await expect(page.getByText(language === 'nb' ? 'Rader per side:' : 'Rows per page:', { exact: true })).toBeVisible();
await capture(pattern, index + publicRoutes.length, status);
}
} finally {
await info.attach('phase9-route-matrix', { body: JSON.stringify({ width, language, theme, samples,
limits: 'Screenshots and DOM evidence; actual visual review must be recorded separately. No provider/device/human linguistic acceptance.' }, null, 2), contentType: 'application/json' });
if (createdId) expect((await admin.put(`${api}/users/${createdId}/roles`, { headers: adminHeaders, data: { roles: [] } })).status()).toBe(204);
await admin.dispose();
}
});
}
@@ -0,0 +1,8 @@
import { defineConfig } from '../../../../../job-tracker-ui/node_modules/@playwright/test';
import base from '../../../../../job-tracker-ui/playwright.config';
import path from 'node:path';
const cwd = path.resolve(__dirname, '../../../../../job-tracker-ui');
const servers = Array.isArray(base.webServer) ? base.webServer : [base.webServer!];
export default defineConfig({ ...base, testDir: __dirname, testMatch: '*.spec.ts',
webServer: servers.map(server => ({ ...server, cwd })) });
@@ -0,0 +1,99 @@
import { test, expect, type APIRequestContext } from '../../../../../job-tracker-ui/node_modules/@playwright/test';
import os from 'node:os';
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();
const csrf = (await request.storageState()).cookies.find(cookie => cookie.name === 'XSRF-TOKEN')!.value;
return { 'X-CSRF-TOKEN': csrf, 'X-Session-Context': me.sessionContext as string };
}
test('P9 bounded sustained reads with 250 jobs and long CV while using native workspaces', async ({ page, context, playwright }, info) => {
test.setTimeout(240000);
const admin = await playwright.request.newContext();
const latency: { worker: number; endpoint: string; ms: number; bytes: number; status: number }[] = [];
const navigation: object[] = [];
const cdp = await context.newCDPSession(page);
await cdp.send('Performance.enable');
try {
const adminHeaders = await login(admin, 'e2e@example.test');
const email = `p9-sustained-${Date.now()}@example.test`;
expect((await admin.post(`${api}/users`, { headers: adminHeaders, data: { email, password, roles: ['Premium'] } })).status()).toBe(200);
const headers = await login(page.request, email);
const company = await page.request.post(`${api}/companies`, { headers, data: { name: 'P9 bounded workload' } });
expect(company.status()).toBe(201); const companyId = (await company.json()).id;
for (let index = 0; index < 250; index++) expect((await page.request.post(`${api}/jobapplications`, { headers,
data: { companyId, jobTitle: `P9 bounded role ${index}`, status: 'Applied', dateApplied: new Date().toISOString() } })).status()).toBe(201);
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()}`) })) };
expect((await page.request.put(`${api}/career/profile`, { headers, data: { profile } })).status()).toBe(200);
const savedProfile = (await (await page.request.get(`${api}/career/profile`)).json()).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, data: { name: 'P9 long workload CV', settings: { themeId: 'minimal', language: 'nb' } } });
expect(cv.status()).toBe(200); const cvId = (await cv.json()).id;
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();
});
const endpoints = ['/jobapplications/stats', '/jobapplications/board', '/career/profile', '/documents'];
const started = Date.now();
const readWorker = async (worker: number) => {
for (let index = 0; index < 120; index++) {
const endpoint = endpoints[index % endpoints.length]; const begin = Date.now();
const response = await page.request.get(`${api}${endpoint}`);
latency.push({ worker, endpoint, ms: Date.now() - begin, bytes: (await response.body()).length, status: response.status() });
expect(response.status(), endpoint).toBe(200);
if (endpoint.endsWith('/board')) expect(await response.json()).toHaveLength(250);
await new Promise(resolve => setTimeout(resolve, 500));
}
};
const interact = async () => {
for (let cycle = 0; cycle < 5; cycle++) for (const route of ['/dashboard', '/jobs?view=table', '/jobs?view=board', `/career/builder/${cvId}`]) {
const begin = Date.now(); await page.goto(route);
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);
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();
}
navigation.push({ cycle, route, readyMs: Date.now() - begin,
metrics: (await cdp.send('Performance.getMetrics')).metrics,
domElements: await page.locator('*').count() });
}
};
await Promise.all([readWorker(0), readWorker(1), interact()]);
const durationMs = Date.now() - started;
expect(durationMs).toBeGreaterThanOrEqual(60000);
expect(latency).toHaveLength(240);
expect((await (await page.request.get(`${api}/jobapplications/board`)).json())).toHaveLength(250);
expect((await (await page.request.get(`${api}/career/profile`)).json()).profile).toEqual(savedProfile);
const sorted = latency.map(row => row.ms).sort((a, b) => a - b);
await info.attach('phase9-sustained-performance', { body: JSON.stringify({ durationMs, fixtureJobs: 250, fixtureCareerRoles: 8,
readConcurrency: 2, reads: latency.length, p50Ms: sorted[Math.floor(sorted.length * .5)], p95Ms: sorted[Math.floor(sorted.length * .95)], maxMs: sorted.at(-1),
environment: { platform: os.platform(), cpus: os.cpus().length, totalMemoryBytes: os.totalmem(), browser: context.browser()?.version(), database: 'isolated SQLite', frontend: 'production static export' },
latency, navigation, limits: 'Finite paced two-reader workload plus 20 native navigations; no SLA/capacity, physical mobile, deployed Linux, model inference or server CPU attribution.' }, null, 2), contentType: 'application/json' });
} finally { await cdp.detach(); await admin.dispose(); }
});