import { createRequire } from 'node:module'; import fs from 'node:fs'; import path from 'node:path'; const require = createRequire(import.meta.url); const { chromium, } = require('C:/Users/macbook/AppData/Local/Temp/investmoz-visual-qa-tools-20261004/node_modules/playwright'); const base = 'http://127.0.0.1:8000'; const out = path.resolve('docs/investmoz/pilot'); fs.mkdirSync(out, { recursive: true }); const browser = await chromium.launch({ headless: true }); const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } }); const errors = []; page.on('pageerror', (error) => errors.push(error.message)); const evidence = { base, testedAt: new Date().toISOString(), pages: [], errors, }; async function visit(route, key) { const response = await page.goto(base + route, { waitUntil: 'networkidle', }); evidence.pages.push({ key, status: response?.status(), title: await page.title(), bodyTextLength: (await page.locator('body').innerText()).length, }); if (!response?.ok()) throw new Error(`${route} returned ${response?.status()}`); } await visit('/oportunidades', 'catalogue'); evidence.catalogueStatus = await page .getByText(/\d+ oportunidades publicadas/) .innerText(); evidence.navigationHasCatalogueGroup = (await page.getByText('Catálogos territoriais e económicos').count()) === 1; evidence.publishedOpportunityCount = await page.locator('article').count(); await visit('/carteira?kind=cluster', 'clusters'); evidence.clusterRecords = await page.locator('article').count(); for (const [legacy, expected] of [ ['/clusters', '/carteira?kind=cluster'], ['/corredores', '/carteira?kind=corridor'], ['/projectos-ancora', '/carteira?kind=project'], ]) { await page.goto(base + legacy, { waitUntil: 'networkidle' }); evidence[`legacy${legacy.replaceAll('/', '')}Redirect`] = new URL(page.url()).pathname + new URL(page.url()).search === expected; } await visit('/carteira?kind=cluster', 'clusters-again'); const province = page.getByLabel('Província'); await province.waitFor({ timeout: 5000 }); const provinceValue = await province .locator('option') .nth(1) .getAttribute('value'); if (provinceValue) { await province.selectOption(provinceValue); await page.getByRole('button', { name: 'Pesquisar' }).click(); await page.waitForURL((url) => url.searchParams.has('province_id'), { timeout: 5000, }); evidence.locationFilterApplied = new URL(page.url()).searchParams.get('province_id') === provinceValue; evidence.locationFilteredResults = await page.locator('article').count(); await visit('/carteira?kind=cluster', 'clusters-after-location-filter'); } else { evidence.locationFilterApplied = false; } if (await page.locator('article a').count()) { const prior = page.url(); const detailHref = await page .locator('article a') .first() .getAttribute('href'); await page.locator('article a').first().click(); await page.waitForURL((url) => url.pathname.startsWith('/carteira/'), { timeout: 5000, }); await page.waitForLoadState('networkidle'); evidence.clusterDetailHref = detailHref; evidence.clusterDetailUrl = page.url(); evidence.clusterDetailLoaded = page.url() !== prior && !/SQLSTATE|Undefined column|500 Server Error/.test( await page.locator('body').innerText(), ); evidence.privateManifestationRequiresAccount = (await page .getByText( /Para associar esta referência a uma manifestação privada, entre na sua conta/i, ) .count()) === 1; } await visit( '/carteira?kind=cluster&q=__sem_resultado_qa__', 'clusters-no-results', ); evidence.usefulEmptyState = (await page.getByText(/Ainda não existem fichas publicadas/).count()) === 1; await visit('/carteira?kind=project', 'anchor-projects'); evidence.anchorDossiers = await page.locator('article').count(); if (await page.locator('article a').count()) { await page.locator('article a').first().click(); await page.waitForURL((url) => url.pathname.startsWith('/carteira/'), { timeout: 5000, }); evidence.anchorReferenceVisible = (await page .getByRole('heading', { name: 'Referência de projecto âncora' }) .count()) === 1; evidence.anchorDoesNotClaimOpenTender = (await page .getByText(/(?:não representa|não significa) concurso aberto/i) .count()) === 1; } await visit('/zonas?category=zee', 'zee-catalogue'); evidence.zoneCountText = await page.getByRole('status').first().innerText(); await visit('/negocios', 'existing-business-marketplace'); evidence.businessListingCount = await page.locator('article').count(); await visit('/como-investir', 'investment-guide-desktop'); evidence.guideHasBothPaths = (await page.getByLabel('Tenho uma ideia').count()) === 1 && (await page.getByLabel('Tenho um projecto definido').count()) === 1; await page.getByLabel('Tenho uma ideia').check(); evidence.ideaPathRenders = (await page .getByRole('heading', { name: '1. Ideia e problema' }) .count()) === 1; await page.getByLabel('Actividade (opcional)').fill('Panificação'); evidence.activityContextShown = (await page.getByText(/Actividade opcional: Panificação/).count()) === 1; await page.setViewportSize({ width: 390, height: 844 }); await page.reload({ waitUntil: 'networkidle' }); await page.getByRole('button', { name: 'Abrir menu' }).click(); evidence.mobileMenuExpanded = (await page .getByRole('button', { name: 'Fechar menu' }) .getAttribute('aria-expanded')) === 'true'; await page.keyboard.press('Tab'); evidence.keyboardFocusVisible = await page.evaluate( () => document.activeElement?.tagName !== 'BODY', ); await page.screenshot({ path: path.join(out, 'investmoz-guide-mobile-2026-10-04.png'), fullPage: true, }); evidence.pageErrors = errors; await browser.close(); fs.writeFileSync( path.join(out, 'investmoz-catalogue-guide-browser-2026-10-04.json'), JSON.stringify(evidence, null, 2) + '\n', ); console.log(JSON.stringify(evidence, null, 2));