fix: defectos encontrados por la suite de extremo a extremo
La primera corrida completa dio 57 fallas. Solo una parte eran del producto; el resto era instrumentación mal escrita. Queda en 261 en verde. Defectos del producto: - La atribución de campaña se perdía. La captura vivía en un efecto de React, así que si la persona llegaba con parámetros de campaña y navegaba antes de hidratar, el primer toque nunca se guardaba y utmCampaign llegaba vacío al CRM. Ahora se captura en un script en línea que corre al analizar el documento. - Siete páginas sin og:image: una página que declara su propio openGraph no hereda las imágenes del layout, las sobreescribe. Solo se nota al compartir el enlace. Se resuelve con una función compartida en seo.ts. - El panel no se podía usar desde Safari por HTTP: la cookie Secure no se guarda y el acceso falla en silencio. Queda explícito y documentado. - El panel se corría de lado en móvil: la cabecera no cabía a 375px. - Las tarjetas de perfil del acceso se cortaban en móvil. Un elemento de rejilla nace con min-width auto y no puede encogerse bajo su contenido. Era además el origen de un desplazamiento que se arrastraba al tablero. - Las tablas accesibles de las gráficas volvían desplazable la página: sr-only fija width 1px y el algoritmo de tablas lo ignora. - El campo de búsqueda del catálogo no tenía nombre accesible. - Límite de tasa de 5 envíos por IP: bloqueaba una demostración en vivo con varias personas en la misma red. Configurable, ahora 20. - El salto de contenido pasa de left:-9999px a recorte de 1px. Instrumentación corregida, con la razón escrita en cada caso: - El desborde horizontal se mide intentando desplazar la página, no con documentElement.scrollWidth, que sobreinforma con contenedores propios. - La comprobación de accesibilidad respeta el árbol de accesibilidad. - El aislamiento entre roles se prueba pidiendo por URL una oportunidad ajena, no comparando folios entre listados paginados. - Fuera las esperas por networkidle, que agotaban el tiempo sin fallar. - Que el panel no esté enlazado se comprueba leyendo el HTML servido. - Safari no enfoca enlaces con Tab: declarado como del navegador. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
0e0331c1fc
commit
31ee899987
@@ -10,7 +10,11 @@ import { RUTAS_MUESTRA } from "./rutas";
|
||||
|
||||
for (const ruta of RUTAS_MUESTRA) {
|
||||
test(`accesibilidad básica en ${ruta}`, async ({ page }) => {
|
||||
await page.goto(ruta, { waitUntil: "networkidle" });
|
||||
await page.goto(ruta, { waitUntil: "domcontentloaded" });
|
||||
// las comprobaciones miden cajas, así que hace falta que el diseño ya haya
|
||||
// asentado; se espera una condición concreta en lugar de la red en reposo
|
||||
await page.locator("h1").first().waitFor({ state: "visible" });
|
||||
await page.waitForTimeout(400);
|
||||
|
||||
const problemas = await page.evaluate(() => {
|
||||
const fallas: string[] = [];
|
||||
@@ -48,6 +52,12 @@ for (const ruta of RUTAS_MUESTRA) {
|
||||
for (const el of Array.from(document.querySelectorAll<HTMLElement>("button, a"))) {
|
||||
const r = el.getBoundingClientRect();
|
||||
if (r.width === 0 && r.height === 0) continue;
|
||||
// Un elemento marcado como oculto al árbol de accesibilidad no aparece
|
||||
// para un lector de pantalla, así que no necesita nombre. Es el caso
|
||||
// legítimo de la imagen de una tarjeta que duplica el enlace del
|
||||
// título: se marca aria-hidden con tabindex -1 justo para no anunciar
|
||||
// dos veces el mismo destino. Exigirle nombre era un falso positivo.
|
||||
if (el.closest('[aria-hidden="true"]')) continue;
|
||||
const texto = (el.textContent ?? "").trim();
|
||||
const nombre = texto || el.getAttribute("aria-label") || el.getAttribute("title") || "";
|
||||
if (!nombre) {
|
||||
@@ -85,7 +95,17 @@ for (const ruta of RUTAS_MUESTRA) {
|
||||
});
|
||||
}
|
||||
|
||||
test("el salto al contenido funciona con teclado", async ({ page }) => {
|
||||
test("el salto al contenido funciona con teclado", async ({ page }, info) => {
|
||||
// Safari no lleva el foco a los enlaces con Tab salvo que la persona active
|
||||
// "Presionar Tab para resaltar cada elemento" en sus preferencias. Es
|
||||
// comportamiento del navegador, no del sitio, así que la comprobación no
|
||||
// aplica en WebKit: ahí el salto sigue existiendo y sigue siendo alcanzable
|
||||
// con las teclas de accesibilidad del sistema.
|
||||
test.skip(
|
||||
info.project.name.includes("webkit"),
|
||||
"En Safari el tabulador no enfoca enlaces por omisión; es del navegador, no del sitio.",
|
||||
);
|
||||
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
await page.keyboard.press("Tab");
|
||||
|
||||
@@ -97,7 +117,12 @@ test("el salto al contenido funciona con teclado", async ({ page }) => {
|
||||
expect(enfocado.href).toBe("#contenido");
|
||||
});
|
||||
|
||||
test("el foco es visible al navegar con teclado", async ({ page }) => {
|
||||
test("el foco es visible al navegar con teclado", async ({ page }, info) => {
|
||||
test.skip(
|
||||
info.project.name.includes("webkit"),
|
||||
"Misma razón: en Safari el tabulador no recorre los enlaces por omisión.",
|
||||
);
|
||||
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
for (let i = 0; i < 5; i++) await page.keyboard.press("Tab");
|
||||
|
||||
@@ -131,7 +156,7 @@ test("el widget de WhatsApp se cierra con Escape y devuelve el foco", async ({ p
|
||||
|
||||
test("se respeta la preferencia de movimiento reducido", async ({ page }) => {
|
||||
await page.emulateMedia({ reducedMotion: "reduce" });
|
||||
await page.goto("/", { waitUntil: "networkidle" });
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
|
||||
// con movimiento reducido, el contenido revelado debe estar visible, no atorado en opacidad 0
|
||||
const ocultos = await page.evaluate(() => {
|
||||
|
||||
Reference in New Issue
Block a user