// Read-only browser smoke test; reuse the dedicated QA Edge debugger on 9224. import fs from 'node:fs'; const base = process.argv[2] ?? 'http://127.0.0.1:8000'; if (!/^http:\/\/127\.0\.0\.1:\d+$/.test(base)) throw Error('Local QA server required'); 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 = []; ws.onmessage = (event) => { const message = JSON.parse(event.data); if (message.method === 'Runtime.exceptionThrown') errors.push(message.params.exceptionDetails.text); if (message.id) { const callback = pending.get(message.id); pending.delete(message.id); callback(message.error ? { error: message.error } : message.result); } }; const send = (method, params = {}) => new Promise((resolve) => { const id = ++sequence; pending.set(id, resolve); ws.send(JSON.stringify({ id, method, params })); }); const evaluate = async (expression) => { const result = await send('Runtime.evaluate', { expression, returnByValue: true, }); if (result.exceptionDetails || result.error) throw Error(JSON.stringify(result)); return result.result?.value; }; await send('Page.enable'); await send('Runtime.enable'); const observations = []; try { for (const width of [390, 1440]) { await send('Emulation.setDeviceMetricsOverride', { width, height: 900, deviceScaleFactor: 1, mobile: width < 600, }); for (const path of [ '/zonas?category=zee', '/zonas?category=zfi', '/zonas?category=industrial_park', '/oportunidades', '/oportunidades?view=combined', ]) { await send('Page.navigate', { url: base + path }); let ready = false; for (let attempt = 0; attempt < 100; attempt++) { await new Promise((resolve) => setTimeout(resolve, 100)); ready = await evaluate( `document.readyState==='complete' && location.href===${JSON.stringify(base + path)} && !!document.querySelector('h1')`, ); if (ready) break; } if (!ready) throw Error('Page not ready: ' + path); const result = await evaluate( `({title:document.querySelector('h1').textContent,overflow:document.documentElement.scrollWidth>innerWidth+1,breadcrumb:!!document.querySelector('nav[aria-label="Caminho de navegação"]'),support:[...document.querySelectorAll('a')].some(a=>a.textContent.includes('Contactar Apoio')),dimensions:['Por Cluster','Por Corredor','Por ZEE','Por ZFI'].every(text=>[...document.querySelectorAll('a')].some(a=>a.textContent===text))})`, ); if ( result.overflow || !result.breadcrumb || !result.support || !result.dimensions ) throw Error(JSON.stringify({ path, width, ...result })); observations.push({ path, width, ...result }); } } await send('Network.enable'); await send('Network.setBlockedURLs', { urls: ['*api/v1/investment/map*'] }); await send('Page.navigate', { url: base + '/oportunidades?view=map' }); let fallback = false; for (let attempt = 0; attempt < 100; attempt++) { await new Promise((resolve) => setTimeout(resolve, 100)); fallback = await evaluate( `document.body.innerText.includes('Mapa indisponível') && [...document.querySelectorAll('article a')].some(a=>a.getAttribute('href')?.startsWith('/oportunidades/'))`, ); if (fallback) break; } if (!fallback) throw Error('Map failure did not preserve investment list'); observations.push({ scenario: 'map_request_blocked', fallback_list: true }); await send('Network.setBlockedURLs', { urls: [] }); if (errors.length) throw Error('JavaScript errors: ' + errors.join('; ')); fs.writeFileSync( 'docs/investmoz/zones/browser-validation.json', JSON.stringify( { date: new Date().toISOString(), scope: 'Public pages and empty states only; no institutional workflow/UAT claim', observations, errors, }, null, 2, ), ); console.log( JSON.stringify({ passed: observations.length, javascript_errors: errors.length, }), ); } finally { await send('Page.close'); ws.close(); }