const { start, BASE, DIR, path, assert, sanitized, } = require('./portal-visual-qa-lib.cjs'); void (async () => { const q = await start('visual-followup-chromium-20261010'); q.proof.accessibility = []; q.proof.maps = []; q.proof.country_statistics = []; q.proof.qa18_authentication_requests = 0; q.proof.opportunity = q.prior.opportunity || { title: 'QA integração territorial — ' + q.stamp, }; let page, ctx; async function audit(route, width) { await page.getByRole('heading').first().waitFor(); await page.addScriptTag({ path: path.join(DIR, 'qa-a11y-toolkit/package/axe.min.js'), }); const scan = await page.evaluate(async () => { const r = await axe.run(document, { runOnly: { type: 'tag', values: [ 'wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'best-practice', ], }, }); return { passes: r.passes.length, incomplete: r.incomplete.map((v) => v.id), violations: r.violations.map((v) => ({ id: v.id, impact: v.impact, targets: v.nodes.map((n) => n.target), })), layout: { viewport: innerWidth, scrollWidth: document.documentElement.scrollWidth, language: document.documentElement.lang, mainLandmarks: document.querySelectorAll('main,[role=main]').length, }, }; }); q.proof.accessibility.push({ route, width, ...scan }); await q.shot(page, route.replace(/\W/g, '_') + '-' + width); q.save(); } async function choice(label, text) { const r = page.getByRole('combobox', { name: new RegExp( '^' + label.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '(?: \\(obrigatório\\))?$', ), }); await r.fill(text); await page .getByRole('option') .filter({ hasText: text }) .first() .click(); } async function map(route, endpoint) { const select = page.getByRole('combobox', { name: 'Província', exact: true, }); await select.locator('option').nth(1).waitFor({ state: 'attached' }); const options = await select .locator('option') .evaluateAll((ns) => ns .filter((n) => n.value) .map((n) => ({ id: n.value, name: n.textContent })), ); let selected = options[0], context; for (const o of options) { const r = await q.json(page, endpoint + '?territory_id=' + o.id); assert.equal(r.status, 200); if (!context || r.data.selected_geometry_available) { selected = o; context = r.data; } if (r.data.selected_geometry_available) break; } await select.selectOption(selected.id); const box = page .getByRole('region', { name: 'Cobertura cartográfica da seleção' }) .first(); await box .getByText( /Limite administrativo disponível|Sem limite administrativo acessível/, ) .waitFor(); if (context.areas.length) await box .locator('.leaflet-overlay-pane svg path') .first() .waitFor({ state: 'attached' }); const rendered = await box .locator('.leaflet-overlay-pane svg path') .count(); assert(!context.areas.length || rendered > 0); q.proof.maps.push({ route, selected, territory_registered: true, geometry_available_to_profile: context.selected_geometry_available, context_geometry_count: context.context_geometry_count, rendered_polygon_paths: rendered, external_tiles: 'blocked in isolated browser; not tested', persisted: false, }); q.save(); return selected; } try { q.phase( 'Public keyboard/reflow/accessibility after corrections, no QA18 login', ); ({ page, ctx } = await q.context('anonymous', 390)); q.proof.keyboard = []; for (const route of [ '/agronegocio', '/turismo', '/manifestar-interesse', ]) { await q.open(page, route); await page.getByRole('heading').first().waitFor(); await page.keyboard.press('Tab'); assert.equal( await page.evaluate(() => document.activeElement.getAttribute('href'), ), '#portal-content', ); await page.keyboard.press('Enter'); assert.equal( await page.evaluate(() => document.activeElement.id), 'portal-content', ); q.proof.keyboard.push({ route, skip_link_first: true, target_reached: true, }); await audit(route, 390); } await page.setViewportSize({ width: 320, height: 844 }); for (const route of ['/agronegocio', '/oportunidades?view=map']) { await q.open(page, route); await audit(route, 320); } for (const route of [ '/mercado-financeiro', '/explorar/mercado-financeiro', ]) assert.equal((await q.open(page, route)).status(), 404); await ctx.close(); q.phase( 'QA21 mobile dashboard/dossier/tool keyboard and opportunity resource map', ); ({ page, ctx } = await q.context('promoter', 390)); await q.login(page, 'promoter'); await audit('/dashboard', 390); await q.open(page, '/investimentos'); await page.locator('a[href^="/investimentos/"]').first().click(); await page.waitForURL(/\/investimentos\/[0-9a-f-]{36}$/); q.proof.mobile_case_id = new URL(page.url()).pathname.split('/').pop(); await audit('/investimentos/' + q.proof.mobile_case_id, 390); await page .getByRole('link', { name: 'Plano e simulador', exact: true }) .click(); await page .locator( 'section[aria-labelledby="simulator-step-title"][aria-busy="false"]', ) .waitFor(); await choice( 'O que pretende calcular ou planear? Escolha o percurso', 'Avaliar o meu investimento', ); await choice('Nível de detalhe', 'Simples'); await choice('O que pretende fazer?', 'Apenas simular'); await choice('Idioma preferido', 'Português (Moçambique)'); const nav = page.getByRole('navigation', { name: 'Etapas do formulário', }); await nav.getByRole('button', { name: /Projecto ou negócio/ }).click(); await page .getByLabel('Título do projecto', { exact: true }) .fill('QA acessibilidade do plano contextual'); await page .getByLabel('Resumo executivo', { exact: true }) .fill( 'Ensaio QA de navegação por teclado; nenhuma decisão externa.', ); await nav.getByRole('button', { name: /Estrutura financeira/ }).click(); const financial = page.getByRole('region', { name: 'Ferramentas de planeamento financeiro', }); await choice('Moeda', 'MZN'); await financial.getByLabel('Capital', { exact: true }).fill('100000'); await financial .getByLabel('Taxa anual (%)', { exact: true }) .fill('12'); await financial.getByLabel('Prazo (meses)', { exact: true }).fill('12'); await audit('/simulador-fiscal?context=QA21', 390); for (const label of [ 'Plano de reembolso indicativo', 'Tabela do fluxo de caixa', ]) { const box = page.getByRole('region', { name: label, exact: true }); await box.focus(); assert.equal(await box.getAttribute('tabindex'), '0'); await page.keyboard.press('ArrowDown'); } q.proof.financial_tables_keyboard_focus = true; await q.open(page, '/dashboard'); await page .getByRole('link', { name: 'Nova oportunidade', exact: true }) .first() .click(); await page.waitForURL(BASE + '/minhas-oportunidades/create'); const e = q.proof.opportunity; if (!e.id) { await page .getByLabel('Título oficial', { exact: true }) .fill(e.title); await page .getByLabel('Motivo da alteração', { exact: true }) .fill( 'Ensaio QA específico dos seletores e mapas por consumidor.', ); await page .getByRole('button', { name: 'Guardar rascunho', exact: true }) .click(); await page.waitForURL(/\/minhas-oportunidades\/\d+\/edit$/); e.id = Number(new URL(page.url()).pathname.split('/')[2]); q.save(); } else await q.open(page, '/minhas-oportunidades/' + e.id + '/edit'); await page .getByRole('link', { name: 'Imagens, documentos e localizações', exact: true, }) .click(); await page.waitForURL( BASE + '/minhas-oportunidades/' + e.id + '/resources', ); await map( new URL(page.url()).pathname, '/minhas-oportunidades/territory-geometries', ); await audit(new URL(page.url()).pathname, 390); assert( !(await page .getByText('ClamD real pendente;', { exact: false }) .count()), ); await q.logout(page); await ctx.close(); q.phase( 'Management territorial consumers and visible country statistics', ); ({ page, ctx } = await q.context('manager', 1440)); await q.login(page, 'manager'); for (const route of [ '/management/projects/create', '/management/clusters/create', '/management/corridors/create', ]) { assert.equal((await q.open(page, route)).status(), 200); await map(route, '/management/territory-geometries'); await audit(route, 1440); } for (const origin of ['ZA', 'unknown']) { await q.open(page, '/management/interests'); await page .getByLabel('Filtrar origem declarada', { exact: true }) .selectOption(origin); await page .getByRole('button', { name: 'Pesquisar', exact: true }) .click(); await page.waitForURL( (u) => u.searchParams.get('origin') === origin, ); await page .getByText( 'Estatísticas de solicitações por origem declarada', { exact: true }, ) .click(); q.proof.country_statistics.push({ route: '/management/interests?origin=' + origin, origin, rendered: await page.locator('details').last().innerText(), }); await audit('/management/interests?origin=' + origin, 1440); } await q.open(page, '/management/crm/leads'); await page .getByText('Solicitações por origem declarada', { exact: true }) .click(); await page .getByLabel('Origem das estatísticas', { exact: true }) .selectOption('unknown'); await page .getByRole('button', { name: 'Filtrar estatísticas', exact: true }) .click(); await page.waitForURL( (u) => u.searchParams.get('origin') === 'unknown', ); await audit('/management/crm/leads?origin=unknown', 1440); await q.logout(page); await ctx.close(); q.proof.checks.no_page_overflow = q.proof.accessibility.every( (r) => r.layout.scrollWidth <= r.width + 1, ); q.proof.checks.no_automatic_accessibility_violations = q.proof.accessibility.every((r) => !r.violations.length); q.proof.checks.portuguese_language = q.proof.accessibility.every( (r) => r.layout.language === 'pt-MZ', ); q.proof.manual_screen_reader_acceptance = false; q.proof.result = 'completed_with_findings'; } catch (e) { q.proof.result = 'failed'; q.proof.error = sanitized(e); if ( page && !['/login', '/two-factor-challenge'].includes( new URL(page.url()).pathname, ) ) { q.proof.failure_state = { path: new URL(page.url()).pathname, labels: await page.locator('label').allTextContents(), alerts: await page.getByRole('alert').allTextContents(), }; await q.shot(page, 'failure').catch(() => {}); } } finally { q.proof.finished_at = new Date().toISOString(); q.save(); await q.browser.close(); console.log( JSON.stringify({ result: q.proof.result, phase: q.proof.phase, checks: q.proof.checks, receipt: q.receipt, }), ); if (q.proof.result === 'failed') process.exitCode = 1; } })();