Files
Investidor-V2/tools/business-wizard-browser.mjs
nala 1106509116
tests / ci (push) Canceled after 0s
vrsao 2
2026-10-08 14:02:01 +02:00

175 lines
7.0 KiB
JavaScript

// Isolated QA only. Synthetic account is prepared separately, never against the application DB.
import fs from 'node:fs';
const base = 'http://127.0.0.1:18027';
const target = await (
await fetch('http://127.0.0.1:9224/json/new?about:blank', { method: 'PUT' })
).json();
const ws = new WebSocket(target.webSocketDebuggerUrl);
await new Promise((resolve) =>
ws.addEventListener('open', resolve, { once: true }),
);
let seq = 0;
const pending = new Map();
const errors = [];
ws.onmessage = (event) => {
const m = JSON.parse(event.data);
if (m.method === 'Runtime.exceptionThrown')
errors.push(m.params.exceptionDetails.text);
if (m.id) {
const p = pending.get(m.id);
pending.delete(m.id);
if (m.error) p.reject(m.error);
else p.resolve(m.result);
}
};
const send = (method, params = {}) =>
new Promise((resolve, reject) => {
const id = ++seq;
pending.set(id, { resolve, reject });
ws.send(JSON.stringify({ id, method, params }));
});
const evaluate = async (expression) => {
const r = await send('Runtime.evaluate', {
expression,
returnByValue: true,
awaitPromise: true,
});
if (r.exceptionDetails) throw Error('Browser expression failed');
return r.result?.value;
};
const wait = async (expression) => {
for (let n = 0; n < 100; n++) {
if (await evaluate(expression)) return;
await new Promise((r) => setTimeout(r, 100));
}
throw Error('Browser condition timed out: ' + expression);
};
const fill = async (selector, value) => {
await evaluate(
`document.querySelector(${JSON.stringify(selector)}).focus()`,
);
await send('Input.insertText', { text: value });
};
const field = async (label, value) => {
const selector = `([...document.querySelectorAll('label')].find(l=>l.textContent.includes(${JSON.stringify(label)}))?.querySelector('input,textarea,select'))`;
await wait(`!!${selector}`);
await evaluate(
`(()=>{const e=${selector}; e.focus(); if(e.tagName==='SELECT'){ e.value=${JSON.stringify(value)}; e.dispatchEvent(new Event('change',{bubbles:true})); }else {e.select();}})()`,
);
if (!(await evaluate(`${selector}.tagName==='SELECT'`)))
await send('Input.insertText', { text: value });
};
const step = async (number) => {
await wait(
`![...document.querySelectorAll('ol button')][${number - 1}].disabled`,
);
await evaluate(
`[...document.querySelectorAll('ol button')][${number - 1}].click()`,
);
await wait(
`document.querySelector('h2')?.textContent.startsWith('${number}/8')`,
);
};
const observations = [];
try {
await send('Page.enable');
await send('Runtime.enable');
await send('Emulation.setDeviceMetricsOverride', {
width: 390,
height: 900,
deviceScaleFactor: 1,
mobile: true,
});
await send('Page.navigate', { url: base + '/login' });
await wait(
`document.readyState==='complete' && (location.pathname!='/login' || !!document.querySelector('input[type="password"]'))`,
);
if (await evaluate(`location.pathname==='/login'`)) {
await fill('input[type="email"]', 'mne-browser@example.test');
await fill('input[type="password"]', 'SyntheticMneOnly-2026!');
await evaluate(
`document.querySelector('button[type="submit"]').click()`,
);
await wait(`!location.pathname.startsWith('/login')`);
}
await send('Page.navigate', { url: base + '/investidor/negocios' });
await wait(`document.body.innerText.includes('Preparar negócio')`);
await fill('main form input', 'Synthetic wizard browser');
await evaluate(`document.querySelector('main form button').click()`);
await wait(`document.querySelector('h2')?.textContent.startsWith('1/8')`);
await field('Tipo de proponente', 'person');
await field('Nome ou denominação', 'Synthetic owner');
await field('NUIT', 'SYNTHETIC');
await field('Email de contacto', 'synthetic@example.test');
await field('País', 'MZ');
await wait(
`document.body.innerText.includes('Rascunho guardado no servidor')`,
);
await send('Page.reload');
await wait(
`!![...document.querySelectorAll('input')].find(e=>e.value==='Synthetic owner')`,
);
observations.push({ scenario: 'autosave_and_reload', passed: true });
await step(2);
await field('Qualidade', 'holder');
await field('Titular declarado', 'Synthetic owner');
await field('Declaro legitimidade', 'yes');
await step(3);
await evaluate(
`(()=>{const e=[...document.querySelectorAll('label')].find(l=>l.querySelector('span')?.textContent==='Sector')?.querySelector('select'); e.value=e.options[1].value;e.dispatchEvent(new Event('change',{bubbles:true}));})()`,
);
await field(
'Descrição reservada',
'Synthetic private description long enough for this browser-only review.',
);
await evaluate(
`(()=>{const e=document.querySelector('input[type=date]');const setter=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value').set;setter.call(e,'2027-12-31');e.dispatchEvent(new Event('input',{bubbles:true}));e.dispatchEvent(new Event('change',{bubbles:true}));})()`,
);
await step(5);
await field('Localização declarada', 'Synthetic location, no coordinates');
await step(7);
await field('Referência da fonte', 'Synthetic source for browser testing');
await step(8);
await wait(
`document.body.innerText.includes('Rascunho guardado no servidor')`,
);
const code = await evaluate(
`document.querySelector('main').innerText.match(/MNE-[0-9a-f-]{36}/)[0]`,
);
await field('Digite o código', code);
await evaluate(`document.querySelector('input[type=checkbox]').click()`);
for (const width of [390, 1440]) {
await send('Emulation.setDeviceMetricsOverride', {
width,
height: 900,
deviceScaleFactor: 1,
mobile: width === 390,
});
const fits = await evaluate(
`document.documentElement.scrollWidth<=innerWidth+1`,
);
if (!fits) throw Error('Horizontal overflow');
observations.push({
scenario: 'responsive_summary',
width,
passed: true,
});
}
await evaluate(
`[...document.querySelectorAll('button')].find(e=>e.textContent.includes('Confirmar e submeter')).click()`,
);
await wait(
`document.body.innerText.includes('Submetido') && !document.querySelector('progress')`,
);
observations.push({ scenario: 'private_submission', passed: true });
if (errors.length) throw Error('Browser exceptions');
fs.writeFileSync(
'docs/investmoz/existing-business/wizard-browser-validation.json',
JSON.stringify({ passed: true, observations, errors }, null, 2) + '\n',
);
console.log(JSON.stringify({ passed: true, observations, errors }));
} finally {
ws.close();
await fetch('http://127.0.0.1:9224/json/close/' + target.id);
}