¡Hola a todos!
Seguimos con la serie de Playwright. En esta entrada vamos a hablar de uno de los problemas más comunes que nos encontramos cuando empezamos a automatizar con Playwright: el locator getByRole no encuentra el elemento aunque está ahí delante de nuestras narices. El test se queda esperando y acaba fallando por timeout. ¿Por qué pasa esto? La respuesta tiene que ver con ARIA y los nombres accesibles. ¡Empezamos!
El problema clásico.
Imagina que tienes que automatizar una caja de búsqueda. Inspeccionas el HTML y ves esto:
| |
Lo lógico es escribir el locator usando el texto del placeholder:
| |
Pero el test falla. Así que pruebas:
| |
Y funciona perfectamente. Pero… ¿cómo? El placeholder pone claramente “Search docs”. ¿Dónde se ha ido el “docs”?
El nombre accesible y su cadena de resolución.
Cuando usamos getByRole con la opción name, Playwright no busca el texto del placeholder ni el id del elemento ni ningún atributo arbitrario. Lo que busca es el nombre accesible computado del elemento: el mismo nombre que un lector de pantalla anunciaría a una persona con discapacidad visual.
Este nombre accesible se resuelve siguiendo una cadena de prioridad estricta, de mayor a menor:
aria-labelledby: referencia el contenido de texto de otro elemento del DOM. Máxima prioridad.aria-label: proporciona directamente el texto del label como cadena.- Elemento
<label>con atributoforque apunte al input. placeholder/title: fallback de último recurso. Mínima prioridad.
En el ejemplo anterior, el input tenía aria-labelledby="docsearch-label". Ese atributo apuntaba a un elemento <label> en el DOM con el texto “Search”, no “Search docs”. Como aria-labelledby tiene la máxima prioridad, el placeholder="Search docs" quedaba completamente ignorado.
El placeholder estaba ahí a la vista, pero era irrelevante para cómo Playwright resolvía el nombre.
input no es un rol ARIA.
Antes de llegar al problema del nombre, hay otra confusión habitual. Al ver un <input>, el instinto es escribir:
| |
Esto no funciona. input es un tag HTML, no un rol ARIA. El método getByRole trabaja con roles ARIA, que son descripciones semánticas de lo que hace un elemento, no del tag que usa.
El navegador asigna automáticamente un rol ARIA implícito a cada elemento HTML. Los más comunes son:
Elemento HTMLRol ARIA implícito<input type="text">``textbox``<input type="search">``searchbox``<input type="checkbox">``checkbox``<input type="radio">``radio``<button>``button``<a href="...">``link``<h1> a <h6>``heading``<select>``combobox``<ul>``list
Cuando los desarrolladores crean componentes personalizados con <div> y <span>, los roles implícitos no existen y es donde ARIA cobra toda su importancia.
Cómo depurar: la pestaña Accessibility de DevTools.
Aquí viene el truco que nos ahorra mucho tiempo. Chrome DevTools tiene una forma de ver exactamente lo mismo que ve Playwright:
- Click derecho sobre el elemento → Inspeccionar.
- En DevTools, busca la pestaña Accessibility (puede estar oculta, haz click en
>>para encontrarla). - Mira Computed Properties → Name.
Para nuestro input de búsqueda, la pestaña mostraría algo así:
| |
La pestaña nos muestra toda la cadena de resolución: qué ha ganado y qué ha quedado sobreescrito (aparece tachado). Esta es tu fuente de verdad cada vez que getByRole no te encuentre el elemento que esperas.
Matching parcial vs. exacto.
Otro punto importante que suele pasar desapercibido: getByRole hace substring matching por defecto para la opción name. Esto significa que cualquiera de estos locators coincide con un elemento cuyo nombre accesible sea “Search docs”:
| |
Si queremos una coincidencia exacta, usamos la opción exact:
| |
Usar exact: true es recomendable para mayor robustez, especialmente cuando la página tiene varios elementos con nombres similares. El Pick Locator de Playwright sugiere { name: 'Search' } porque busca el match más corto que sea único en la página.
¿Qué es ARIA?
Si llevas un tiempo escribiendo tests de Playwright sin pensar demasiado en ARIA, aquí va un repaso rápido.
ARIA son las siglas de Accessible Rich Internet Applications. Es un conjunto de atributos HTML que describe los elementos de la interfaz para tecnologías asistivas como los lectores de pantalla. Un usuario con visión puede ver que un <div> estilado actúa como botón, pero un lector de pantalla no puede saberlo a menos que se lo digamos:
| |
ARIA proporciona tres categorías de atributos:
- Roles: qué es el elemento.
role="button",role="searchbox",role="dialog",role="navigation". - Properties: lo describen.
aria-label="Search",aria-labelledby="label-id",aria-placeholder="Escribe aquí". - States: condición actual.
aria-expanded="true",aria-checked="false",aria-disabled="true".
Los elementos HTML nativos tienen roles implícitos incorporados, por lo que normalmente no necesitamos añadir ARIA manualmente. Pero cuando los desarrolladores construyen componentes con <div> y <span>, ARIA se vuelve esencial tanto para la accesibilidad como para que nuestros tests funcionen.
Aplicándolo a un Page Object Model.
Veamos cómo encaja todo esto en un POM real en TypeScript:
| |
Y en el test:
| |
Un detalle importante: getHeading() devuelve un Locator de forma síncrona, no necesita async/await. El await va en expect().toHaveText(), que es donde Playwright hace el polling del DOM y reintenta hasta que la condición se cumple o se agota el timeout.
Checklist de depuración.
La próxima vez que getByRole no encuentre el elemento, repasa esta lista:
- ¿Estoy usando el rol correcto? Comprueba el rol ARIA, no el tag HTML.
<input type="search">essearchbox, noinput. - ¿Estoy usando el nombre correcto? Abre DevTools → pestaña Accessibility → revisa el Name computado. No asumas que coincide con el placeholder o el texto visible.
- ¿Hay un
aria-labelledbysobreescribiendo el nombre? Este es el gotcha más frecuente.aria-labelledbysiempre gana sobrearia-label,<label>yplaceholder. - ¿El elemento es visible? Algunos elementos solo aparecen tras una interacción, como hacer click en un botón para abrir un modal o un desplegable.
- ¿Necesito
exact: true? Si el substring matching está pillando el elemento equivocado, usaexact: truepara forzar la coincidencia exacta.
Conclusión.
getByRoleno busca por placeholder ni por texto visible: busca por el nombre accesible computado, que sigue una cadena de prioridad estricta.aria-labelledbytiene la máxima prioridad y sobreescribe todo lo demás, incluido elplaceholder.inputno es un rol ARIA: usa los roles implícitos comotextbox,searchbox,checkbox, etc.- La pestaña Accessibility de Chrome DevTools te muestra exactamente lo que ve Playwright: el nombre resuelto y qué atributos han ganado o sido descartados.
getByRolehace substring matching por defecto: usaexact: truepara coincidencias precisas.
Y hasta aquí nuestra entrada sobre los problemas más comunes con getByRole y los nombres accesibles en Playwright. Una vez que entiendes cómo funciona la cadena de resolución de ARIA, estos problemas dejan de ser un misterio. ¡Nos leemos en la siguiente entrada!
