Localizadores CSS avanzados para Cypress (u otras herramientas de automatización)

Idealmente, cada elemento a localizar en nuestras pruebas debería contener un atributo específico que lo identificara de forma unívoca, como por ejemplo data-testid o data-cy. Sin embargo, a veces es posible que no podamos añadir estos atributos, como cuando se utilizan componentes de terceros.

Por ello, no viene de más estar familiarizados con la localización de elementos mediante otras formas. La más común en Cypress es a través de CSS locators.

En este artículo no voy a entrar en detalles sobre cómo localizar elementos a través de su tagname o su clase, ya que hay cientos de artículos sobre cómo hacerlo, como por ejemplo este o este. En vez de eso, comentaré de un par de estrategias que nos permitirán seleccionar elementos de la forma más explícita posible en situaciones menos típicas.

Valores de atributos parciales.

Sea el siguiente código HTML:

<header>
  <nav>
    <ul>
      <li data-testid="header-menu-home-option">
          <span>Home</span>
      </li>
      <li data-testid="header-menu-cart-option">
          <span>Cart</span>
      </li>
      <li data-testid="header-menu-aboutus-option">
          <div>
              <span>About Us</span>
          </div>
      </li>
    </ul>
  </nav>
<header>
<main>
  <h1>This is my page</h1>
  <div>
    <span>Hello!</span>
  </div>
</main>

Cada elemento contiene un data-testid, por lo que es posible seleccionar cada uno de ellos de forma unívoca. No obstante, es posible que necesitemos seleccionar todos los list items utilizando un único selector para realizar algún tipo de comprobación (por ejemplo, comprobar que existen tres elementos en el menú). Para ello, podemos utilizar los valores de los atributos data-testid utilizando aproximaciones parciales. Esto es:

Elementos cuyo valor contenga un texto concreto:

cy.get('[data-testid*="-menu-"]');

Elementos cuyo valor comience por un texto determinado:

cy.get('[data-testid^="header-menu-"]');

Elementos cuyo valor finalice por un texto determinado:

cy.get('[data-testid$="-option"]');

Y la opción más restrictiva: elementos cuyo valor comience por un texto determinado y finalice por otro texto determinado:

cy.get('[data-testid^="header-menu-"][data-testid$="-option"]');

Este último selector nos devolverá todos aquellos elementos que empiecen por "header-menu-" y terminen por "-option".

const expectedItems = 3;
cy.get('[data-testid^="header-menu-"][data-testid$="-option"]').should('have.length', expectedItems);

Descendientes directos (hijos)

CSS nos ofrece una serie de selectores que nos permiten referenciar elementos que sean hijos de otros elementos o que compartan su mismo padre.

Así, para obtener todos los elementos <span> que sean hijos directos de <li>, utilizaríamos el siguiente selector:

cy.get('li > span');

Este selector devolverá únicamente los nodos span que sean hijos directos de un nodo li, es decir:

<span>Home</span>
<span>Cart</span>

El <span> que contiene el texto "About Us" no sería devuelto por este localizador, ya que su padre es un <div>, no un <li>. Nótese la diferencia con el siguiente selector:

 cy.get('li span');

En este caso obtendríamos todos los que tengan algún ancestro (padre, abuelo, bisabuelo, ...) que sea un li. Por lo tanto, nos devolvería lo siguiente:

<span>Home</span>
<span>Cart</span>
<span>About Us</span>

 También podemos seleccionar elementos que sean nietos de otros elementos concatenando el operador '>' con el comodín '*'. Por ejemplo, el siguiente selector devolverá los tags <ul> que sean nietos de un tag <header> :

 cy.get('header >*> ul');

Elementos consecutivos (hermanos)

También podemos seleccionar elementos que compartan un mismo nodo padre, es decir, que sean hermanos. Para hacer esto haremos uso de los operadores '+' y '~‘.

 El operador '+' nos devolverá el siguiente hermano del elemento anterior (y sólo el siguiente hermano). Así, el siguiente selector:

 cy.get('[data-testid="header-menu-home-option"] + li');

Nos devolverá el primer hermano del li cuyo data-testid es "header-menu-home-option", es decir: 

<li data-testid="header-menu-cart-option">
  <span>Cart</span>
</li>

Si lo que queremos es obtener todos los hermanos que se encuentren después de un elemento determinado, utilizaremos el selector '~‘:

 cy.get('[data-testid="header-menu-home-option"] + li');

El selector nos devolverá todos los hermanos del primer li, es decir:

<li data-testid="header-menu-cart-option">
  <span>Cart</span>
</li>
<li data-testid="header-menu-aboutus-option">
  <div>
    <span>About Us</span>
  </div>
</li>

Es importante tener en cuenta que sólo obtendremos los hermanos que se encuentran a continuación del elemento anterior a '~‘, so if there were any other <li> element before it, it would not be retrieved using this selector.

Elementos que contienen otros elementos. :has()

Uno de los grandes problemas que nos encontramos a la hora de localizar elementos con CSS es la imposibilidad de navegar en sentido ascendente, es decir, obtener el padre de un elemento. Si bien XPath nos ofrece la posibilidad de hacer esto directamente, CSS nos permite también hacerlo mediante un pequeño truco: indicando en el selector que queremos seleccionar un elemento que contenga otro elemento. Por ejemplo, si queremos obtener aquellos <li> que contengan un <span>, utilizaremos el operador :has() para indicárselo:

 cy.get('li:has(span)');

Esto devolverá lo siguiente:

<li data-testid="header-menu-home-option">
  <span>Home</span>
</li>
<li data-testid="header-menu-cart-option">
  <span>Cart</span>
</li>
<li data-testid="header-menu-aboutus-option">
  <div>
    <span>About Us</span>
  </div>
</li>

¡Ojo! Podemos aprovecharnos del filtrado de hijos y hermanos y realizar queries más complejas, como por ejemplo, aquellos <li> que tengan un <span> como descendiente directo (child):

 cy.get('li:has(> span)');

En este caso, el resultado omitirá el último <li>, que tiene un <div> como hijo directo en lugar de un <span>

<li data-testid="header-menu-home-option">
  <span>Home</span>
</li>
<li data-testid="header-menu-cart-option">
  <span>Cart</span>
</li>

Elementos que no cumplen una condición. :not()

El El operador :not() es también un buen aliado a la hora de seleccionar elementos por descarte. Así, el siguiente selector nos permitirá localizar todos los <li> excepto "About Us":

 cy.get('[data-testid^="header-menu-"][data-testid$="-option"]:not([data-testid*="-aboutus-"])');

Elementos que contienen un texto. :contains()

Mediante la pseudo-clase :contains() es posible localizar elementos que contengan un texto, bien directamente o en uno de sus descendientes. Por ejemplo, el siguiente selector:

cy.get('*:contains("Ho")');

devolverá los elementos header, nav, ul, li y span corresponde al siguiente subárbol:

<header>
  <nav>
    <ul>
      <li data-testid="header-menu-home-option">
        <span>Home</span>

También podemos refinar la búsqueda especificando otras opciones:

cy.get('span:contains("Ho")');

Lo que devolverá únicamente el elemento span

Nota:: :contains() es case-sensitive

Elementos que cumplen una condición entre varias

Como última sugerencia, podemos crear un selector CSS que actúe como un OR lógico concatenando otros selectores con comas (,). Por ejemplo:

 cy.get('span:contains("Home"),span:contains("Cart")');

Este selector devolverá aquellos elementos span que contenga bien la cadena "Homeo la cadena "Cart". Esta técnica es muy útil para escenarios en los que dos elementos diferentes pueden mostrarse ante una misma acción.

Más información:

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

es_ESSpanish
Scroll al inicio
Consentimiento de Cookies con Real Cookie Banner