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();
});