Files
Investidor-V2/tools/existing-business-browser.mjs
T

72 lines
5.8 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); m.error ? p.reject(m.error) : 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 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.querySelector('h1') && document.body.innerText.includes('Preparar neg')`);
await fill('main form input', 'Synthetic browser business');
await evaluate(`document.querySelector('main form button').click()`);
await wait(`location.pathname.startsWith('/investidor/negocios/') && !!document.querySelector('textarea')`);
await fill('fieldset textarea', 'Synthetic private description for review of a browser-only business.');
await fill('fieldset input[maxlength="2000"]', 'Synthetic declared reason');
await fill('fieldset input[maxlength="2000"]:last-of-type', 'Synthetic source evidence');
// Find source input by its wrapping label; field labels are accessible label elements.
await evaluate(`(()=>{const labels=[...document.querySelectorAll('label')];const field=labels.find(l=>l.textContent.includes('Referência da fonte'))?.querySelector('input'); if(field) field.focus();})()`);
await send('Input.insertText', { text: 'Synthetic source evidence' });
await evaluate(`(()=>{const el=document.querySelector('input[type="date"]'); const setter=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value').set; setter.call(el,'2027-12-31');el.dispatchEvent(new Event('input',{bubbles:true}));el.dispatchEvent(new Event('change',{bubbles:true}));})()`);
await fill('main form > label textarea', 'Synthetic save for browser QA');
await evaluate(`document.querySelector('main form button').click()`);
await wait(`document.body.innerText.includes('versão 2')`);
observations.push({ scenario: 'private_create_and_save', passed: true });
await fill('main form > label textarea', 'Synthetic submission for browser QA');
await evaluate(`document.querySelector('input[type="checkbox"]').click()`);
await evaluate(`[...document.querySelectorAll('button')].find(b=>b.textContent==='Submeter para análise').click()`);
await wait(`document.body.innerText.includes('versão 3') && document.body.innerText.includes('Submetido')`);
observations.push({ scenario: 'private_submit_with_review_consent', passed: true });
for (const width of [390, 1440]) {
await send('Emulation.setDeviceMetricsOverride', { width, height: 900, deviceScaleFactor: 1, mobile: width < 600 });
const result = await evaluate(`(()=>{const a=[...document.querySelectorAll('a')].find(a=>getComputedStyle(a).position==='fixed'&&a.href.endsWith('/apoio'));const b=a?.getBoundingClientRect();return {width:innerWidth,breadcrumb:!!document.querySelector('nav[aria-label="Caminho de navegação"]'),supportVisible:!!b&&b.bottom<=innerHeight&&b.left>=0,overflow:document.documentElement.scrollWidth>innerWidth+1};})()`);
if (!result.breadcrumb || !result.supportVisible || result.overflow) throw Error('Layout failure');
observations.push(result);
}
if (errors.length) throw Error('JavaScript errors observed');
fs.writeFileSync('docs/investmoz/existing-business/browser-validation.json', JSON.stringify({ date: new Date().toISOString(), scope: 'Synthetic private create/save and responsive layout in isolated QA; institutional review covered by integration tests', observations, errors }, null, 2) + '\n');
console.log(JSON.stringify({ passed: observations.length, javascript_errors: errors.length }));
} catch (error) {
console.log(await evaluate(`({path:location.pathname,title:document.title,body:document.body.innerText.slice(0,1200)})`));
throw error;
} finally { await send('Page.close'); ws.close(); }