// Synthetic QA server only; never writes to the application database. import fs from 'node:fs'; 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 sequence = 0; const pending = new Map(); const errors = []; const requests = []; const responses = []; const responseIds = new Set(); const responseBodies = []; ws.onmessage = (event) => { const message = JSON.parse(event.data); if ( message.method === 'Network.requestWillBeSent' && message.params.request.url.includes('/management/opportunities') && message.params.request.postData ) { const request = message.params.request; let data = {}; try { data = JSON.parse(request.postData); } catch {} const keys = [ 'province_id', 'district_id', 'admin_post_id', 'locality_id', 'territory_version', 'reference', ]; requests.push({ method: request.method, path: new URL(request.url).pathname, payload: Object.fromEntries( keys.filter((k) => k in data).map((k) => [k, data[k]]), ), }); } if ( message.method === 'Network.responseReceived' && message.params.response.url.includes('/management/opportunities') ) { responses.push({ path: new URL(message.params.response.url).pathname, status: message.params.response.status, }); responseIds.add(message.params.requestId); } if ( message.method === 'Network.loadingFinished' && responseIds.has(message.params.requestId) ) { send('Network.getResponseBody', { requestId: message.params.requestId }) .then((result) => { try { const body = JSON.parse(result.body); responseBodies.push({ component: body.component, errors: body.props?.errors, }); } catch {} }) .catch(() => {}); } if (message.method === 'Runtime.exceptionThrown') errors.push(message.params.exceptionDetails.text); if (message.id) { const callback = pending.get(message.id); pending.delete(message.id); if (message.error) callback.reject(message.error); else callback.resolve(message.result); } }; const send = (method, params = {}) => new Promise((resolve, reject) => { const id = ++sequence; pending.set(id, { resolve, reject }); ws.send(JSON.stringify({ id, method, params })); }); const evaluate = async (expression) => { const result = await send('Runtime.evaluate', { expression, returnByValue: true, awaitPromise: true, }); if (result.exceptionDetails) throw Error(JSON.stringify(result.exceptionDetails)); return result.result?.value; }; const wait = async (expression) => { for (let n = 0; n < 100; n++) { if (await evaluate(expression)) return; await new Promise((resolve) => setTimeout(resolve, 100)); } throw Error('Timed out: ' + expression); }; const select = async (index, value) => evaluate( `(()=>{const element=document.querySelectorAll('select')[${index}];Object.getOwnPropertyDescriptor(HTMLSelectElement.prototype,'value').set.call(element,${JSON.stringify(value)});element.dispatchEvent(new Event('change',{bubbles:true}));})()`, ); try { await send('Runtime.enable'); await send('Page.enable'); await send('Network.enable'); await send('Page.navigate', { url: 'http://127.0.0.1:18027/login' }); await wait( `document.readyState==='complete' && (location.pathname!='/login' || !!document.querySelector('input[type="password"]'))`, ); if (await evaluate(`location.pathname==='/login'`)) { for (const [selector, text] of [ ['input[type="email"]', 'mne-browser@example.test'], ['input[type="password"]', 'SyntheticMneOnly-2026!'], ]) { await evaluate( `document.querySelector(${JSON.stringify(selector)}).focus()`, ); await send('Input.insertText', { text }); } await evaluate( `document.querySelector('button[type="submit"]').click()`, ); await wait(`!location.pathname.startsWith('/login')`); } const token = 'geo-browser-' + Date.now(); const observations = []; await send('Page.navigate', { url: 'http://127.0.0.1:18027/management/opportunities/create', }); await wait(`document.querySelectorAll('select').length===7`); const fill = async (label, value) => { await evaluate( `(()=>{const label=[...document.querySelectorAll('label')].find(l=>l.textContent.startsWith(${JSON.stringify(label)}));const input=label.querySelector('input');input.focus();input.select();})()`, ); if (value === '') { await send('Input.dispatchKeyEvent', { type: 'keyDown', key: 'Backspace', code: 'Backspace', windowsVirtualKeyCode: 8, }); await send('Input.dispatchKeyEvent', { type: 'keyUp', key: 'Backspace', code: 'Backspace', windowsVirtualKeyCode: 8, }); } else await send('Input.insertText', { text: value }); }; await fill('Referência', token); await fill('Título', token); await fill('Slug', token); await select( 4, await evaluate( `document.querySelectorAll('select')[4].options[1].value`, ), ); for (let level = 0; level < 4; level++) { await wait( `!document.querySelectorAll('select')[${level}].disabled && document.querySelectorAll('select')[${level}].options.length>1`, ); await select( level, `10000000-0000-4000-8000-${String(level + 2).padStart(12, '0')}`, ); } const save = () => evaluate( `[...document.querySelectorAll('button')].find(b=>b.textContent==='Guardar').click()`, ); const reopen = async () => { await wait( `location.pathname==='/management/opportunities' && document.body.innerText.includes(${JSON.stringify(token)})`, ); await evaluate( `[...document.querySelectorAll('article')].find(a=>a.textContent.includes(${JSON.stringify(token)})).querySelector('a').click()`, ); await wait( `location.pathname.endsWith('/edit') && document.querySelectorAll('select').length===7`, ); }; await save(); await reopen(); await wait( `document.querySelectorAll('select')[3].value==='10000000-0000-4000-8000-000000000005'`, ); observations.push('create_save_reopen_full_path'); // Server validation must retain the chain and expose the failing field. await fill('Referência', ''); await save(); await wait(`document.querySelector('[role="alert"]')!==null`); if ( !(await evaluate( `document.querySelectorAll('select')[3].value==='10000000-0000-4000-8000-000000000005'`, )) ) throw Error('Validation lost selection'); await fill('Referência', token); observations.push('validation_preserves_selection'); await select(2, ''); await wait( `document.querySelectorAll('select')[3].disabled && document.querySelectorAll('select')[3].value===''`, ); await save(); await reopen(); await wait( `document.querySelectorAll('select')[1].value==='10000000-0000-4000-8000-000000000003' && document.querySelectorAll('select')[2].value===''`, ); observations.push('edit_save_reopen_and_descendant_reset'); await send('Network.setBlockedURLs', { urls: [ '*://127.0.0.1:18027/management/territory-options?type=province*', ], }); await evaluate( `document.querySelector('input[aria-label="Pesquisar Província"]').focus()`, ); await send('Input.insertText', { text: 'Synthetic' }); await wait(`document.body.innerText.includes('Tentar novamente')`); await send('Network.setBlockedURLs', { urls: [] }); await evaluate( `[...document.querySelectorAll('button')].find(b=>b.textContent==='Tentar novamente').click()`, ); await wait( `!document.body.innerText.includes('Tentar novamente') && document.querySelectorAll('select')[0].options.length>1 && !document.querySelectorAll('select')[0].disabled`, ); observations.push('network_failure_and_retry'); await evaluate( `document.querySelector('input[aria-label="Pesquisar Distrito"]').focus()`, ); await send('Input.insertText', { text: 'no-such-synthetic-district' }); await wait( `document.body.innerText.includes('Sem opções para esta pesquisa.')`, ); observations.push('empty_search'); await select(0, ''); await select(0, '10000000-0000-4000-8000-000000000002'); await select(0, ''); await new Promise((resolve) => setTimeout(resolve, 500)); if ( !(await evaluate( `[...document.querySelectorAll('select')].slice(1,4).every(s=>s.disabled && s.value==='' && s.options.length===1)`, )) ) throw Error('Stale children after rapid parent changes'); observations.push('rapid_parent_change_cancels_and_clears_children'); const widths = []; for (const width of [390, 1440]) { await send('Emulation.setDeviceMetricsOverride', { width, height: 900, deviceScaleFactor: 1, mobile: width < 600, }); widths.push({ width, noOverflow: await evaluate( 'document.documentElement.scrollWidth<=innerWidth+1', ), }); } if (errors.length || widths.some((w) => !w.noOverflow)) throw Error('Browser validation failed'); const report = { date: new Date().toISOString(), observations, requests, responses, widths, javascript_errors: errors.length, scope: 'Synthetic isolated QA', }; fs.writeFileSync( 'docs/investmoz/gis/opportunity-dropdown-browser.json', JSON.stringify(report, null, 2) + '\n', ); console.log(JSON.stringify(report)); } catch (error) { console.log(JSON.stringify({ requests, responses, responseBodies })); console.log( await evaluate( `({path:location.pathname,body:document.body.innerText.slice(0,800)})`, ), ); throw error; } finally { ws.close(); await fetch(`http://127.0.0.1:9224/json/close/${target.id}`); }