Alex Sample
Synthetic teacher profile
// Secondary routes in the actual export; every API result is a synthetic fixture. import { chromium, expect } from '@playwright/test'; import AxeBuilder from '@axe-core/playwright'; import ts from 'typescript'; import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; const compiled = ts.transpileModule(fs.readFileSync(new URL('../src/i18n/translations.ts', import.meta.url), 'utf8'), { compilerOptions: { module: ts.ModuleKind.ESNext } }).outputText; const { translations } = await import(`data:text/javascript;base64,${Buffer.from(compiled).toString('base64')}`); const visualMatrix = Boolean(process.env.A3_VISUAL_MATRIX); const offerDates = process.env.A3_OFFER_DATES === '1'; const out = path.resolve(import.meta.dirname, offerDates ? '../../docs/ui-modernisation/visual-correction/evidence/offer-dates' : visualMatrix ? '../../docs/ui-modernisation/visual-correction/evidence/secondary-after' : '../../docs/ui-modernisation/evidence/secondary/browser'); fs.mkdirSync(out, { recursive: true }); const report = { screens: [], journeys: [], errors: [], writes: [], unavailableReads: [], blockedExternal: [] }; const browser = await chromium.launch(); try { const configurations = offerDates ? ['America/Los_Angeles', 'Europe/Oslo', 'Pacific/Kiritimati'].flatMap(timeZone => ['en', 'nb'].map(language => [390, language, 'light', timeZone])) : process.env.A3_PILOT ? [[320, 'en', 'light']] : (visualMatrix ? [320, 360, 390, 430, 768, 1024, 1280, 1440, 1920] : [320, 390, 768, 1440]).flatMap(width => ['en', 'nb'].flatMap(language => ['light', 'dark'].map(mode => [width, language, mode]))); for (const [width, language, mode, timeZone] of configurations) { console.log(`Checking ${width}/${language}/${mode}/${timeZone ?? 'host'}`); const t = key => { assert.ok(translations[language][key], key); return translations[language][key]; }; const context = await browser.newContext({ viewport: { width, height: 1000 }, reducedMotion: 'reduce', ...(timeZone ? { timezoneId: timeZone } : {}) }); const page = await context.newPage(); page.on('pageerror', error => report.errors.push(error.message)); await page.addInitScript(({ language, mode }) => { localStorage.setItem('uiLanguage', language); localStorage.setItem('jobtracker.themeMode', mode); }, { language, mode }); const company = { id: 7, name: 'Recorded school', location: 'Oslo', website: null, contactName: 'Recorded contact', contactEmail: 'contact@example.test', contactPhone: null, notes: '', jobsCount: 1 }; const offers = [{ id: 42, jobTitle: 'Recorded teacher role', company, status: 'Offer', dateApplied: '2026-10-01', deadline: '2026-12-01', location: 'Oslo', salaryMin: 610000, salaryMax: 710000, salaryCurrency: 'NOK', salaryPeriod: 'year', notes: 'Recorded teaching role', followUpAt: null }, { id: 43, jobTitle: 'Recorded nursing role', company: { id: 8, name: 'Recorded clinic' }, status: 'Offer', dateApplied: '2026-10-02', location: 'Bergen', salaryMin: null, salaryMax: null, notes: '', deadline: null, followUpAt: null }]; if (offerDates) offers[0].followUpAt = '2026-12-01T00:30:00Z'; let authReads = 0; await page.route('**/*', route => { const req = route.request(), url = new URL(req.url()), p = url.pathname; if (url.origin !== 'http://localhost:4338' && !(url.origin === 'http://localhost:5202' && p.startsWith('/api/'))) { report.blockedExternal.push(url.origin); return route.abort(); } if (!p.startsWith('/api/')) return route.continue(); if (req.method() !== 'GET') { report.writes.push(p); return route.abort(); } const answer = data => route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(data) }); if (p === '/api/auth/me') authReads++; if (p === '/api/jobapplications') return answer({ items: new URL(page.url()).pathname === '/offers' ? offers : [], total: new URL(page.url()).pathname === '/offers' ? 2 : 0, page: 1, pageSize: 25 }); const fixtures = { '/api/auth/config': { requireAuth: true }, '/api/auth/me': { id: 'secondary-owner', sessionContext: 'secondary-session', provider: 'local', firstName: 'Alex', displayName: 'Alex Sample', roles: [], plan: 'pro', entitlements: { ai: false, proThemes: true }, uiLanguage: language === 'nb' ? 'nb-NO' : 'en' }, '/api/companies': [company], '/api/jobapplications/reminders': [], '/api/notifications/unread-count': { count: 0 }, '/api/operations': [{ id: '11111111-1111-4111-8111-111111111111', taskType: 'cv.process', status: 'running', createdAtUtc: '2026-11-03T10:00:00Z', progressStage: 'Recorded processing stage', progressPercent: 40, canCancel: false, canRetry: false }], '/api/notifications': [], '/api/jobapplications/stats': { total: 5, active: 4, byStatus: { Applied: 2, Offer: 2, Saved: 1 } }, '/api/jobapplications/analytics-overview': { funnel: [{ label: 'Applied', count: 2 }], responseRateBySource: [{ label: 'NAV', total: 4, responses: 1, rate: 25 }], topCompanies: [{ companyId: 7, company: company.name, count: 4, responses: 1, responseRate: 25 }], timeInStage: [{ stage: 'Applied', medianDays: 3, count: 2 }], salaryInsights: [{ currency: 'NOK', period: 'year', count: 2, minimum: 610000, maximum: 710000, averageMidpoint: 660000 }] }, '/api/jobapplications/analytics': [{ month: '2026-10', applied: 2, responses: 1 }], '/api/jobapplications/tags': [{ tag: 'Teaching', count: 2 }], '/api/jobapplications/tag-trends': { months: ['2026-10'], series: [{ tag: 'Teaching', counts: [2] }] }, '/api/public-cv/synthetic': { name: 'Alex Sample', html: '
Synthetic teacher profile