const { chromium, } = require('C:/Users/macbook/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules/playwright'); const fs = require('node:fs'); const assert = require('node:assert/strict'); const base = 'http://127.0.0.1:18091'; (async () => { const browser = await chromium.launch({ headless: true, executablePath: 'C:/Users/macbook/AppData/Local/ms-playwright/chromium-1243/chrome-win64/chrome.exe', }); const errors = []; const log = []; async function login(role) { const context = await browser.newContext(); await context.route('**/*', (r) => r.request().url().startsWith(base) ? r.continue() : r.abort(), ); const page = await context.newPage(); page.on('pageerror', (e) => errors.push(e.message)); const response = await page.goto(base + '/login'); assert.equal( response.headers()['x-investmoz-qa-target'], 'investmoz_guidance_20261008_test:55441', ); await page .locator('input[type=email]') .fill(`guidance-qa-${role}@example.invalid`); await page.locator('input[type=password]').fill('QA-Guidance-2026!'); await page.locator('button[type=submit]').click(); await page.waitForURL((url) => !url.pathname.startsWith('/login')); return page; } async function json(page, path, method = 'GET', data) { const response = await page.evaluate( async ({ path, method, data }) => { const token = decodeURIComponent( document.cookie .split('; ') .find((v) => v.startsWith('XSRF-TOKEN=')) ?.slice(11) ?? '', ); const r = await fetch(path, { method, headers: { Accept: 'application/json', 'Content-Type': 'application/json', 'X-XSRF-TOKEN': token, }, ...(data && method !== 'GET' ? { body: JSON.stringify(data) } : {}), }); return { status: r.status, data: await r.json() }; }, { path, method, data }, ); assert.ok(response.status < 300, JSON.stringify(response)); return response.data; } try { console.log('QA browser: normal authentication'); const investor = await login('investor'); const manager = await login('manager'); console.log('QA browser: prepare dossier'); await investor.goto(base + '/balcao-virtual/painel'); await investor .getByText('Preparar acompanhamento de um projecto', { exact: true, }) .click(); await investor .getByLabel('Título do projecto', { exact: true }) .fill('Percurso completo QA browser'); await investor.locator('input[type=checkbox]').check(); await investor .getByRole('button', { name: 'Preparar dossier', exact: true }) .click(); await investor.waitForURL(/\/balcao-virtual\/dossiers\//); const caseId = investor.url().split('/').pop(); await investor .getByRole('button', { name: 'Preparar rascunho de pedido', exact: true, }) .click(); await investor.waitForURL(/\/balcao-virtual\/pedidos\//); const requestId = investor.url().split('/').pop(); console.log('QA browser: save draft and submit'); await investor .getByLabel(/Descrição e objetivo/) .fill('Preciso de apoio para preparar o investimento fictício QA.'); await investor.getByLabel(/Valor do investimento/).fill('1000.00'); await investor.getByLabel(/Moeda/).first().selectOption('MZN'); await investor .getByRole('button', { name: 'Guardar rascunho', exact: true }) .click(); await investor.waitForLoadState('networkidle'); await investor.reload(); assert.equal( await investor.getByLabel('Valor do investimento').inputValue(), '1000.00', ); await investor .getByLabel( 'Revi o conteúdo e autorizo a submissão para análise interna.', ) .check(); await investor .getByRole('button', { name: 'Submeter este pedido', exact: true }) .click(); await investor.waitForLoadState('networkidle'); let row = (await json(manager, `/balcao-virtual/pedidos/${requestId}`)) .request; assert.equal(row.status, 'submitted'); log.push( 'Rascunho recuperado e submetido explicitamente com protocolo interno.', ); await json( manager, `/balcao-virtual/pedidos/${requestId}/estado`, 'POST', { version: row.version, status: 'returned', reason: 'Indique o mercado previsto. Esclarecimento interno QA.', editable_fields: ['description'], }, ); await investor.reload(); await investor .getByLabel(/Descrição e objetivo/) .fill( 'Mercado declarado em QA; evidência de procura permanece pendente.', ); await investor .getByLabel('Justificação de correcção') .fill('Resposta ao esclarecimento do responsável.'); await investor .getByLabel( 'Revi o conteúdo e autorizo a submissão para análise interna.', ) .check(); await investor .getByRole('button', { name: 'Submeter este pedido', exact: true }) .click(); await investor.waitForLoadState('networkidle'); row = (await json(manager, `/balcao-virtual/pedidos/${requestId}`)) .request; for (const status of ['reviewing', 'opinion', 'accepted']) { await json( manager, `/balcao-virtual/pedidos/${requestId}/estado`, 'POST', { version: row.version, status, reason: 'Resultado interno QA, sem aprovação ou licença institucional.', }, ); row = (await json(manager, `/balcao-virtual/pedidos/${requestId}`)) .request; } await manager.goto(base + `/balcao-virtual/pedidos/${requestId}`); await manager .getByLabel('Mensagem privada', { exact: true }) .fill( 'Resposta humana QA: o dossier foi aceite internamente para acompanhamento.', ); await manager .getByRole('button', { name: 'Enviar esta mensagem no pedido', exact: true, }) .click(); await manager.waitForLoadState('networkidle'); await investor.reload(); await investor .getByText( 'Resposta humana QA: o dossier foi aceite internamente para acompanhamento.', { exact: true }, ) .waitFor(); await investor.screenshot({ path: 'storage/app/private/virtual-desk/qa-response.png', fullPage: true, }); await investor.goto(base + `/balcao-virtual/dossiers/${caseId}`); const guidance = await json( investor, `/balcao-virtual/dossiers/${caseId}/orientacao`, ); await json( investor, `/balcao-virtual/dossiers/${caseId}/recomendacoes`, 'POST', { code: 'support_preparation', context_hash: guidance.fingerprint, decision: 'accepted', reason: 'Assumo o acompanhamento deste mesmo pedido.', }, ); const task = await json( investor, `/balcao-virtual/dossiers/${caseId}/tarefas`, 'POST', { code: 'support_preparation', context_hash: guidance.fingerprint, consent: true, request_id: requestId, due_at: new Date(Date.now() + 86400000).toISOString(), }, ); await investor.reload(); await investor .getByRole('link', { name: 'Consultar o pedido e a resposta do responsável', exact: true, }) .waitFor(); await investor.goto(base + '/parcerias'); await investor .getByRole('button', { name: 'Acompanhar o meu projecto neste portal', exact: true, }) .click(); await investor.getByLabel('Contexto do projecto').selectOption(caseId); await investor.getByText('Regras internas', { exact: false }).waitFor(); log.push( 'Esclarecimento, nova submissão, parecer, aceitação interna e resposta humana preservados.', ); log.push( 'Tarefa aceite no CRM interno ligada ao mesmo pedido; contexto recuperado na Bolsa de Parcerias.', ); assert.deepEqual(errors, []); fs.writeFileSync( 'storage/app/private/virtual-desk/browser-qa-evidence.json', JSON.stringify( { passed: true, database: 'investmoz_guidance_20261008_test', port: 55441, caseId, requestId, taskId: task.id, log, pageErrors: errors, }, null, 2, ), ); console.log(JSON.stringify({ passed: true, log, pageErrors: errors })); } finally { await browser.close(); } })().catch((e) => { console.error(e); process.exitCode = 1; });