const { start, BASE: _BASE, DIR, path, assert, sanitized, } = require('./portal-visual-qa-lib.cjs'); void (async () => { const q = await start('consumers-mobile-accessibility-chromium-20261010'); const resume = process.env.INVESTMOZ_VISUAL_RESUME === 'remaining'; q.proof.coverage = resume ? q.prior.coverage || [] : []; q.proof.accessibility = resume ? q.prior.accessibility || [] : []; q.proof.prior_evidence_started_at = resume ? q.prior.started_at : null; q.proof.land_id = q.prior.land_id || q.prior.events ?.findLast( (e) => e.method === 'POST' && e.path === '/investidor/terra-duat' && e.redirect, ) ?.redirect.split('/') .pop(); q.proof.business_id = q.prior.business_id; let page, ctx; const axePath = path.join(DIR, 'qa-a11y-toolkit/package/axe.min.js'); async function audit(route, width) { await page.locator('h1').first().waitFor(); await page.addScriptTag({ path: axePath }); const scan = await page.evaluate(async () => { const r = await window.axe.run(document, { runOnly: { type: 'tag', values: [ 'wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'best-practice', ], }, }); return { version: r.testEngine.version, passes: r.passes.length, incomplete: r.incomplete.map((v) => ({ id: v.id, targets: v.nodes.map((n) => n.target), })), violations: r.violations.map((v) => ({ id: v.id, impact: v.impact, help: v.help, nodes: v.nodes.map((n) => ({ target: n.target, failureSummary: n.failureSummary, })), })), }; }); const layout = await page.evaluate(() => ({ viewport: innerWidth, scrollWidth: document.documentElement.scrollWidth, language: document.documentElement.lang, mainLandmarks: document.querySelectorAll('main,[role="main"]').length, })); const row = { route, width, ...scan, layout }; q.proof.accessibility.push(row); await q.shot(page, route.replace(/[^a-zA-Z0-9]/g, '_') + '-' + width); q.save(); return row; } async function countryCoverage(route) { const response = await q.open(page, route); await page.locator('h1').first().waitFor(); const selects = await page.locator('select').evaluateAll((nodes) => nodes .filter( (n) => Array.from(n.options).some((o) => o.value === 'MZ') && Array.from(n.options).some((o) => o.value === 'PT'), ) .map((n) => ({ label: n.getAttribute('aria-label') || n.labels?.[0]?.textContent?.trim() || '', id: n.id, options: n.options.length, current: n.value, })), ); q.proof.coverage.push({ route, status: response.status(), country_selectors: selects, persistence_verified: false, }); q.save(); return selects; } try { if (!resume) { q.phase( 'Public mobile/reflow and accessibility, Financial Market private', ); ({ page, ctx } = await q.context('anonymous', 390)); for (const route of [ '/explorar/agronegocio', '/agronegocio', '/explorar/energia-mineracao', '/turismo', '/infraestruturas', '/oportunidades?view=map', ]) { await q.open(page, route); 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); q.proof.checks.financial_market_not_public = true; await ctx.close(); q.phase( 'QA18 country/profile persistence, land and submission consumers', ); ({ page, ctx } = await q.context('investor18', 390)); await q.login(page, 'investor18'); await countryCoverage('/investidor/profile'); const residence = page.locator('#residence_country_codeaccount'); const before = await residence.inputValue(); await residence.selectOption('PT'); const save = page.waitForResponse( (r) => r.request().method() === 'PUT' && new URL(r.url()).pathname === '/investidor/profile', ); await page .getByRole('button', { name: 'Guardar perfil de investimento', exact: true, }) .click(); assert([200, 302, 303].includes((await save).status())); await page.reload({ waitUntil: 'domcontentloaded' }); assert.equal(await residence.inputValue(), 'PT'); q.proof.coverage.at(-1).persistence_verified = true; q.proof.coverage.at(-1).before = before; await audit('/investidor/profile', 390); await countryCoverage( q.proof.land_id ? '/investidor/terra-duat/' + q.proof.land_id + '/editar' : '/investidor/terra-duat/novo', ); await page .getByRole('combobox', { name: 'País, se aplicável', exact: true, }) .selectOption('PT'); await page .getByRole('combobox', { name: 'País de origem declarado da solicitação (opcional)', exact: true, }) .selectOption('FR'); const landSave = page.waitForResponse( (r) => ['POST', 'PUT'].includes(r.request().method()) && new URL(r.url()).pathname.startsWith( '/investidor/terra-duat', ), ); await page .getByRole('button', { name: 'Guardar rascunho', exact: true }) .click(); assert([200, 302, 303].includes((await landSave).status())); if (!q.proof.land_id) { await page.waitForURL( /\/investidor\/terra-duat\/[0-9a-f-]{36}(?:\/editar)?$/, ); q.proof.land_id = new URL(page.url()).pathname.split('/')[3]; q.save(); } await q.open( page, '/investidor/terra-duat/' + q.proof.land_id + '/editar', ); assert.equal( await page .getByRole('combobox', { name: 'País, se aplicável', exact: true, }) .inputValue(), 'PT', ); assert.equal( await page .getByRole('combobox', { name: 'País de origem declarado da solicitação (opcional)', exact: true, }) .inputValue(), 'FR', ); q.proof.coverage.at(-1).persistence_verified = true; await audit( '/investidor/terra-duat/' + q.proof.land_id + '/editar', 390, ); await countryCoverage('/investidor/negocios'); if (!q.proof.business_id) { await page .getByLabel('Título privado', { exact: true }) .fill('QA países negócio — ' + q.stamp); await page .getByRole('button', { name: 'Criar rascunho privado', exact: true, }) .click(); await page.waitForURL(/\/investidor\/negocios\/[0-9a-f-]{36}$/); q.proof.business_id = new URL(page.url()).pathname .split('/') .pop(); q.save(); } else await q.open( page, '/investidor/negocios/' + q.proof.business_id, ); await page .getByRole('combobox', { name: 'País de origem ou constituição', exact: true, }) .selectOption('PT'); await page .getByRole('combobox', { name: 'País de origem declarado desta solicitação (opcional)', exact: true, }) .selectOption('FR'); await page .getByRole('button', { name: 'Guardar rascunho', exact: true }) .click(); await page.reload({ waitUntil: 'domcontentloaded' }); assert.equal( await page .getByRole('combobox', { name: 'País de origem ou constituição', exact: true, }) .inputValue(), 'PT', ); assert.equal( await page .getByRole('combobox', { name: 'País de origem declarado desta solicitação (opcional)', exact: true, }) .inputValue(), 'FR', ); q.proof.coverage.push({ route: '/investidor/negocios/' + q.proof.business_id, country: 'PT', origin: 'FR', persistence_verified: true, }); await audit('/investidor/negocios/' + q.proof.business_id, 390); for (const route of [ '/formularios/investidor', '/formularios/empresa', '/manifestar-interesse', '/apoio', ]) { await countryCoverage(route); await audit(route, 390); } } else { q.phase( 'Resume remaining mobile tool/maps/statistics; preserve prior consumer evidence', ); ({ page, ctx } = await q.context('investor18', 390)); await q.login(page, 'investor18'); q.proof.checks.financial_market_not_public = true; } const ab = require( path.join(DIR, 'investor-ab-chromium-20261010.json'), ); assert.equal(ab.result, 'passed'); for (const [name, e] of Object.entries(ab.scenarios)) { await q.open(page, '/investimentos/' + e.id); await page .getByRole('heading', { name: e.title, exact: true }) .waitFor(); await audit('/investimentos/' + e.id, 390); await page .getByRole('link', { name: 'Plano e simulador', exact: true }) .click(); await page.waitForURL( (u) => u.searchParams.get('context') === e.id, ); await page .getByText('Dossier privado recuperado da sua conta.', { exact: true, }) .waitFor(); await page .getByRole('navigation', { name: 'Etapas do formulário' }) .getByRole('button', { name: /Estrutura financeira/ }) .click(); await page .getByRole('region', { name: 'Ferramentas de planeamento financeiro', }) .waitFor(); await audit('/simulador-fiscal?context=' + name, 390); q.proof.coverage.push({ route: '/investimentos/' + e.id, consumer: 'mobile dossier and contextual tool', scenario: name, context_preserved: new URL(page.url()).searchParams.get('context') === e.id, persistence_verified: true, }); } await q.logout(page); await ctx.close(); q.phase('Promoter dashboard, canonical territorial consumer'); ({ page, ctx } = await q.context('promoter', 390)); await q.login(page, 'promoter'); await q.open(page, '/dashboard'); await audit('/dashboard', 390); await q.open(page, '/minhas-oportunidades/30/edit'); await audit('/minhas-oportunidades/30/edit', 390); const cartography = page.getByRole('region', { name: 'Cobertura cartográfica da seleção', }); if (await cartography.count()) { await cartography .first() .getByText( 'Limite administrativo disponível para este território.', { exact: true }, ) .waitFor(); await cartography .locator('.leaflet-overlay-pane svg path') .first() .waitFor({ state: 'attached' }); const polygons = await cartography .locator('.leaflet-overlay-pane svg path') .count(); q.proof.coverage.push({ route: '/minhas-oportunidades/30/edit', consumer: 'selected canonical location', territory_registered: true, geometry_available: true, rendered_polygon_paths: polygons, basemap: 'external tiles deliberately blocked; fallback separate', persistence_verified: false, }); assert(polygons > 0); await q.shot(page, 'canonical-map-390'); } await q.logout(page); await ctx.close(); q.phase( 'Manager country filters, per-origin statistics and map consumers', ); ({ page, ctx } = await q.context('manager', 1440)); await q.login(page, 'manager'); await q.open(page, '/management/interests'); await page .getByLabel('Filtrar origem declarada', { exact: true }) .selectOption('ZA'); await page .getByRole('button', { name: 'Pesquisar', exact: true }) .click(); await page.waitForURL( (u) => u.pathname === '/management/interests' && u.searchParams.get('origin') === 'ZA', ); await page .getByText('Estatísticas de solicitações por origem declarada', { exact: true, }) .click(); await page .getByRole('heading', { name: 'Pedidos do Balcão', exact: true }) .waitFor(); q.proof.coverage.push({ route: '/management/interests?origin=ZA', consumer: 'country filter/statistics', ui_filter: 'ZA', counts: await page.locator('details').last().innerText(), persistence_verified: false, drafts_exclusion: 'UI describes exclusion; new A/B drafts not counted as submission', }); await audit('/management/interests?origin=ZA', 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('ZA'); await page .getByRole('button', { name: 'Filtrar estatísticas', exact: true }) .click(); await page.waitForURL((u) => u.searchParams.get('origin') === 'ZA'); await audit('/management/crm/leads?origin=ZA', 1440); for (const route of [ '/oportunidades?view=map', '/management/projects/create', '/management/clusters/create', '/management/corridors/create', ]) { const r = await q.open(page, route); q.proof.coverage.push({ route, status: r.status(), territorial_selectors: await page.getByRole('combobox').count(), rendered_polygons: await page .locator('.leaflet-overlay-pane svg path') .count(), persistence_verified: false, }); await audit(route, 1440); } await q.logout(page); await ctx.close(); q.proof.checks.mobile_no_page_overflow = q.proof.accessibility.every( (r) => r.layout.scrollWidth <= r.layout.viewport + 1, ); q.proof.checks.automatic_accessibility_zero_violations = q.proof.accessibility.every((r) => r.violations.length === 0); q.proof.manual_assistive_technology_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, coverage: q.proof.coverage.length, accessibility_pages: q.proof.accessibility.length, checks: q.proof.checks, receipt: q.receipt, }), ); if (q.proof.result === 'failed') process.exitCode = 1; } })();