test(ui): qualify flat administration across responsive locales
This commit is contained in:
@@ -8,14 +8,16 @@ 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 accountOnly = Boolean(process.env.A3_ACCOUNT_FLAT);
|
||||
const out = path.resolve(import.meta.dirname, accountOnly ? '../../docs/ui-modernisation/visual-correction/evidence/account-flat' : '../../docs/ui-modernisation/evidence/account-admin/browser');
|
||||
const adminOnly = Boolean(process.env.A3_ADMIN_FLAT);
|
||||
const out = path.resolve(import.meta.dirname, adminOnly ? '../../docs/ui-modernisation/visual-correction/evidence/admin-flat' : accountOnly ? '../../docs/ui-modernisation/visual-correction/evidence/account-flat' : '../../docs/ui-modernisation/evidence/account-admin/browser');
|
||||
fs.mkdirSync(out, { recursive: true });
|
||||
const report = { screens: [], journeys: [], errors: [], unexpectedWrites: [], exportDenials: [], unavailableReads: [], blockedExternal: [] };
|
||||
const browser = await chromium.launch();
|
||||
const processingOnly = Boolean(process.env.A3_PROCESSING_ONLY);
|
||||
assert.ok(!(processingOnly && accountOnly), 'Account and processing qualification are distinct scopes.');
|
||||
assert.ok(!adminOnly || !(processingOnly || accountOnly), 'Admin qualification is a distinct scope.');
|
||||
try {
|
||||
const configurations = process.env.A3_PILOT ? [[320, 'en', 'light']] : (accountOnly ? [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])));
|
||||
const configurations = process.env.A3_PILOT ? (adminOnly ? [[320, 'en', 'light'], [1440, 'en', 'light']] : [[320, 'en', 'light']]) : (accountOnly || adminOnly ? [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] of configurations) {
|
||||
console.log(`Checking ${width}/${language}/${mode}`);
|
||||
const t = key => { assert.ok(translations[language][key], key); return translations[language][key]; };
|
||||
@@ -25,6 +27,17 @@ try {
|
||||
const session = { id: 'current', deviceLabel: 'Synthetic current device', isCurrentSession: true, createdAtUtc: '2026-11-03T10:00:00Z', lastSeenAtUtc: '2026-11-03T10:00:00Z', expiresAtUtc: '2026-12-03T10:00:00Z' };
|
||||
const operation = { id: '11111111-1111-4111-8111-111111111111', owner: { id: 'a3-admin-owner', name: 'Alex Sample', email: 'alex@example.test' }, taskType: 'cv.process', status: 'failed', stage: 'cv_extracting', attemptCount: 1, maxAttempts: 3, createdAtUtc: '2026-11-03T10:00:00Z', startedAtUtc: null, completedAtUtc: null, lastHeartbeatAtUtc: null, durationSeconds: 8, worker: 'document', workerEnabled: false, failureCode: 'parser_failed', lastFailureAtUtc: '2026-11-03T10:00:08Z', lastFailureStage: 'cv_extracting', job: { id: 42, title: 'Recorded teacher application', company: 'Synthetic school', canOpen: false }, document: { importId: 7, name: 'Recorded teacher CV.pdf', type: 'application/pdf', bytes: 500, pages: 2, state: 'failed' } };
|
||||
const listing = { items: [operation], total: 1, counts: { failed: 1 }, workers: [{ name: 'CvDocumentImports', flagEnabled: false, individualOperationsRecorded: true }] };
|
||||
let populatedAudit = false, systemAvailable = false;
|
||||
const system = {
|
||||
environment: 'Synthetic qualification', contentRoot: '/synthetic/app', version: 'fixture', commitSha: 'fixture', buildStamp: '2026-11-04',
|
||||
storage: { dataRoot: '/synthetic', dbPath: '/synthetic/db', dbExists: true, dbSizeBytes: 2048, companyCount: 3, jobCount: 7, deletedCount: 1 },
|
||||
email: { enabled: false, host: 'smtp.example.test', port: 587, enableSsl: true, from: 'noreply@example.test', fromName: 'Synthetic' },
|
||||
database: { provider: 'mariadb', looksConfigured: true, canConnect: true, target: 'synthetic', usesFileStorage: false, warning: null },
|
||||
runtime: { framework: 'Synthetic .NET', osDescription: 'Fixture', processArchitecture: 'X64', machineName: 'fixture' },
|
||||
auth: { required: true, hasJwtKey: true, googleConfigured: false, gmailConfigured: false },
|
||||
ai: { healthy: false, model: null, device: null, gpuAvailable: false, gpuName: null, ocrAvailable: false, ocrLanguages: '', ollamaConfigured: false, ollamaReachable: false, ollamaModel: null, ollamaModelAvailable: false, ollamaVersion: null, ollamaInstalledModels: [], ollamaLoadedModels: [], ollamaLoadedCount: 0, healthLatencyMs: 0, probeLatencyMs: 0, probeFailures: 0, requests: 0, cacheHits: 0, cacheMisses: 0, failures: 0, averageLatencyMs: 0, ocrRequests: 0, ocrFailures: 0, averageOcrLatencyMs: 0, lastError: null },
|
||||
};
|
||||
const benchmark = { rootPath: '/synthetic/benchmarks', lastUpdatedAtUtc: '2026-11-04T10:00:00Z', reportMarkdown: '# Synthetic report\n\nFixture only.', indexJson: JSON.stringify({ CorpusRoot: '/synthetic/corpus', OutputRoot: '/synthetic/benchmarks', GeneratedAtUtc: '2026-11-04T10:00:00Z', TotalFiles: 1, AverageCoverage: 0.5, AverageConfidence: 0.65, AverageConsistency: 0.8, FilesWithSuspiciousLocations: 1, MissingApprovedFixtures: 1, Entries: [{ FileName: 'Synthetic-teacher.txt', Slug: 'synthetic-teacher', Extension: '.txt', Characters: 2000, OutputPath: '/synthetic/output.json', ApprovedFixturePath: null, CandidateFixturePath: '/synthetic/candidate.json', ContactLocation: 'Oslo, Hobbies', FirstJob: 'Teacher', FirstEducation: 'Education degree', QualificationLevels: ['Other'], SuspiciousLocations: [], CoverageScore: 0.5, ConfidenceScore: 0.65, ConsistencyScore: 0.8, DiffSummary: 'Synthetic unapproved fixture.' }] }) };
|
||||
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(); }
|
||||
@@ -43,10 +56,16 @@ try {
|
||||
'/api/account-lifecycle/status': { deletionEnabled: true, deletionStatus: 'active', requiredConfirmation: 'DELETE alex@example.test', request: null },
|
||||
'/api/gmail/status': { connected: false, configured: false }, '/api/microsoft-graph/status': { connected: false, configured: false }, '/api/imap/status': { connected: false, configured: false },
|
||||
'/api/users': [{ id: 'a3-admin-owner', email: 'alex@example.test', userName: 'alex', emailConfirmed: true, roles: ['Admin'], isCurrentUser: true, canRemoveAdmin: false }],
|
||||
'/api/admin/audit': [], '/api/admin/processing': listing,
|
||||
'/api/admin/audit': populatedAudit ? [{ id: 1, type: 'StatusChanged', oldValue: 'Applied', newValue: 'Interview', note: 'Synthetic audit event', at: '2026-11-04T10:00:00Z', jobApplicationId: 42, jobTitle: operation.job.title, companyName: operation.job.company, ownerUserId: 'a3-admin-owner', ownerEmail: 'alex@example.test', ownerUserName: 'alex' }] : [], '/api/admin/processing': listing,
|
||||
[`/api/admin/processing/${operation.id}`]: operation,
|
||||
[`/api/admin/processing/${operation.id}/events`]: { items: [{ id: '9007199254740993', atUtc: '2026-11-03T10:00:08Z', kind: 'Failed', status: 'failed', stage: 'cv_extracting', attempt: 1, failureCode: 'parser_failed' }], hasMore: false, retentionDays: 30 },
|
||||
};
|
||||
if (systemAvailable) Object.assign(fixtures, {
|
||||
'/api/admin/system': system,
|
||||
'/api/admin/system/email-settings': { ...system.email, user: 'mailer@example.test', timeoutMs: 15000, usesOverrides: false, hasPassword: false },
|
||||
'/api/admin/system/cv-benchmark': benchmark,
|
||||
'/api/admin/system/maintenance-jobs': [{ id: 'cv-benchmark', title: 'Synthetic benchmark', description: 'Fixture only', available: false, runsUrl: null }],
|
||||
});
|
||||
if (Object.hasOwn(fixtures, p)) return answer(fixtures[p]);
|
||||
report.unavailableReads.push(p); return route.fulfill({ status: 503, contentType: 'application/json', body: JSON.stringify({ detail: 'Controlled offline read unavailable.' }) });
|
||||
});
|
||||
@@ -58,6 +77,7 @@ try {
|
||||
await page.screenshot({ path: path.join(out, `${state}-${language}-${mode}-${width}.png`), fullPage: width >= 768 && !keepScroll });
|
||||
};
|
||||
if (!processingOnly) {
|
||||
if (!adminOnly) {
|
||||
await page.goto('http://localhost:4338/profile');
|
||||
const name = page.getByRole('textbox', { name: t('profileDisplayName'), exact: true }); await expect(name).toHaveValue('Alex Sample'); await name.fill('Unsaved account name');
|
||||
const nav = page.getByRole('navigation', { name: t('accountWorkspaceNavigation'), exact: true });
|
||||
@@ -72,10 +92,19 @@ try {
|
||||
await page.getByRole('button', { name: t('accountExportDownload'), exact: true }).click(); await expect(page.getByText(t('accountExportFailed'), { exact: true })).toBeVisible();
|
||||
await page.getByRole('button', { name: t('accountDeleteButton'), exact: true }).click(); const deletion = page.getByRole('dialog'); await expect(deletion).toContainText('DELETE alex@example.test'); await scan('data-confirmation'); await deletion.getByRole('button', { name: t('cancel'), exact: true }).click();
|
||||
await page.goto('http://localhost:4338/settings/connected-accounts'); await expect(page.getByRole('heading', { name: t('connectedAccounts'), exact: true })).toBeVisible(); await scan('connections');
|
||||
}
|
||||
if (!accountOnly) {
|
||||
await page.goto('http://localhost:4338/admin/users'); await expect(page.getByRole('button', { name: t('adminUsersRemoveAdmin'), exact: true })).toBeDisabled(); await expect(page.getByRole('button', { name: t('adminUsersDelete'), exact: true })).toBeDisabled(); await scan('last-admin');
|
||||
await page.goto('http://localhost:4338/admin/audit'); await expect(page.getByText(t('adminAuditNoEvents'), { exact: true })).toBeVisible(); await scan('audit-empty');
|
||||
if (adminOnly) { populatedAudit = true; await page.reload(); await expect(page.getByRole('cell', { name: 'Applied -> Interview Synthetic audit event', exact: true })).toBeVisible(); await scan('audit-populated'); }
|
||||
await page.goto('http://localhost:4338/admin/system'); await expect(page.getByText(t('adminSystemLoadFailed'), { exact: true })).toBeVisible(); await scan('system-unavailable');
|
||||
if (adminOnly) {
|
||||
systemAvailable = true; await page.reload(); await expect(page.getByText(system.environment, { exact: true })).toBeVisible(); await scan('system-normal');
|
||||
const benchmarkHeading = page.getByRole('heading', { name: t('adminBenchmarkTitle'), exact: true });
|
||||
await benchmarkHeading.evaluate(el => window.scrollBy({ top: el.getBoundingClientRect().top - 90, behavior: 'instant' })); await scan('system-benchmark', true);
|
||||
await page.getByRole('heading', { name: t('adminBenchmarkTopFindings'), exact: true }).evaluate(el => window.scrollBy({ top: el.getBoundingClientRect().top - 90, behavior: 'instant' })); await scan('system-findings', true);
|
||||
await page.getByRole('tab', { name: t('adminSystemSettingsTab'), exact: true }).click(); await scan('system-settings');
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!accountOnly) {
|
||||
@@ -91,12 +120,12 @@ try {
|
||||
await page.getByRole('button', { name: t('processingViewDetails').replace('{id}', operation.id), exact: true }).click(); await expect(page.getByRole('heading', { name: t('processingDetails'), exact: true })).toBeFocused();
|
||||
await expect(page.getByRole('link', { name: operation.job.title, exact: true })).toHaveCount(0); await scan('processing-detail');
|
||||
}
|
||||
report.journeys.push({ width, language, mode, draftPreservedAcrossKeyboardSections: processingOnly ? 'NOT RUN' : 'PASS', revocationAndDeletionCancelled: processingOnly ? 'NOT RUN' : 'PASS', recentExportDenial: processingOnly ? 'NOT RUN' : 'PASS', lastAdminActionsDisabled: processingOnly || accountOnly ? 'NOT RUN' : 'PASS', processingKeyboardAndOwnerLink: accountOnly ? 'NOT RUN' : 'PASS', processingFilterLabelsAndDurationGeometry: accountOnly ? 'NOT RUN' : 'PASS' }); await context.close();
|
||||
report.journeys.push({ width, language, mode, draftPreservedAcrossKeyboardSections: processingOnly || adminOnly ? 'NOT RUN' : 'PASS', revocationAndDeletionCancelled: processingOnly || adminOnly ? 'NOT RUN' : 'PASS', recentExportDenial: processingOnly || adminOnly ? 'NOT RUN' : 'PASS', lastAdminActionsDisabled: processingOnly || accountOnly ? 'NOT RUN' : 'PASS', processingKeyboardAndOwnerLink: accountOnly ? 'NOT RUN' : 'PASS', processingFilterLabelsAndDurationGeometry: accountOnly ? 'NOT RUN' : 'PASS', populatedAdminReadStates: adminOnly ? 'PASS' : 'NOT RUN' }); await context.close();
|
||||
}
|
||||
assert.equal(report.screens.length, configurations.length * (accountOnly ? 7 : processingOnly ? 1 : 8)); assert.equal(report.journeys.length, configurations.length); assert.equal(report.exportDenials.length, processingOnly ? 0 : configurations.length);
|
||||
assert.equal(report.screens.length, configurations.length * (accountOnly ? 7 : processingOnly ? 1 : adminOnly ? 9 : 8)); assert.equal(report.journeys.length, configurations.length); assert.equal(report.exportDenials.length, processingOnly || adminOnly ? 0 : configurations.length);
|
||||
assert.deepEqual(report.errors, []); assert.deepEqual(report.unexpectedWrites, []); assert.ok(report.blockedExternal.every(origin => origin === 'https://fonts.googleapis.com'));
|
||||
console.log(`PASS:${report.journeys.length} controlled journeys,${report.screens.length} axe scans/captures; no mutation requests.`);
|
||||
} finally {
|
||||
fs.writeFileSync(path.join(out, processingOnly ? 'PROCESSING.json' : process.env.A3_PILOT ? 'PILOT.json' : 'BROWSER.json'), JSON.stringify({ ...report, limits: ['Every API response is synthetic. Account export is a controlled 403; revocation/deletion confirmations are cancelled, not executed.', 'System monitor capture is the controlled unavailable state, audit is empty; detailed success/action scenarios are unit tests, not live qualification.', 'No real credentials, provider activation, model, native device, integrated security, production or release acceptance.'] }, null, 2));
|
||||
fs.writeFileSync(path.join(out, processingOnly ? 'PROCESSING.json' : process.env.A3_PILOT ? 'PILOT.json' : 'BROWSER.json'), JSON.stringify({ ...report, limits: ['Every API response is synthetic. In admin-only mode all mutations are blocked and account flows are NOT RUN; otherwise account export is a controlled 403 and confirmations are cancelled.', adminOnly ? 'System and audit populated states are synthetic read fixtures; no probe, maintenance, settings save, undo, restore or provider action executed.' : 'System monitor is unavailable and audit empty; detailed success/action scenarios are unit tests, not live qualification.', 'No real credentials, provider activation, model, native device, integrated security, production or release acceptance.'] }, null, 2));
|
||||
await browser.close();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user