Files
Investidor-V2/tools/portal-country-paths-visual-qa.cjs
T
nala 287e991486
tests / ci (push) Waiting to run
oportunidades 7
2026-10-13 11:40:29 +02:00

543 lines
22 KiB
JavaScript

// Countries/origin per remaining consumer. Synthetic public intakes only; no notifications or external workers are invoked.
const {
start,
BASE,
DIR,
fs,
path,
assert,
sanitized,
} = require('./portal-visual-qa-lib.cjs');
void (async () => {
const q = await start(
process.argv.includes('--search-only')
? 'country-search-chromium-20261010'
: 'country-paths-chromium-20261010',
);
q.proof.forms = q.prior.forms || {};
q.proof.recovery = [];
let page, ctx, management;
async function props(response) {
const html = await response.text(),
encoded = html.match(/data-page="([^"]+)"/);
if (encoded && encoded[1].startsWith('{'))
return JSON.parse(
encoded[1]
.replace(/"/g, '"')
.replace(/&/g, '&')
.replace(/'/g, "'"),
).props;
const script = html.match(
/<script[^>]*type="application\/json"[^>]*>([\s\S]*?)<\/script>/,
);
assert(script);
return JSON.parse(script[1]).props;
}
async function countStats(p) {
return (
p.originStatistics.interests.find((r) => r.code === 'ZA')?.count ||
0
);
}
async function audit(route) {
await page.addScriptTag({
path: path.join(DIR, 'qa-a11y-toolkit/package/axe.min.js'),
});
const result = await page.evaluate(async () => {
const r = await axe.run(document, {
runOnly: {
type: 'tag',
values: [
'wcag2a',
'wcag2aa',
'wcag21a',
'wcag21aa',
'best-practice',
],
},
});
return {
violations: r.violations.map((v) => ({
id: v.id,
targets: v.nodes.map((n) => n.target),
})),
viewport: innerWidth,
scrollWidth: document.documentElement.scrollWidth,
};
});
(q.proof.accessibility ||= []).push({ route, ...result });
q.save();
assert.equal(result.violations.length, 0);
assert(result.scrollWidth <= result.viewport + 1);
}
try {
const routes = [
'/formularios/investidor',
'/formularios/empresa',
'/apoio',
'/manifestar-interesse',
];
if (process.argv.includes('--search-only')) {
q.phase(
'Remaining country-search criteria only; no submissions or authentication',
);
({ page, ctx } = await q.context('anonymous-country-search', 390));
q.proof.search = [];
for (const route of routes) {
await q.open(page, route);
await page.getByRole('heading').first().waitFor();
const country = page.getByRole('combobox', {
name: 'País do requerente',
exact: true,
});
const origin = page.getByRole('combobox', {
name: 'País de origem declarado desta solicitação',
exact: true,
});
await page
.getByRole('textbox', {
name: 'Pesquisar país do requerente',
exact: true,
})
.fill('Portugal');
assert.equal(await country.locator('option').count(), 2);
await country.selectOption('PT');
await page
.getByRole('textbox', {
name: 'Pesquisar país do requerente',
exact: true,
})
.fill('França');
assert.equal(await country.inputValue(), 'PT');
assert.equal(
await country.locator('option[value="FR"]').count(),
1,
);
await page
.getByRole('textbox', {
name: 'Pesquisar país de origem declarado desta solicitação',
exact: true,
})
.fill('África do Sul');
assert.equal(await origin.locator('option').count(), 2);
await origin.selectOption('ZA');
assert.equal(await country.inputValue(), 'PT');
assert.equal(await origin.inputValue(), 'ZA');
q.proof.search.push({
route,
country_query: 'Portugal',
country_result: 'PT',
changed_query: 'França',
previous_selection_preserved: 'PT',
origin_query: 'África do Sul',
origin_result: 'ZA',
});
q.save();
}
q.proof.business_data_writes = 0;
q.proof.result = 'passed_read_only_four_country_searches';
} else if (process.argv.includes('--probe-labels')) {
q.phase(
'Read-only accessible names for country and declared origin before correction',
);
({ page, ctx } = await q.context('anonymous-country-labels', 390));
q.proof.labels = [];
for (const route of routes) {
await q.open(page, route);
await page.getByRole('heading').first().waitFor();
const controls = await page
.locator('select')
.evaluateAll((nodes) =>
nodes
.filter((n) =>
Array.from(n.options).some(
(o) => o.value === 'PT',
),
)
.map((n) => ({
accessible_label: n.getAttribute('aria-label'),
options: n.options.length,
visual_label: n
.closest('label')
?.textContent?.trim()
.slice(0, 150),
})),
);
q.proof.labels.push({ route, controls });
q.save();
}
q.proof.result = 'diagnosed_C89_indistinguishable_country_names';
} else {
q.phase(
'Management baseline for positive declared-origin statistics',
);
management = await q.context('manager-country-stats', 1440);
await q.login(management.page, 'manager');
const baseline = await props(
await q.open(
management.page,
'/management/interests?origin=ZA',
),
);
q.proof.baseline_known_origin =
q.prior.baseline_known_origin ?? (await countStats(baseline));
q.save();
({ page, ctx } = await q.context(
'anonymous-country-submissions',
390,
));
for (const [i, route] of routes.entries()) {
const entry = (q.proof.forms[route] ||= {
name: 'QA países origem formulário ' + i + ' 2026-10-10',
email: 'qa-ab-countries-' + i + '-20261010@example.invalid',
country: 'PT',
origin: 'ZA',
});
// The public API requires a DNS mail domain. Invalid/reserved no-mail domains are correctly rejected; QA outgoing notifications are disabled.
if (route === '/manifestar-interesse' && !entry.reference) {
entry.rejected_synthetic_email = entry.email.endsWith(
'.invalid',
)
? entry.email
: entry.rejected_synthetic_email;
entry.email =
'qa-investmoz-countries-' +
i +
'-20261010-9bd17fc0482e@gmail.com';
}
if (entry.reference) continue;
// If a prior attempt saved before capturing its confirmation, recover by this exact synthetic identity before considering another submission.
const existing = await props(
await q.open(
management.page,
'/management/interests?q=' +
encodeURIComponent(entry.email),
),
);
const found = existing.requests.data.find(
(r) => r.email === entry.email,
);
if (found) {
entry.reference = found.reference;
entry.id = found.id;
entry.recovered_existing_attempt = true;
q.save();
continue;
}
q.phase(
'Public form ' +
route +
': country/origin -> submission -> visible protocol',
);
await q.open(page, route);
await page.getByRole('heading').first().waitFor();
const form = page.locator('form').filter({
has: page.getByRole('button', {
name:
route === '/manifestar-interesse'
? 'Enviar manifestação de interesse'
: 'Enviar e obter protocolo',
exact: true,
}),
});
await form
.getByLabel(
route === '/manifestar-interesse'
? /^Nome completo/
: /^Nome do representante/,
)
.fill(entry.name);
await form.getByLabel(/^Email/).fill(entry.email);
const country = form.getByRole('combobox', {
name: 'País do requerente',
exact: true,
});
const origin = form.getByRole('combobox', {
name: 'País de origem declarado desta solicitação',
exact: true,
});
assert.equal(await country.locator('option').count(), 250);
await country.selectOption('PT');
await origin.selectOption('ZA');
if (route === '/manifestar-interesse') {
await form
.getByLabel(/^Tipo de investidor/)
.selectOption('individual');
await form
.getByLabel(/^Sector de interesse/)
.selectOption({ index: 1 });
await form
.getByLabel(/^Mensagem/)
.fill(
'Ensaio sintético QA de países e origem declarada; nenhum pedido de comunicação externa.',
);
} else {
await form
.getByLabel('Sector ou assunto', { exact: true })
.fill(
'Ensaio sintético QA de cobertura por formulário',
);
if (route === '/formularios/empresa')
await form
.getByLabel('Nome da empresa', { exact: true })
.fill('Empresa sintética QA países 20261010');
await form
.getByLabel('Descreva o pedido', { exact: true })
.fill(
'Ensaio sintético QA de países e origem declarada; nenhum pedido de comunicação externa.',
);
}
await form.getByRole('checkbox').check();
await q.shot(page, 'form-' + i + '-country-origin-390');
await audit(route);
const posted = page.waitForResponse(
(r) =>
r.request().method() === 'POST' &&
new URL(r.url()).pathname ===
(route === '/manifestar-interesse'
? '/api/v1/interest-requests'
: '/apoio'),
);
await form
.getByRole('button', {
name:
route === '/manifestar-interesse'
? 'Enviar manifestação de interesse'
: 'Enviar e obter protocolo',
exact: true,
})
.click();
const response = await posted;
if (![200, 201, 302, 303].includes(response.status())) {
q.proof.rejected_form = {
route,
status: response.status(),
validation: await response.json().catch(() => null),
};
q.save();
throw Error(
'Public form rejected; inspect sanitized validation evidence',
);
}
if (route === '/manifestar-interesse')
entry.reference = (await response.json()).data.reference;
else {
await page
.getByText(/Pedido registado\. Protocolo:/)
.waitFor();
entry.reference = (
await page
.getByText(/Pedido registado\. Protocolo:/)
.innerText()
).match(/AP-\d{4}-[A-Z0-9]+/)[0];
}
await page
.getByText(new RegExp(entry.reference))
.first()
.waitFor();
entry.submitted_via_ui = true;
entry.post_status = response.status();
q.save();
await q.shot(page, 'form-' + i + '-protocol');
}
await ctx.close();
q.phase(
'Recovery by authorized management, country/origin filters and positive statistics',
);
({ page, ctx } = management);
for (const [_route, entry] of Object.entries(q.proof.forms)) {
const result = await props(
await q.open(
page,
'/management/interests?q=' +
encodeURIComponent(entry.reference) +
'&country=PT&origin=ZA',
),
);
assert.equal(result.requests.data.length, 1);
const row = result.requests.data[0];
assert.equal(row.email, entry.email);
assert.equal(row.declaredOrigin.code, 'ZA');
entry.id = row.id;
entry.filtered_recovery = true;
const link = page.getByRole('link', {
name: entry.reference,
exact: true,
});
await link.click();
await page.waitForURL(BASE + '/management/interests/' + row.id);
await page
.getByText('África do Sul (ZA)', { exact: true })
.waitFor();
await page.getByText('PT', { exact: true }).waitFor();
entry.management_detail_presented = true;
await q.shot(page, 'detail-' + row.id);
q.save();
}
const positive = await props(
await q.open(
page,
'/management/interests?country=PT&origin=ZA',
),
);
q.proof.known_origin_after = await countStats(positive);
assert.equal(
q.proof.known_origin_after,
q.proof.baseline_known_origin + 4,
);
await page
.getByText(
'Estatísticas de solicitações por origem declarada',
{ exact: true },
)
.click();
await page
.getByText('África do Sul: ' + q.proof.known_origin_after, {
exact: true,
})
.waitFor();
await q.shot(page, 'management-positive-origin-statistics');
await page
.getByRole('combobox', {
name: 'Filtrar país do requerente',
exact: true,
})
.selectOption('ZA');
await page
.getByRole('button', { name: 'Pesquisar', exact: true })
.click();
await page.waitForURL(
(u) => u.searchParams.get('country') === 'ZA',
);
for (const entry of Object.values(q.proof.forms))
assert.equal(
await page
.getByRole('link', {
name: entry.reference,
exact: true,
})
.count(),
0,
);
q.proof.country_is_independent_from_declared_origin = true;
await ctx.close();
q.phase(
'Fresh QA18 session: recover previous profile, business and land drafts without rewriting',
);
({ page, ctx } = await q.context(
'investor18-country-recovery',
390,
));
await q.login(page, 'investor18');
const prior = JSON.parse(
fs.readFileSync(
path.join(
DIR,
'consumers-mobile-accessibility-chromium-20261010.json',
),
'utf8',
),
);
await q.open(page, '/investidor/profile');
assert.equal(
await page
.locator('#residence_country_codeaccount')
.inputValue(),
'PT',
);
q.proof.recovery.push({
route: '/investidor/profile',
country: 'PT',
fresh_session: true,
business_data_writes: 0,
});
for (const [route, countryName, originName] of [
[
'/investidor/negocios/' + prior.business_id,
'País de origem ou constituição',
'País de origem declarado desta solicitação (opcional)',
],
[
'/investidor/terra-duat/' + prior.land_id + '/editar',
'País, se aplicável',
'País de origem declarado da solicitação (opcional)',
],
]) {
await q.open(page, route);
await page
.getByRole('combobox', { name: countryName, exact: true })
.waitFor();
assert.equal(
await page
.getByRole('combobox', {
name: countryName,
exact: true,
})
.inputValue(),
'PT',
);
assert.equal(
await page
.getByRole('combobox', {
name: originName,
exact: true,
})
.inputValue(),
'FR',
);
q.proof.recovery.push({
route,
country: 'PT',
origin: 'FR',
fresh_session: true,
business_data_writes: 0,
});
await q.shot(
page,
route.includes('/negocios/')
? 'business-fresh-session-390'
: 'land-fresh-session-390',
);
q.save();
}
q.proof.checks = {
four_public_forms_submitted_and_recovered: true,
independent_country_origin_filters: true,
positive_known_origin_statistics: true,
previous_business_and_land_recovered_new_session: true,
};
q.proof.external_dispatches = 0;
q.proof.result = 'passed';
}
} 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;
}
})();