Una compuerta de accesibilidad de dos segundos que corre en cada push
Ya tenía un arnés de vitest que renderiza mis componentes de Svelte y simula las llamadas hacia Rust. Agregar chequeos de accesibilidad resultó casi gratis: apuntar axe-core a cada pantalla renderizada y verificar que no encuentre nada. Así que ahora cada push corre axe sobre el asistente de onboarding, el dashboard tanto vacío como lleno, y los dos diálogos, y falla la build si a cualquiera de ellos le aparece una etiqueta faltante o un atributo ARIA roto. Tarda unos dos segundos.
Lo limité a las reglas WCAG A y AA reales a propósito. Axe trae un montón de reglas de “mejores prácticas” además del estándar, y algunas de ellas, como “todo contenido debe estar dentro de una región landmark,” se disparan constantemente cuando se renderiza un solo diálogo aislado en vez de una página completa. Eso es ruido, no un problema real, así que configuré axe para que corriera solo las reglas etiquetadas como WCAG. La compuerta marca las cosas que están realmente mal y se queda callada ante las cosas que solo están mal fuera de contexto.
Acá va la parte con la que quiero ser honesto, porque es fácil sobrevender esto. Las pruebas corren en jsdom, que construye un DOM pero nunca hace layout ni pinta nada. Eso significa que el primer chequeo de accesibilidad en el que todo el mundo piensa, el contraste de color, no puede correr acá. Axe lo intenta, no logra conseguir un canvas para medir píxeles, y silenciosamente reporta el resultado como “incomplete” en vez de aprobado o fallado. Así que una regresión de contraste pasa directo por esta compuerta. Lo mismo pasa con los estilos de focus-visible. Lo que realmente tengo es una compuerta estructural: nombres, roles, etiquetas, encabezados, asociaciones de formularios. Valiosa, y la mayor parte de lo que se rompe día a día, pero no el panorama completo.
En vez de fingir lo contrario, dejé escrita la brecha en el comentario del código y archivé la otra mitad contra mi issue de modo oscuro. El contraste realmente empieza a romperse cuando se cambian los colores del tema, y eso es exactamente lo que es el modo oscuro. Cuando lo construya, voy a agregar una pasada de axe en navegador real que revise el contraste en ambos temas. La herramienta correcta, en el momento correcto.
Dos trampas pequeñas en el camino. Los tipos de TypeScript de la librería de matchers estaban escritos para una versión más vieja de vitest y simplemente no se conectaban con la actual, así que mi verificador de tipos insistía en que la aserción no existía aunque corría bien. Una declaración de tipos de cuatro líneas, propia, lo arregló. Y antes de confiar en un resultado en verde, le di a axe un fragmento deliberadamente roto, una imagen sin texto alternativo y un botón vacío, para confirmar que efectivamente reportaba violaciones. Una prueba de accesibilidad que pasa y que no puede fallar es peor que no tener prueba, porque hace creer que algo está cubierto cuando no lo está.
El resultado bueno: mis pantallas existentes salieron limpias, así que no hubo nada que arreglar, solo una compuerta para mantenerlas así.
Lecturas relacionadas
Arreglar el error de manejo de foco de Greenhouse me enseñó la diferencia entre un diálogo y una página
El ticket decía que había que restaurar el foco al elemento que lo disparó. Ese elemento ya no existe, se destruyó en el instante en que se abrió la vista. El patrón correcto era el de una navegación de página, no el de un modal.
El botón de cancelar que no cancelaba
Escape parecía funcionar en cada prueba manual. Escribir la prueba de regresión obligó a ver el orden real de eventos, y el blur perdido que guardaba lo que debía descartarse.
Seis ítems pequeños de UI, y los dos casi-desastres escondidos adentro
Un bloque de CSS que grep decía que estaba muerto pero del que dependía una prueba, y una sola línea de localStorage que rompió treinta y nueve pruebas sin relación por culpa de una actualización de Node.