418 lines
16 KiB
JavaScript
418 lines
16 KiB
JavaScript
// Public React/Inertia screens with synthetic sectors and local assets; no database.
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { createRequire } from 'node:module';
|
|
|
|
const require = createRequire(import.meta.url);
|
|
const { chromium } = require(
|
|
process.env.SECTORS_QA_PLAYWRIGHT ||
|
|
'C:/Users/jorge/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules/playwright',
|
|
);
|
|
const root = process.cwd();
|
|
const base = (
|
|
process.env.SECTORS_QA_VITE || fs.readFileSync('public/hot', 'utf8')
|
|
).trim();
|
|
assert.equal(new URL(base).hostname, '127.0.0.1');
|
|
const dir = path.join(root, 'outputs/home-sectors-20261023');
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
const sectors = [
|
|
[
|
|
'agronegocio',
|
|
'Agronegócio',
|
|
'Produção, agroprocessamento, irrigação e cadeias de valor agrícolas.',
|
|
],
|
|
[
|
|
'turismo',
|
|
'Turismo',
|
|
'Hotelaria, turismo costeiro, cultural e ecológico.',
|
|
],
|
|
[
|
|
'infraestruturas',
|
|
'Infraestruturas',
|
|
'Logística, transportes, parques industriais e energia.',
|
|
],
|
|
[
|
|
'economia-azul',
|
|
'Economia Azul',
|
|
'Pesca, aquacultura, portos e logística marítima.',
|
|
],
|
|
[
|
|
'mercado-financeiro',
|
|
'Mercado Financeiro',
|
|
'Fintech, capital de risco e instrumentos financeiros.',
|
|
],
|
|
['mineracao', 'Mineração', null],
|
|
['energia-especifica', 'Energia', null],
|
|
['imobiliario', 'Imobiliário', null],
|
|
['bens-de-consumo-e-franquias', 'Bens de Consumo e Franquias', null],
|
|
['tecnologias-de-informacao', 'Tecnologias de Informação', null],
|
|
].map(([slug, name, description], index) => ({
|
|
id: index + 1,
|
|
slug,
|
|
name,
|
|
shortDescription: description,
|
|
short_description: description,
|
|
description,
|
|
opportunitiesCount: index === 4 ? 4 : 0,
|
|
opportunities_count: index === 4 ? 4 : 0,
|
|
url: `/sectores/${slug}`,
|
|
specializations:
|
|
index === 4
|
|
? [
|
|
{
|
|
id: 901,
|
|
name: 'Mercado Financeiro — Fintech',
|
|
slug: 'fintech',
|
|
url: '/sectores/mercado-financeiro/fintech',
|
|
},
|
|
]
|
|
: [],
|
|
}));
|
|
const fixturePath = '/outputs/home-sectors-20261023/fixture.html';
|
|
const html =
|
|
'<!doctype html><html lang="pt"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Sectores QA</title></head><body><div id="app"></div><script type="module" src="/outputs/home-sectors-20261023/fixture.tsx"></script></body></html>';
|
|
fs.writeFileSync(path.join(dir, 'fixture.html'), html);
|
|
fs.writeFileSync(
|
|
path.join(dir, 'fixture.tsx'),
|
|
`
|
|
import React from 'react';
|
|
import {createRoot} from 'react-dom/client';
|
|
import {createInertiaApp} from '@inertiajs/react';
|
|
import Portal from '../../resources/js/pages/PortalInvestidor';
|
|
import Services from '../../resources/js/pages/portal/services';
|
|
import Navigation from '../../resources/js/layouts/portal-navigation';
|
|
import '../../resources/css/app.css';
|
|
const sectors=${JSON.stringify(sectors)};
|
|
const url=new URL(location.href),catalogue=url.pathname==='/sectores',detail=url.pathname.startsWith('/sectores/');
|
|
const selected=sectors.find(sector=>sector.url===url.pathname)||{id:901,name:'Fintech',slug:'fintech',description:null,short_description:null};
|
|
const props={auth:{user:null,canManagePortal:false},countries:[],essentialResources:[],...(catalogue?{section:'sectors',sectors:url.searchParams.has('empty')?[]:sectors}:detail?{section:'sector',sector:selected,opportunities:[]}:{} )};
|
|
await createInertiaApp({page:{component:catalogue||detail?'portal/services':'PortalInvestidor',props,url:url.pathname+url.search,version:null},resolve:()=>catalogue||detail?Services:Portal,layout:()=>Navigation,setup({el,App,props}){createRoot(el).render(<App {...props}/>);}});
|
|
`,
|
|
);
|
|
const browser = await chromium.launch({
|
|
headless: true,
|
|
...(process.env.SECTORS_QA_CHROMIUM
|
|
? { executablePath: process.env.SECTORS_QA_CHROMIUM }
|
|
: {}),
|
|
});
|
|
// Full navigations need Vite's transformed HTML, including the React preamble.
|
|
const fixtureResponse = await fetch(base + fixturePath);
|
|
assert.ok(fixtureResponse.ok);
|
|
const navigationHtml = await fixtureResponse.text();
|
|
const errors = [],
|
|
results = [],
|
|
navigation = [];
|
|
let currentPage;
|
|
async function openCase(width, { empty = false, fail = 'none' } = {}) {
|
|
const context = await browser.newContext({
|
|
viewport: { width, height: 1000 },
|
|
reducedMotion: 'reduce',
|
|
});
|
|
await context.routeWebSocket('**/*', () => {});
|
|
await context.route('**/*', (route) => {
|
|
const url = new URL(route.request().url());
|
|
if (url.origin !== base) return route.abort();
|
|
if (url.pathname.startsWith('/images/')) {
|
|
if (
|
|
url.pathname.startsWith('/images/sectors/') &&
|
|
(fail === 'all' ||
|
|
(fail === 'primary' &&
|
|
!url.pathname.includes('outros-sectores')))
|
|
)
|
|
return route.fulfill({ status: 404 });
|
|
const file = path.resolve(root, 'public', '.' + url.pathname);
|
|
assert.ok(
|
|
file.startsWith(path.join(root, 'public', 'images') + path.sep),
|
|
);
|
|
return fs.existsSync(file)
|
|
? route.fulfill({ path: file })
|
|
: route.fulfill({ status: 404 });
|
|
}
|
|
if (url.pathname === '/api/v1/home')
|
|
return route.fulfill({
|
|
json: {
|
|
success: true,
|
|
data: {
|
|
sectors: empty ? [] : sectors,
|
|
featuredOpportunities: [],
|
|
stats: {
|
|
opportunities: 4,
|
|
sectors: empty ? 0 : 10,
|
|
provinces: 3,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
if (url.pathname.startsWith('/api/'))
|
|
return route.fulfill({ json: { data: [] } });
|
|
if (
|
|
route.request().isNavigationRequest() &&
|
|
(url.pathname === '/sectores' ||
|
|
url.pathname.startsWith('/sectores/') ||
|
|
url.pathname === '/')
|
|
) {
|
|
navigation.push(url.pathname);
|
|
return route.fulfill({
|
|
contentType: 'text/html',
|
|
body: navigationHtml,
|
|
});
|
|
}
|
|
return route.continue();
|
|
});
|
|
const page = await context.newPage();
|
|
currentPage = page;
|
|
page.on('pageerror', (error) => errors.push(error.message));
|
|
await page.goto(base + fixturePath, {
|
|
waitUntil: 'networkidle',
|
|
timeout: 60000,
|
|
});
|
|
return { context, page };
|
|
}
|
|
async function loadImages(region) {
|
|
for (const image of await region.locator('img').all()) {
|
|
await image.scrollIntoViewIfNeeded();
|
|
await region
|
|
.page()
|
|
.waitForFunction(
|
|
(node) => node.complete && node.naturalWidth > 0,
|
|
await image.elementHandle(),
|
|
{ timeout: 15000 },
|
|
);
|
|
}
|
|
}
|
|
try {
|
|
for (const width of [320, 390, 768, 1024, 1440, 1920]) {
|
|
const { context, page } = await openCase(width);
|
|
const section = page.getByRole('region', {
|
|
name: 'Sectores de investimento',
|
|
exact: true,
|
|
});
|
|
await section.waitFor();
|
|
await loadImages(section);
|
|
assert.equal(await section.getByRole('article').count(), 5);
|
|
assert.deepEqual(
|
|
await section.locator('h3').allTextContents(),
|
|
sectors.slice(0, 5).map((s) => s.name),
|
|
);
|
|
assert.equal(
|
|
await section
|
|
.getByRole('link', { name: /^Ler mais sobre / })
|
|
.count(),
|
|
5,
|
|
);
|
|
assert.ok(
|
|
await section.getByText('0 oportunidades', { exact: true }).count(),
|
|
);
|
|
assert.equal(
|
|
await section.getByText('4 oportunidades', { exact: true }).count(),
|
|
1,
|
|
);
|
|
assert.equal(
|
|
await section
|
|
.getByRole('link', { name: 'Especialização: Fintech' })
|
|
.getAttribute('href'),
|
|
'/sectores/mercado-financeiro/fintech',
|
|
);
|
|
const headings = await page.locator('main h2').evaluateAll((nodes) =>
|
|
nodes.map((node) => ({
|
|
title: node.textContent.trim(),
|
|
size: getComputedStyle(node).fontSize,
|
|
weight: getComputedStyle(node).fontWeight,
|
|
color: getComputedStyle(node).color,
|
|
lineHeight: getComputedStyle(node).lineHeight,
|
|
})),
|
|
);
|
|
assert.equal(headings.length, 6);
|
|
headings.forEach((heading) => {
|
|
assert.equal(heading.color, 'rgb(237, 28, 36)');
|
|
assert.equal(heading.size, '24px');
|
|
assert.equal(heading.weight, '700');
|
|
assert.equal(heading.lineHeight, '32px');
|
|
});
|
|
const homeColumns = await section
|
|
.locator(':scope > div')
|
|
.first()
|
|
.locator(':scope > div')
|
|
.evaluate(
|
|
(node) =>
|
|
getComputedStyle(node).gridTemplateColumns.split(' ')
|
|
.length,
|
|
);
|
|
assert.equal(homeColumns, width >= 1024 ? 5 : width >= 640 ? 3 : 2);
|
|
assert.equal(
|
|
await page.evaluate(
|
|
() => document.documentElement.scrollWidth > innerWidth,
|
|
),
|
|
false,
|
|
);
|
|
await section.screenshot({
|
|
path: path.join(dir, `home-sectors-${width}.png`),
|
|
});
|
|
if (width === 1440)
|
|
await page.screenshot({
|
|
path: path.join(dir, 'homepage-1440.png'),
|
|
fullPage: true,
|
|
});
|
|
const more = section.getByRole('link', {
|
|
name: 'Ver mais sectores',
|
|
exact: true,
|
|
});
|
|
assert.equal(await more.getAttribute('href'), '/sectores');
|
|
await more.focus();
|
|
await page.keyboard.press('Enter');
|
|
await page.waitForURL(base + '/sectores');
|
|
await page
|
|
.getByRole('heading', {
|
|
name: 'Sectores de investimento',
|
|
exact: true,
|
|
})
|
|
.waitFor();
|
|
const catalogue = page.getByRole('region', {
|
|
name: 'Todos os sectores de investimento',
|
|
});
|
|
await loadImages(catalogue);
|
|
assert.equal(await catalogue.getByRole('article').count(), 10);
|
|
assert.deepEqual(
|
|
await catalogue.locator('h3').allTextContents(),
|
|
sectors.map((s) => s.name),
|
|
);
|
|
assert.equal(
|
|
await catalogue
|
|
.getByRole('link', { name: /^Ler mais sobre / })
|
|
.count(),
|
|
10,
|
|
);
|
|
assert.equal(
|
|
await page.evaluate(
|
|
() => document.documentElement.scrollWidth > innerWidth,
|
|
),
|
|
false,
|
|
);
|
|
await catalogue.screenshot({
|
|
path: path.join(dir, `catalogue-${width}.png`),
|
|
});
|
|
if (width === 1440) {
|
|
for (const sector of sectors) {
|
|
await catalogue
|
|
.getByRole('link', {
|
|
name: `Ler mais sobre ${sector.name}`,
|
|
exact: true,
|
|
})
|
|
.click();
|
|
await page.waitForURL(base + sector.url);
|
|
await page
|
|
.getByRole('heading', { name: sector.name, exact: true })
|
|
.waitFor();
|
|
await page.goto(base + '/sectores', {
|
|
waitUntil: 'networkidle',
|
|
});
|
|
await catalogue.waitFor();
|
|
}
|
|
await catalogue
|
|
.getByRole('link', { name: 'Especialização: Fintech' })
|
|
.click();
|
|
await page.waitForURL(
|
|
base + '/sectores/mercado-financeiro/fintech',
|
|
);
|
|
await page
|
|
.getByRole('heading', { name: 'Fintech', exact: true })
|
|
.waitFor();
|
|
await page.goto(base + '/sectores', { waitUntil: 'networkidle' });
|
|
await catalogue
|
|
.getByRole('link', { name: 'Voltar ao início' })
|
|
.click();
|
|
await page.waitForURL(base + '/');
|
|
await page
|
|
.getByRole('region', {
|
|
name: 'Sectores de investimento',
|
|
exact: true,
|
|
})
|
|
.waitFor();
|
|
}
|
|
results.push({
|
|
width,
|
|
homeSectors: 5,
|
|
catalogueSectors: 10,
|
|
homeColumns,
|
|
headings,
|
|
status: 'passed',
|
|
});
|
|
await context.close();
|
|
}
|
|
for (const fail of ['primary', 'all']) {
|
|
const { context, page } = await openCase(390, { fail });
|
|
const section = page.getByRole('region', {
|
|
name: 'Sectores de investimento',
|
|
exact: true,
|
|
});
|
|
await section.waitFor();
|
|
for (const article of await section.getByRole('article').all())
|
|
await article.scrollIntoViewIfNeeded();
|
|
await page.waitForFunction((mode) => {
|
|
const cards = [
|
|
...document.querySelectorAll(
|
|
'#home-sectors-heading ~ div article',
|
|
),
|
|
];
|
|
return (
|
|
cards.length === 5 &&
|
|
cards.every((card) =>
|
|
mode === 'all'
|
|
? !card.querySelector('img')
|
|
: card.querySelector('img')?.complete &&
|
|
card.querySelector('img')?.naturalWidth > 0 &&
|
|
card
|
|
.querySelector('img')
|
|
?.currentSrc.includes('outros-sectores'),
|
|
)
|
|
);
|
|
}, fail);
|
|
assert.equal(
|
|
await section
|
|
.getByRole('link', { name: /^Ler mais sobre / })
|
|
.count(),
|
|
5,
|
|
);
|
|
await section.screenshot({
|
|
path: path.join(dir, `fallback-${fail}.png`),
|
|
});
|
|
results.push({ fail, status: 'passed' });
|
|
await context.close();
|
|
}
|
|
const { context, page } = await openCase(390, { empty: true });
|
|
const section = page.getByRole('region', {
|
|
name: 'Sectores de investimento',
|
|
exact: true,
|
|
});
|
|
await section.getByText('Não existem sectores publicados.').waitFor();
|
|
assert.equal(await section.getByRole('article').count(), 0);
|
|
await page.goto(base + '/sectores?empty=1', { waitUntil: 'networkidle' });
|
|
await page.getByText('Não existem sectores publicados.').waitFor();
|
|
results.push({ emptyCatalogue: true, status: 'passed' });
|
|
await context.close();
|
|
assert.deepEqual(errors, []);
|
|
fs.writeFileSync(
|
|
path.join(dir, 'qa-results.json'),
|
|
JSON.stringify(
|
|
{ results, errors, navigation, databaseAccess: false },
|
|
null,
|
|
2,
|
|
),
|
|
);
|
|
console.log(JSON.stringify({ cases: results.length, errors, navigation }));
|
|
} catch (error) {
|
|
if (currentPage && !currentPage.isClosed())
|
|
await Promise.all([
|
|
Promise.resolve(
|
|
currentPage.screenshot({ path: path.join(dir, 'failure.png') }),
|
|
),
|
|
Promise.resolve(currentPage.content()).then((html) =>
|
|
fs.writeFileSync(path.join(dir, 'failure.html'), html),
|
|
),
|
|
]);
|
|
throw error;
|
|
} finally {
|
|
await browser.close();
|
|
}
|