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 { execFileSync } = require('node:child_process'); const base = 'http://127.0.0.1:18092'; const dir = 'storage/app/private/investmoz-v2'; (async () => { const browser = await chromium.launch({ headless: true, executablePath: 'C:/Users/macbook/AppData/Local/ms-playwright/chromium-1243/chrome-win64/chrome.exe', }); const errors = [], blocked = [], checks = []; async function context(width) { const ctx = await browser.newContext({ viewport: { width, height: width < 640 ? 844 : 1000 }, hasTouch: width < 640, }); await ctx.route('**/*', (r) => { if (r.request().url().startsWith(base)) return r.continue(); blocked.push(r.request().url()); return r.abort(); }); const page = await ctx.newPage(); page.on('pageerror', (e) => errors.push(e.message)); return page; } async function visit(page, path) { const r = await page.goto(base + path); assert.equal( r.headers()['x-investmoz-qa-target'], 'investmoz_v2_20261008_test:55441', ); assert.ok(r.status() < 400, path + ':' + r.status()); await page.locator('nav[aria-label="Áreas do portal"]').waitFor(); assert.equal( await page.locator('nav[aria-label="Áreas do portal"]').count(), 1, ); await page.waitForLoadState('networkidle'); } async function login(page, role) { await visit(page, '/login'); 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')); } async function api(page, path, method = 'GET', data) { return 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 }, ); } async function menus(page, management, touch) { const nav = page.locator('nav[aria-label="Áreas do portal"]'); assert.equal( await nav.getByRole('button', { name: /^Gestão/ }).count(), management ? 1 : 0, ); const groups = nav.locator(':scope > ul > li > button'); for (let i = 0; i < (await groups.count()); i++) { const b = groups.nth(i); await b.scrollIntoViewIfNeeded(); if (touch) await b.tap(); else { await b.focus(); await page.keyboard.press('Enter'); } assert.equal(await b.getAttribute('aria-expanded'), 'true'); const panel = nav.locator( `[id="${await b.getAttribute('aria-controls')}"]`, ); const box = await panel.boundingBox(), width = page.viewportSize().width; assert.ok( box.x >= -1 && box.x + box.width <= width + 1, JSON.stringify({ box, width }), ); const nested = panel.getByRole('button'); if (await nested.count()) { const n = nested.first(); await n.click(); assert.equal(await n.getAttribute('aria-expanded'), 'true'); await page.keyboard.press('Escape'); assert.equal(await n.getAttribute('aria-expanded'), 'false'); assert.equal( await n.evaluate((el) => el === document.activeElement), true, ); } await page.keyboard.press('Escape'); assert.equal(await b.getAttribute('aria-expanded'), 'false'); assert.equal( await b.evaluate((el) => el === document.activeElement), true, ); } const overflow = await page.evaluate( () => document.documentElement.scrollWidth > window.innerWidth, ); assert.equal(overflow, false, page.url()); } try { const caseId = JSON.parse( fs.readFileSync(dir + '/desk-browser-evidence.json'), ).caseId; for (const width of [1440, 390]) { const page = await context(width); await visit(page, '/'); await menus(page, false, width < 640); await page.screenshot({ path: `${dir}/public-${width}.png`, fullPage: true, }); for (const [kind, title] of [ ['project', 'V2 QA project'], ['cluster', 'V2 QA cluster'], ]) { await visit(page, `/carteira?kind=${kind}`); await page.getByLabel('Pesquisar', { exact: true }).fill(title); await page .getByRole('button', { name: 'Pesquisar', exact: true }) .click(); await page .getByRole('link', { name: title, exact: true }) .click(); await page .getByRole('heading', { name: title, exact: true }) .waitFor(); assert.ok( await page .locator('[data-current-group="true"]') .textContent() .then((t) => t.includes('Explorar')), ); } await visit(page, '/zonas'); await page.getByLabel('Pesquisar por nome ou código').fill('V2 QA'); await page .getByRole('button', { name: 'Pesquisar', exact: true }) .click(); await page .getByRole('link', { name: 'V2 QA localização fictícia', exact: true, }) .click(); await page .getByRole('heading', { name: 'V2 QA localização fictícia', exact: true, }) .waitFor(); await login(page, 'investor'); await visit(page, '/investidor'); await menus(page, false, width < 640); await page.screenshot({ path: `${dir}/investor-${width}.png`, fullPage: true, }); const draft = await api( page, '/api/v1/simulation-drafts/00000000-0000-4000-8000-000000000202', ); assert.equal(draft.status, 200); assert.equal( draft.data.data.economic_snapshot.project_title, 'V2 QA rascunho anterior', ); const restored = await api( page, '/balcao-virtual/dossiers', 'POST', { title: 'V2 QA continuidade do rascunho', simulation_id: '00000000-0000-4000-8000-000000000202', reuse_consent: true, client_key: crypto.randomUUID(), }, ); assert.equal(restored.status, 201); const guidance = await api( page, `/balcao-virtual/dossiers/${restored.data.id}/orientacao`, ); assert.equal( guidance.data.facts.project_summary, 'Ideia anterior preservada QA', ); assert.equal( guidance.data.phases[1].evidence[0].state, 'investor_declared_not_verified', ); assert.equal(guidance.data.phases[2].evidence[0].state, 'absent'); for (const path of [ '/como-investir', '/simulador-fiscal', `/balcao-virtual/dossiers/${caseId}`, '/parcerias', ]) { await visit(page, path); await page .getByRole('button', { name: 'Acompanhar o meu projecto neste portal', exact: true, }) .click(); await page .getByLabel('Contexto do projecto') .selectOption(caseId); await page .getByText('Regras internas', { exact: false }) .waitFor(); await page .getByRole('button', { name: 'Acompanhar o meu projecto neste portal', exact: true, }) .click(); } checks.push( `Publicação QA, pesquisa e fichas; menus públicos e de investidor; rascunho e contexto entre módulos em ${width}px.`, ); await page.context().close(); } const owner = await context(1440); await login(owner, 'investor'); const manager = await context(1440); await login(manager, 'manager'); await visit(manager, '/management/crm/leads'); await menus(manager, true, false); execFileSync( 'php', ['tools/investmoz-v2-qa-router.php', 'seed', caseId], { stdio: 'pipe' }, ); await visit(owner, `/balcao-virtual/dossiers/${caseId}?phase=location`); await owner .getByText('Documento associado, por conferir', { exact: false }) .first() .waitFor(); const dossier = await api( manager, `/balcao-virtual/dossiers/${caseId}`, ); const document = dossier.data.documents.find( (d) => d.category === 'location_evidence', ); assert.ok(document); await visit(manager, `/balcao-virtual/dossiers/${caseId}`); const review = manager.locator('form').filter({ has: manager.getByRole('heading', { name: 'Conferência documental interna', }), }); await review .getByLabel('Documento', { exact: true }) .selectOption(document.id); await review.getByLabel('Localização', { exact: true }).check(); await review .getByLabel('O que foi verificado') .fill('Conferido apenas o acesso descrito na fixture V2 QA.'); await review .getByLabel('Evidência utilizada') .fill('Ficheiro sintético, versão e hash registados nesta QA.'); await review .getByRole('button', { name: 'Registar conferência com a minha autoria', }) .click(); await manager.waitForLoadState('networkidle'); await visit(owner, `/balcao-virtual/dossiers/${caseId}?phase=location`); await owner .getByText('Verificador: Guidance QA manager', { exact: false }) .waitFor(); await owner .getByText('Conferido internamente no âmbito descrito', { exact: false, }) .first() .waitFor(); await owner.screenshot({ path: `${dir}/scoped-evidence.png`, fullPage: true, }); const other = await context(390); await login(other, 'other'); assert.equal( (await api(other, `/balcao-virtual/dossiers/${caseId}/orientacao`)) .status, 403, ); assert.equal( ( await api( other, '/api/v1/simulation-drafts/00000000-0000-4000-8000-000000000202', ) ).status, 404, ); assert.equal((await api(other, '/management/crm/leads')).status, 403); await visit(other, '/investidor'); await menus(other, false, true); checks.push( 'Conferência pela interface com autor/data/âmbito; gestão autorizada; isolamento de dossier e rascunho entre utilizadores.', ); assert.deepEqual(errors, []); fs.writeFileSync( dir + '/navigation-browser-evidence.json', JSON.stringify( { passed: true, database: 'investmoz_v2_20261008_test', port: 55441, checks, errors, blockedExternalRequests: blocked, }, null, 2, ), ); console.log(JSON.stringify({ passed: true, checks, errors })); } finally { await browser.close(); } })().catch((e) => { console.error(e); process.exitCode = 1; });