Pruebas de Elementos Personalizados que Usan ElementInternals
Cómo Axe DevTools para Web lee semánticas ARIA desde ElementInternals, qué deben hacer tus componentes para exponerlas y qué lenguajes lo soportan.
Los componentes web pueden declarar sus semánticas de accesibilidad en JavaScript en lugar de en el marcado, llamando a attachInternals() y configurando propiedades como role y ariaLabel en el objeto ElementInternals devuelto. Un componente construido de esta manera no tiene atributos role o aria-* en el DOM, por lo que una herramienta de prueba que solo lee atributos ve un elemento sin semánticas en absoluto.
Axe DevTools para Web ahora lee estas semánticas. Esta página explica qué versiones lo soportan, qué deben hacer tus componentes para hacer visibles sus internos y cómo interactúan los internos con los atributos ya presentes en el elemento. Para información general sobre cómo seleccionar tu versión de axe-core, consulta Personalización de Reglas.
Lenguajes y Versiones Soportados
El soporte proviene del motor de pruebas de accesibilidad subyacente, por lo que está disponible dondequiera que la versión agrupada de axe-core sea 4.13.0 o superior.
| Lenguaje | Versión que añadió soporte | Axe-core agrupado |
|---|---|---|
| C# | 4.13.0 | 4.13.0 |
| Python | 4.13.0 | 4.13.0 |
| Ruby | 4.13.0 | 4.13.0 (a través de axe-core-api 4.13.0) |
| Java | 4.13.0 | 4.13.0 (a través de com.deque.html.axe-core 4.13.0) |
| Node.js y la CLI | 4.13.0 | 4.13.0 |
No hay nada que activar. En cada lenguaje mencionado anteriormente, el motor de pruebas de accesibilidad se ejecuta directamente en la página bajo prueba, por lo que lee ElementInternals en cada análisis sin ninguna opción, bandera o cambio de configuración. Si tus componentes ya siguen el protocolo descrito a continuación, actualizar es lo único necesario.
Exponiendo ElementInternals a Axe DevTools
ElementInternals es privado por diseño: JavaScript no proporciona una API para leer los internos de otro elemento. Una herramienta de prueba solo puede ver el objeto si tu componente lo publica deliberadamente. Los autores de componentes hacen esto a través de un protocolo de la comunidad, y Axe DevTools soporta cada forma de ese protocolo.
La forma recomendada es un WeakMap global, que mantiene el objeto fuera del propio elemento:
customElements.define(
'my-custom-button',
class MyCustomButton extends HTMLElement {
constructor() {
super();
const internals = this.attachInternals();
internals.role = 'button';
globalThis._elementInternals ??= new WeakMap();
globalThis._elementInternals.set(this, internals);
}
}
);Asignar el objeto a una propiedad en el elemento también funciona:
customElements.define(
'my-custom-button',
class MyCustomButton extends HTMLElement {
constructor() {
super();
this._internals = this.attachInternals();
this._internals.role = 'button';
}
}
);Además del mapa globalThis._elementInternals, Axe DevTools busca el objeto bajo cualquiera de estos nombres en el elemento:
_internals(recomendado)internalsinternals_Symbol('internals')Symbol('privateInternals')
Un componente que mantiene sus internos genuinamente privados es invisible para Axe DevTools, y esta es la razón más común por la que no se ve ningún cambio después de la actualización. Dos casos a revisar:
- Campos de clase privados.
this.#internals = this.attachInternals()no se puede leer desde fuera de la clase, por lo que ninguna herramienta de prueba puede verlo. Varias bibliotecas de componentes usan este patrón. - Getters. Una propiedad definida como un accesor en lugar de un valor simple es omitida, incluso si está nombrada
_internals. Asigna el objeto directamente.
En ambos casos, la solución pertenece al componente, no a tu configuración de prueba. Publica el objeto a través de una de las formas mencionadas, o espera que el elemento sea evaluado como si no tuviera semánticas ARIA.
Probar que tus componentes son visibles para Axe DevTools no requiere un análisis. En la consola del navegador en una página que use el componente, globalThis._elementInternals?.get(document.querySelector('my-custom-button')) o document.querySelector('my-custom-button')._internals debería devolver un objeto ElementInternals en lugar de undefined.
Lo que Lee Axe DevTools
Una vez que el objeto es visible, Axe DevTools lee la propiedad role y las propiedades ARIA que corresponden a los atributos aria-*, como ariaLabel para aria-label, ariaDescription para aria-description, y ariaLabelledByElements para aria-labelledby. Estos valores alimentan las mismas reglas que habrían corrido contra los atributos equivalentes, así que un elemento personalizado que configura role y ariaLabel a través de internos es verificado para un nombre accesible de la misma manera que uno que configura role y aria-label en el marcado.
Precedencia
Los valores presentes en el DOM siempre ganan. Esto significa que añadir atributos a tu marcado es una manera confiable de anular lo que un componente declara internamente, y que los internos nunca enmascaran un problema visible en el DOM.
- Rol. El
roledeElementInternalses tratado como el rol implícito del elemento, el mismo estatus que el rol incorporado de un elemento nativo. Un atributoroleexplícito en el elemento lo sobrescribe. - Valores ARIA. Cada valor se resuelve primero desde el atributo, luego la propiedad correspondiente en el elemento, y solo después desde
ElementInternals.
Limitaciones
El soporte es real pero aún no está completo, y las lagunas son importantes de conocer antes de interpretar un análisis.
Las reglas que seleccionan atributos no coinciden con elementos exclusivamente internos. Varias reglas identifican los elementos a los que se aplican con un selector CSS contra el DOM. Debido a que un elemento cuyas características provienen solo de ElementInternals no tiene un atributo role, esas reglas nunca se aplican a él. Por ejemplo, aria-required-attr selecciona [role], y aria-command-name selecciona [role="link"], [role="button"], [role="menuitem"]. Un elemento personalizado que declara role = 'button' a través de internals no es evaluado por ninguna de las reglas. Las reglas que resuelven el rol en lugar de coincidir con un selector, como aquellas que calculan un nombre accesible, sí se aplican.
Los valores de roles no son validados. Un role establecido a través de internals se usa tal como está. Un valor inválido o mal escrito no se reporta de la misma manera que lo haría un atributo role inválido.
Algunas reglas solo se aplican parcialmente. Las reglas que inspeccionan la relación de un elemento con sus hijos o ancestros, como aria-required-children, pueden no evaluar completamente un elemento exclusivamente interno.
Debido a la primera limitación en particular, espere que un componente que declara sus características solo a través de internals produzca menos hallazgos que el marcado equivalente lo haría, en lugar de más. Un análisis limpio de tal componente es una evidencia más débil que un análisis limpio de uno que use atributos.
Páginas Relacionadas
- Personalizar Reglas para seleccionar la versión de axe-core que usan sus análisis.
- Acerca de Axe DevTools para API Web para la lista completa de idiomas y marcos compatibles.
- Documentación de la API de C#, Descripción general de Python, Uso Avanzado de la API con Ruby, Descripción general de la API de Java y Pruebas basadas en Node.js y del Navegador para las vinculaciones de lenguaje que lo soportan.
