import { expect, test } from "@playwright/test"; import { RUTAS_MUESTRA, TODAS_LAS_RUTAS } from "./rutas"; /** * Humo: cada ruta pública responde, tiene una sola cabecera principal, título * propio y los metadatos que la auditoría de marca señaló como ausentes en el * sitio original. */ /** * El barrido de las 45 rutas corre en un solo proyecto. Que una ruta responda * 200 y traiga un `

` lo decide el servidor, no el motor del navegador, así * que repetirlo cuatro veces solo alargaba la suite. Lo que sí es sensible al * motor —responsividad, interacción, foco— vive en las otras suites y ahí sí * corre en WebKit. */ test.describe("rutas públicas responden", () => { for (const ruta of TODAS_LAS_RUTAS) { test(`200 y contenido en ${ruta}`, async ({ page }, info) => { test.skip( info.project.name !== "escritorio-chromium", "El barrido completo de rutas corre en un solo proyecto; ver la nota de arriba.", ); const res = await page.goto(ruta, { waitUntil: "domcontentloaded" }); expect(res?.status(), `${ruta} debe responder 200`).toBe(200); // exactamente un h1: si hay dos, la jerarquía del documento está mal const h1 = page.locator("h1"); await expect(h1, `${ruta} debe tener un solo h1`).toHaveCount(1); await expect(h1).not.toBeEmpty(); const titulo = await page.title(); expect(titulo.length, `${ruta} necesita un con sustancia`).toBeGreaterThan(12); }); } }); test.describe("metadatos que faltaban en el sitio original", () => { for (const ruta of RUTAS_MUESTRA) { test(`description y open graph en ${ruta}`, async ({ page }) => { await page.goto(ruta, { waitUntil: "domcontentloaded" }); const desc = await page.locator('meta[name="description"]').getAttribute("content"); expect(desc, `${ruta} sin meta description`).toBeTruthy(); expect(desc!.length, `${ruta}: description demasiado corta`).toBeGreaterThan(50); expect(desc!.length, `${ruta}: description demasiado larga para el snippet`).toBeLessThan(300); const ogTitulo = await page.locator('meta[property="og:title"]').getAttribute("content"); expect(ogTitulo, `${ruta} sin og:title`).toBeTruthy(); const ogImagen = await page.locator('meta[property="og:image"]').first().getAttribute("content"); expect(ogImagen, `${ruta} sin og:image`).toBeTruthy(); const canonica = await page.locator('link[rel="canonical"]').getAttribute("href"); expect(canonica, `${ruta} sin canónica`).toBeTruthy(); }); } }); test("idioma declarado en español de México", async ({ page }) => { await page.goto("/"); await expect(page.locator("html")).toHaveAttribute("lang", /^es/); }); test("sitemap lista rutas reales y excluye el panel", async ({ page }) => { const res = await page.goto("/sitemap.xml"); expect(res?.status()).toBe(200); const xml = await page.content(); expect(xml).toContain("/catalogo"); expect(xml).toContain("/soluciones/"); expect(xml, "el panel no debe aparecer en el sitemap").not.toContain("/admin"); }); test("robots prohíbe el panel y la API", async ({ request }) => { const res = await request.get("/robots.txt"); expect(res.status()).toBe(200); const txt = await res.text(); expect(txt).toMatch(/Disallow:\s*\/admin/i); expect(txt).toMatch(/Sitemap:/i); }); test("iconos y tarjeta social se generan", async ({ request }) => { for (const ruta of ["/icon", "/opengraph-image"]) { const res = await request.get(ruta); expect(res.status(), `${ruta} debe responder`).toBeLessThan(400); expect(res.headers()["content-type"], `${ruta} debe ser imagen`).toContain("image"); } }); test("sonda de salud responde", async ({ request }) => { const res = await request.get("/api/salud"); expect(res.status()).toBe(200); const json = (await res.json()) as { ok: boolean }; expect(json.ok).toBe(true); }); test("una ruta inexistente devuelve 404 con salidas útiles", async ({ page }) => { const res = await page.goto("/producto/esto-no-existe-jamas"); expect(res?.status()).toBe(404); // un 404 sin camino de regreso es un callejón await expect(page.getByRole("link", { name: /cat[áa]logo|soluciones|inicio|contacto/i }).first()).toBeVisible(); });