test(discovery): qualify search-first layout and keyboard submission

This commit is contained in:
cesnimda
2026-11-09 23:03:59 +01:00
parent 0540e84ff6
commit 99a2b6f4f6
126 changed files with 2877 additions and 10 deletions
+33 -7
View File
@@ -9,11 +9,13 @@ const compiled = ts.transpileModule(fs.readFileSync(new URL('../src/i18n/transla
const { translations } = await import(`data:text/javascript;base64,${Buffer.from(compiled).toString('base64')}`);
const visualMatrix = process.env.A3_VISUAL_MATRIX === '1';
const trackingOnly = process.env.A3_DISCOVERY_TRACK === '1';
const widths = process.env.A3_PILOT ? (trackingOnly ? [320, 1440] : [320]) : visualMatrix || trackingOnly ? [320, 360, 390, 430, 768, 1024, 1280, 1440, 1920] : [320, 390, 768, 1440];
const searchFirst = process.env.A3_DISCOVERY_SEARCH_FIRST === '1';
assert.ok(!(trackingOnly && searchFirst), 'Search and tracker qualification are separate scopes.');
const widths = process.env.A3_PILOT ? (trackingOnly || searchFirst ? [320, 1440] : [320]) : visualMatrix || trackingOnly || searchFirst ? [320, 360, 390, 430, 768, 1024, 1280, 1440, 1920] : [320, 390, 768, 1440];
const languages = process.env.A3_PILOT ? ['en'] : ['en', 'nb'];
const modes = process.env.A3_PILOT ? ['light'] : ['light', 'dark'];
const expectedJourneys = widths.length * languages.length * modes.length;
const out = path.resolve(import.meta.dirname, trackingOnly ? '../../docs/ui-modernisation/visual-correction/evidence/discovery-track' : visualMatrix ? '../../docs/ui-modernisation/visual-correction/evidence/discovery' : '../../docs/ui-modernisation/evidence/discovery/browser');
const out = path.resolve(import.meta.dirname, searchFirst ? '../../docs/ui-modernisation/visual-correction/evidence/discovery-search-first' : trackingOnly ? '../../docs/ui-modernisation/visual-correction/evidence/discovery-track' : visualMatrix ? '../../docs/ui-modernisation/visual-correction/evidence/discovery' : '../../docs/ui-modernisation/evidence/discovery/browser');
fs.mkdirSync(out, { recursive: true });
const report = { screens: [], journeys: [], errors: [], unexpectedWrites: [], mockedWrites: [], previewRequests: [], unavailableReads: [], blockedExternal: [] };
const browser = await chromium.launch();
@@ -31,7 +33,7 @@ try {
const description = 'Synthetic nurse role: supervised care and recorded handover.';
await page.route('**/*', route => {
const req = route.request(), url = new URL(req.url());
if (trackingOnly && url.origin !== 'http://localhost:4338' && !(url.origin === 'http://localhost:5202' && url.pathname.startsWith('/api/'))) { report.blockedExternal.push(url.origin); return route.abort(); }
if ((trackingOnly || searchFirst) && url.origin !== 'http://localhost:4338' && !(url.origin === 'http://localhost:5202' && url.pathname.startsWith('/api/'))) { report.blockedExternal.push(url.origin); return route.abort(); }
if (!['localhost', '127.0.0.1'].includes(url.hostname)) { report.blockedExternal.push(url.origin); return route.abort(); }
if (!url.pathname.startsWith('/api/')) return route.continue();
const answer = data => route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(data) });
@@ -44,7 +46,7 @@ try {
const body = req.postDataJSON(); assert.equal(tracked, null); for (const [key, value] of Object.entries({ jobTitle: jobs[0].title, companyId: company.id, status: 'Saved', jobUrl: jobs[0].url, source: 'nav', countryCode: 'NO', description, descriptionLanguage: 'en', deadline: '2026-11-12', followUpAt: null })) assert.equal(body[key], value, key);
tracked = { ...body, id: 42, version: 1, company, savedAt: '2026-11-04T10:00:00Z', isDeleted: false, needsFollowUp: false, workflowSignal: null }; note('createTrackedJob'); return answer({ id: 42 });
}
if (trackingOnly && req.method() !== 'GET') { report.unexpectedWrites.push(url.pathname); return route.abort(); }
if ((trackingOnly || searchFirst) && req.method() !== 'GET') { report.unexpectedWrites.push(url.pathname); return route.abort(); }
if (req.method() === 'POST' && url.pathname === '/api/job-discovery/saved-searches') {
const data = req.postDataJSON(); assert.deepEqual(data, { name: 'Care roles', query: 'nurse', location: 'Oslo' });
saved = { ...data, id: 7, isActive: true, resultCount: 0, dismissedCount: 0 }; note('create'); return answer(saved);
@@ -61,7 +63,11 @@ try {
if (req.method() === 'DELETE' && url.pathname === '/api/job-discovery/saved-searches/7') { saved = null; note('confirmedDelete'); return answer({}); }
if (req.method() !== 'GET') { report.unexpectedWrites.push(url.pathname); return route.abort(); }
if (url.pathname === '/api/job-discovery/saved-searches') return answer(saved ? [saved] : []);
if (url.pathname === '/api/job-discovery/search') { searches++; return answer(jobs.filter(job => !url.searchParams.get('q') || job.title.toLowerCase().includes(url.searchParams.get('q').toLowerCase()))); }
if (url.pathname === '/api/job-discovery/search') {
searches++;
if (searchFirst) assert.deepEqual(Object.fromEntries(url.searchParams), { q: 'nurse', location: 'Oslo' });
return answer(jobs.filter(job => !url.searchParams.get('q') || job.title.toLowerCase().includes(url.searchParams.get('q').toLowerCase())));
}
const fixtures = {
'/api/auth/config': { requireAuth: true },
'/api/auth/me': { id: 'a3-discovery-owner', sessionContext: 'a3-discovery-session', firstName: 'Alex', displayName: 'Alex Sample', roles: [], uiLanguage: language === 'nb' ? 'nb-NO' : 'en' },
@@ -87,11 +93,31 @@ try {
const role = page.getByRole('textbox', { name: language === 'en' ? 'Role or company' : 'Rolle eller bedrift', exact: true });
await filters.getByRole('button', { name: language === 'en' ? 'Jump to results' : 'Gå til resultater', exact: true }).click();
assert.equal(await results.evaluate(node => node === document.activeElement), true);
await results.getByRole('button', { name: language === 'en' ? 'Return to search filters' : 'Tilbake til søkefiltre', exact: true }).click();
await results.getByRole('button', { name: t('jobDiscoveryReturnFilters'), exact: true }).click();
assert.equal(await role.evaluate(node => node === document.activeElement), true); assert.equal(searches, 0);
const left = await filters.boundingBox(), right = await results.boundingBox();
assert.ok(width > 1000 ? right.x > left.x + left.width - 2 && Math.abs(right.y - left.y) < 2 : right.y >= left.y + left.height - 2);
await scan('initial');
if (searchFirst) {
const form = page.getByRole('form', { name: t('jobDiscoverySearchNav'), exact: true });
const [query, filtersAtTop] = await Promise.all([form.boundingBox(), filters.boundingBox()]);
assert.ok(query.y + query.height <= filtersAtTop.y, 'Search form is above both columns');
const source = filters.getByText(t('jobDiscoverySourceCoverage'), { exact: true });
const help = filters.getByText(t('jobDiscoverySourceHelp'), { exact: true });
await expect(help).toBeHidden();
await source.press('Enter'); await expect(help).toBeVisible(); await scan('source-context');
await source.press('Enter'); await expect(help).toBeHidden();
const municipality = filters.getByRole('textbox', { name: t('jobDiscoveryMunicipality'), exact: true });
await role.fill('nurse'); await municipality.fill('Oslo'); await municipality.press('Enter');
const vacancy = results.getByRole('article', { name: jobs[0].title, exact: true });
await expect(vacancy.getByText(jobs[0].sourceName, { exact: true })).toBeVisible();
await expect(vacancy.getByRole('button', { name: t('jobDiscoverySaveTracker'), exact: true })).toBeVisible();
await expect(results.getByRole('article')).toHaveCount(1);
assert.equal(searches, 1);
await scan('results');
report.journeys.push({ width, language, mode, searchFirst: 'PASS', municipalityKeyboardSubmit: 'PASS', providerWrites: 'NOT RUN' });
await context.close(); continue;
}
const search = page.getByRole('button', { name: language === 'en' ? 'Search NAV' : 'Søk i NAV', exact: true });
await search.click(); await results.getByRole('article', { name: 'Electrical engineer', exact: true }).waitFor();
assert.equal(await results.getByRole('article').count(), 3); await scan('results');
@@ -125,7 +151,7 @@ try {
report.journeys.push({ width, language, mode, searchSavePauseRunDismissConfirmedDelete: 'PASS' });
await context.close();
}
assert.equal(report.screens.length, expectedJourneys * (trackingOnly ? 4 : 3)); assert.equal(report.journeys.length, expectedJourneys); assert.equal(report.mockedWrites.length, expectedJourneys * (trackingOnly ? 1 : 5)); assert.equal(report.previewRequests.length, trackingOnly ? expectedJourneys : 0);
assert.equal(report.screens.length, expectedJourneys * (trackingOnly ? 4 : 3)); assert.equal(report.journeys.length, expectedJourneys); assert.equal(report.mockedWrites.length, expectedJourneys * (searchFirst ? 0 : trackingOnly ? 1 : 5)); assert.equal(report.previewRequests.length, trackingOnly ? expectedJourneys : 0);
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 discovery journeys,${report.screens.length} axe scans/captures,${report.mockedWrites.length} intercepted fixture mutations.`);