72 lines
5.8 KiB
JavaScript
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(); }
|