import AxeBuilder from "@axe-core/playwright"; import { expect, test } from "@playwright/test"; import { DESKTOP, isDevServer, PHONE, sitemapPaths } from "./helpers"; /** * (a) Todas as rotas do /sitemap.xml respondem 200, sem erro de console e sem rolagem horizontal, no computador * (1440x900) e no celular (390x844). * (b) O axe não acha violação serious ou critical em nenhuma delas, nos mesmos dois tamanhos. * As rotas vêm do próprio sitemap: uma página nova entra no teste sozinha. * * Erro de console é falha, sem exceção em produção. Única exceção, só quando o BASE_URL é um servidor de * desenvolvimento (`next dev`, detectado por isDevServer): o aviso do React "Each child in a list should have a * unique "key" prop", que só existe no React de desenvolvimento (o build de produção não o emite) e que a pilha * atribui a `OuterLayoutRouter`, do roteador do próprio Next, e não a um componente do site. Ele não é * esquecido: cada rota em que aparece fica anotada no relatório do teste ("aviso do next dev ignorado"). Qualquer * outro erro de console reprova nos dois modos. */ const DEV_ONLY_KEY_WARNING = /Each child in a list should have a unique "key" prop/; const VIEWPORTS = [ { name: "computador 1440x900", size: DESKTOP }, { name: "celular 390x844", size: PHONE }, ] as const; for (const { name, size } of VIEWPORTS) { test.describe(`rotas do sitemap, ${name}`, () => { test.use({ viewport: size }); test("respondem 200, sem erro de console e sem rolagem horizontal", async ({ page, request, }) => { const paths = await sitemapPaths(request); expect(paths.length, "rotas no sitemap").toBeGreaterThan(10); const dev = await isDevServer(request); const ignoredWarnings = new Set(); for (const path of paths) { await test.step(path, async () => { const problems: string[] = []; const onConsole = ( message: import("@playwright/test").ConsoleMessage, ) => { if (message.type() !== "error") return; if (dev && DEV_ONLY_KEY_WARNING.test(message.text())) { ignoredWarnings.add(path); return; } problems.push(`console.error: ${message.text()}`); }; const onPageError = (error: Error) => problems.push(`erro não tratado: ${error.message}`); page.on("console", onConsole); page.on("pageerror", onPageError); const response = await page.goto(path, { waitUntil: "load" }); // dá tempo de a hidratação e das primeiras animações acontecerem (e de errarem) await page.waitForTimeout(800); page.off("console", onConsole); page.off("pageerror", onPageError); expect.soft(response?.status(), `${path}: status`).toBe(200); expect.soft(problems, `${path}: console`).toEqual([]); const overflow = await page.evaluate( () => Math.max( document.documentElement.scrollWidth, document.body.scrollWidth, ) - document.documentElement.clientWidth, ); expect .soft(overflow, `${path}: px de rolagem horizontal`) .toBeLessThanOrEqual(0); }); } for (const path of ignoredWarnings) test.info().annotations.push({ type: "aviso do next dev ignorado", description: `${path}: Each child in a list should have a unique "key" prop`, }); }); }); } for (const { name, size } of VIEWPORTS) { test.describe(`acessibilidade (axe), ${name}`, () => { test.use({ viewport: size }); test("nenhuma violação serious ou critical em nenhuma rota do sitemap", async ({ page, request, }) => { const paths = await sitemapPaths(request); for (const path of paths) { await test.step(path, async () => { await page.goto(path, { waitUntil: "load" }); await page.waitForTimeout(800); const { violations } = await new AxeBuilder({ page }).analyze(); const serious = violations .filter( ({ impact }) => impact === "serious" || impact === "critical", ) .map( ({ id, impact, nodes }) => `${id} (${impact}): ${nodes.length} elemento(s), por exemplo ${nodes[0]?.target.join(" ")}`, ); expect.soft(serious, `${path}: axe`).toEqual([]); }); } }); }); }