// Actual desktop Chromium UI on guarded QA. No QA18 credentials, cookies, or MFA values in evidence. const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const crypto = require('node:crypto'); const { chromium, } = require('C:/Users/macbook/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules/playwright'); const ROOT = path.resolve(__dirname, '..'), PRIVATE = path.join(ROOT, 'storage/app/private/investmoz-v2/portal-audit'); const BASE = 'http://127.0.0.1:18097', TARGET = 'desk_restore_20261009_174726_test:55441'; const actors = JSON.parse( fs.readFileSync( path.join(PRIVATE, 'profiles-v1-actors-private.json'), 'utf8', ), ); assert.equal(actors.promoter.id, 21); const receipt = path.join(PRIVATE, 'legacy-sectors-browser-qa.json'); const old = fs.existsSync(receipt) ? JSON.parse(fs.readFileSync(receipt, 'utf8')) : {}; if (old.started_at) fs.copyFileSync( receipt, path.join( PRIVATE, 'legacy-sectors-browser-attempt-' + old.started_at.replaceAll(':', '-') + '.json', ), ); const stamp = new Date().toISOString().replace(/[:.]/g, '-'), shots = path.join(PRIVATE, 'legacy-sectors-browser-' + stamp); fs.mkdirSync(shots, { recursive: true }); const proof = { started_at: new Date().toISOString(), base: BASE, target: TARGET, browser: 'Chromium 1243, isolated headless desktop contexts', interface_observed: true, actor_id: 21, active_writes: 0, dossiers: old.dossiers || {}, checks: {}, screenshots: [], events: [], phase: 'initialization', }; let browser; function save() { fs.writeFileSync(receipt, JSON.stringify(proof, null, 2)); } function phase(value) { proof.phase = value; save(); console.log(JSON.stringify({ phase: value })); } function otp(secret) { let bits = 0, value = 0; const bytes = [], alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; for (const char of secret.toUpperCase().replace(/=+$/, '')) { const digit = alphabet.indexOf(char); assert(digit >= 0); value = (value << 5) | digit; bits += 5; if (bits >= 8) { bytes.push((value >>> (bits - 8)) & 255); bits -= 8; } } const counter = Buffer.alloc(8); counter.writeBigUInt64BE(BigInt(Math.floor(Date.now() / 30000))); const digest = crypto .createHmac('sha1', Buffer.from(bytes)) .update(counter) .digest(), offset = digest[19] & 15; return String( (digest.readUInt32BE(offset) & 0x7fffffff) % 1000000, ).padStart(6, '0'); } async function context() { const ctx = await browser.newContext({ viewport: { width: 1440, height: 1000 }, locale: 'pt-MZ', reducedMotion: 'reduce', }); ctx.qaConfirmed = false; await ctx.route('**/*', async (route) => { const request = route.request(), url = new URL(request.url()); if (url.origin !== BASE) return route.abort(); if (!['GET', 'HEAD'].includes(request.method())) assert(ctx.qaConfirmed, 'Actual QA guard precedes writes'); await route.continue(); }); ctx.on('response', (response) => { const url = new URL(response.url()); if (url.origin === BASE && !url.pathname.startsWith('/build-qa-')) { const headers = response.headers(); proof.events.push({ method: response.request().method(), path: url.pathname, status: response.status(), target: headers['x-investmoz-qa-target'] || null, redirect_path: headers.location ? new URL(headers.location, BASE).pathname : null, }); save(); } }); ctx.on('requestfailed', (request) => { const url = new URL(request.url()); proof.events.push({ request_failed: url.pathname, reason: request.failure()?.errorText, }); save(); }); return ctx; } async function open(page, url) { const response = await page.goto(BASE + url, { waitUntil: 'domcontentloaded', timeout: 45000, }); assert.equal(response.headers()['x-investmoz-qa-target'], TARGET); page.context().qaConfirmed = true; return response; } async function shot(page, name, fullPage = true) { const file = path.join(shots, name + '.png'); await page.screenshot({ path: file, fullPage }); proof.screenshots.push(path.relative(ROOT, file).replaceAll('\\', '/')); } async function login(page, role) { const account = actors[role]; if (new URL(page.url()).pathname !== '/login') await open(page, '/login'); await page.locator('#email').fill(account.email); await page.locator('#password').fill(account.password); await page.locator('[data-test="login-button"]').click(); await page.waitForURL(BASE + '/two-factor-challenge'); await page.locator('input[name="code"]').fill(otp(account.totp_secret)); await page.getByRole('button', { name: 'Continue', exact: true }).click(); await page.waitForURL( (url) => !['/login', '/two-factor-challenge'].includes(url.pathname), ); await page.waitForLoadState('networkidle'); } async function logout(page) { await open(page, '/dashboard'); const triggers = page.locator('button[aria-haspopup="menu"]'); for (let i = (await triggers.count()) - 1; i >= 0; i--) { await triggers.nth(i).click(); const action = page.locator('[data-test="logout-button"]'); if (await action.isVisible()) { await action.click(); await page.waitForURL(BASE + '/'); return; } await page.keyboard.press('Escape'); } throw new Error('Visible logout missing'); } async function dossier(page, entry) { await page.waitForURL(BASE + '/investimentos/' + entry.id); await page .getByRole('heading', { name: entry.title, exact: true }) .waitFor(); assert.equal( await page.getByRole('heading', { name: /^Fase \d+ ·/ }).count(), 5, ); assert.equal(await page.locator('article[id^="S"]').count(), 19); const refs = page.getByRole('region', { name: 'Fontes da ideia' }); assert.equal(await refs.locator('li').count(), 1); assert.equal( new URL(await refs.locator('a').first().getAttribute('href'), BASE) .pathname, entry.href, ); await page .getByRole('heading', { name: 'Próxima acção', exact: true }) .waitFor(); } (async () => { browser = await chromium.launch({ headless: true, executablePath: 'C:/Users/macbook/AppData/Local/ms-playwright/chromium-1243/chrome-win64/chrome.exe', }); phase('public sector pages, images and search'); const ctx = await context(), page = await ctx.newPage(); page.setDefaultTimeout(15000); for (const [group, expected] of [ ['energia-mineracao', 3], ['turismo', 12], ['infraestruturas', 1], ]) { assert.equal((await open(page, '/explorar/' + group)).status(), 200); assert.equal( await page .getByRole('link', { name: 'Ler o artigo', exact: true }) .count(), expected, ); const image = page.locator('main img'); await image.waitFor(); await image.evaluate((img) => img.decode()); assert( await image.evaluate((img) => img.complete && img.naturalWidth > 0), ); assert( await page.evaluate( () => document.documentElement.scrollWidth <= innerWidth, ), ); await shot(page, 'public-' + group); const articleLinks = await page .getByRole('link', { name: 'Ler o artigo', exact: true }) .evaluateAll((links) => links.map((a) => a.getAttribute('href'))); for (const href of articleLinks) { await open(page, href); const body = page.getByRole('region', { name: 'Artigo educativo' }); await body.waitFor(); assert((await body.innerText()).trim().length > 0); await page.locator('main img').evaluate((img) => img.decode()); assert( await page .locator('main img') .evaluate((img) => img.complete && img.naturalWidth > 0), ); } await open(page, '/explorar/' + group); await page.getByRole('searchbox').fill('zzzz-no-content-qa'); await page .getByRole('button', { name: 'Pesquisar', exact: true }) .click(); await page .getByText('Nenhum conteúdo corresponde à pesquisa.', { exact: true, }) .waitFor(); assert.equal( await page .getByRole('link', { name: 'Ler o artigo', exact: true }) .count(), 0, ); } await open(page, '/explorar/energia-mineracao'); await page.getByRole('searchbox').fill('Energia'); await page.getByRole('button', { name: 'Pesquisar', exact: true }).click(); await page.locator('h3 a[href="/energia"]').waitFor(); await open(page, '/energia-mineracao'); const body = page.getByRole('region', { name: 'Artigo educativo' }); await body.locator('a[href="/energia"]').click(); await page.waitForURL(BASE + '/energia'); await open(page, '/energia-mineracao'); await body.locator('a[href="/mineracao"]').click(); await page.waitForURL(BASE + '/mineracao'); await open(page, '/infraestruturas'); await page .getByRole('region', { name: 'Artigo educativo' }) .getByRole('heading', { name: '2. Sectores Prioritários de Investimento', exact: true, }) .waitFor(); await shot(page, 'infra-original-heading'); assert.equal((await open(page, '/mercado-financeiro')).status(), 404); proof.checks.public_read_search_images_distinct_articles = true; save(); phase('public CTA, QA21 normal MFA, UI creation or continuation'); for (const href of [ '/energia', '/mineracao', '/turismo', '/infraestruturas', ]) { await open(page, href); await page .getByRole('link', { name: 'Iniciar ou continuar uma ideia', exact: true, }) .click(); await page.waitForURL((url) => ['/login', '/investimentos/a-partir-de' + href].includes( url.pathname, ), ); if (new URL(page.url()).pathname === '/login') await login(page, 'promoter'); await page.waitForURL(BASE + '/investimentos/a-partir-de' + href); const entry = (proof.dossiers[href] ||= { title: 'QA visual — ideia ' + href.slice(1) + ' ' + stamp, href, }); if (!entry.id) { await page.getByLabel('Título', { exact: true }).fill(entry.title); await page .getByLabel( 'Autorizo reutilizar o meu perfil neste dossier privado.', ) .check(); await page .getByRole('button', { name: 'Criar e guardar dossier', exact: true, }) .click(); await page.waitForURL(/\/investimentos\/[0-9a-f-]{36}$/); entry.id = new URL(page.url()).pathname.split('/').pop(); entry.created_via_interface_at = new Date().toISOString(); save(); } else { const card = page.getByRole('listitem').filter({ has: page.getByRole('link', { name: entry.title, exact: true, }), }); await card .getByRole('button', { name: 'Continuar neste dossier com o artigo', exact: true, }) .click(); } await dossier(page, entry); await shot(page, 'dossier-' + href.slice(1), false); await page .getByRole('region', { name: 'Fontes da ideia' }) .locator('a') .first() .click(); await page.waitForURL( (url) => url.pathname === href && url.searchParams.get('context') === entry.id, ); await page .getByRole('link', { name: 'Continuar: ' + entry.title, exact: true, }) .click(); await dossier(page, entry); entry.source_return_to_same_dossier = true; save(); } phase('visible logout, new MFA browser session, recovery and refresh'); await logout(page); await ctx.close(); const freshCtx = await context(), fresh = await freshCtx.newPage(); const first = Object.values(proof.dossiers)[0]; await open(fresh, '/investimentos/' + first.id); await fresh.waitForURL(BASE + '/login'); await login(fresh, 'promoter'); for (const entry of Object.values(proof.dossiers)) { await open(fresh, '/investimentos/' + entry.id); await dossier(fresh, entry); await fresh.reload({ waitUntil: 'networkidle' }); await dossier(fresh, entry); await open(fresh, '/investimentos/a-partir-de' + entry.href); const card = fresh.getByRole('listitem').filter({ has: fresh.getByRole('link', { name: entry.title, exact: true, }), }); await card .getByRole('button', { name: 'Continuar neste dossier com o artigo', exact: true, }) .click(); await dossier(fresh, entry); entry.new_session_recovered_and_refreshed = true; entry.single_reference_after_repetition = true; save(); } phase('other profile denies dossiers, public context remains private'); const otherCtx = await context(), other = await otherCtx.newPage(); await open(other, '/login'); await login(other, 'outsider'); const publicCtx = await context(), publicPage = await publicCtx.newPage(); for (const entry of Object.values(proof.dossiers)) { assert.equal( (await open(other, '/investimentos/' + entry.id)).status(), 403, ); for (const reader of [other, publicPage]) { await open(reader, entry.href + '?context=' + entry.id); await reader .getByRole('region', { name: 'Artigo educativo' }) .waitFor(); assert.equal( await reader.getByText(entry.title, { exact: false }).count(), 0, ); } entry.other_actor_id = actors.outsider.id; entry.denied_status = 403; entry.private_context_hidden = true; } await logout(fresh); await freshCtx.close(); await otherCtx.close(); await publicCtx.close(); proof.result = 'passed'; proof.completed_at = new Date().toISOString(); save(); console.log( JSON.stringify({ result: proof.result, dossiers: Object.keys(proof.dossiers).length, screenshots: proof.screenshots.length, interface_observed: true, active_writes: 0, }), ); })() .catch(async (error) => { proof.result = 'failed'; proof.error_type = error.name; let message = error.message; for (const actor of Object.values(actors)) for (const key of ['password', 'totp_secret', 'secret']) if (actor[key]) message = message.replaceAll(actor[key], '[REDACTED]'); proof.error = message .replace(/\b\d{6}\b/g, '[MFA OMITIDO]') .slice(0, 1600); proof.page_states = browser ? await Promise.all( browser .contexts() .flatMap((ctx) => ctx.pages()) .map(async (page) => ({ path: new URL(page.url()).pathname, headings: await page .locator('h1') .allTextContents() .catch(() => []), alerts: await page .getByRole('alert') .count() .catch(() => 0), })), ) : []; save(); console.error( JSON.stringify({ result: 'failed', phase: proof.phase, type: error.name, }), ); process.exitCode = 1; }) .finally(async () => { if (browser) await browser.close(); });