Managing random dynamic elements in Cypress

En ocasiones podemos encontrarnos con elementos que pueden (o no) aparecer en el flujo de nuestra aplicación, pero que no son el objeto de nuestra prueba. Un ejemplo de ello puede ser el popup que nos solicita aceptar o denegar las cookies.

Una posible solución para gestionar este problema puede ser forjar una cookie en el hook beforeEach() que haga que nuestro servidor detecte que las cookies ya han sido aceptadas, pero esto puede llevarnos a encontrarnos otros problemas (como que Cloudfare, si es el caso, identifique el acceso como un potencial ataque de forgery y nos devuelva un error 403)

Una solución más universal consiste en utilizar un snippet en JavaScript que genere un temporizador que compruebe la presencia de un elemento cada cierto tiempo y, en caso de que aparezca, ejecute una acción y descarte el temporizador.

Dado que no estamos "exigiendo" la presencia de un elemento mediante un cy.get(), la interacción únicamente se producirá en paralelo a la prueba en caso de que dicho elemento aparezca.

Quede claro que esta técnica es invasiva: estamos alterando el comportamiento original de la página, por lo que es necesario estudiar el caso de uso en el que utilizarlo y asegurarnos de que no afecta al escenario. Úsese con precaución.

Comprobando en segundo plano

El truco es simple: declaramos un intervalo que, en cada ejecución, compruebe si el botón existe y, en caso afirmativo, lo pulse y elimine el intervalo haciendo que no se vuelva a ejecutar. En caso de que el elemento no aparezca, se volverá a ejecutar pasados poolingTimeMs milisegundos.

La pulsación del botón hará que las cookies se acepten, haciendo que el popup desaparezca y no se vuelva a mostrar hasta que las cookies se eliminen.

function tryToClickButtonUntilFound(
  buttonLocator: string,
  poolingIntervalMs: number
) {
  return new Cypress.Promise((resolve) => {
    const interval = setInterval(() => {
      const button = Cypress.$(buttonLocator);
      if (button.length) {
        button.trigger("click");
        clearInterval(interval);
        resolve("Button clicked");
      }
    }, poolingIntervalMs);
  });
}

Configuración del hook

Para que nuestro snippet se ejecute en cada prueba, basta con invocarlo desde el hook before(), dentro de nuestro fichero e2e.js / e2e.ts:

before(): {
  tryToClickButtonUntilFound("accept-cookies-button", 3000);
}

Ejemplo

Vamos a crear un ejemplo real para ilustrar esta técnica. Crearemos un escenario en el que el usuario navega a una página que puede (o no) mostrar un popup para aceptar las cookies.

Vamos a servirnos nuevamente de https://the-internet.herokuapp.com, ya que dispone de un ejemplo de popup gestionado por cookies. Haremos que nuestra prueba acceda aleatoriamente a esta página o, en su defecto, a otra página que no muestre el popup, cerrando el popup en caso de que éste aparezca pero no fallando en caso de que el popup nunca aparezca. https://the-internet.herokuapp.com again, since it has an example of a popup managed by cookies. We will make our test randomly access this page or, failing that, another page that does not show the popup, closing the popup in case it appears but not failing in case the popup never appears.

Feature: Element that can be present or not is dismissed
    Scenario: Popup button is clicked if it appears, but it's not clicked otherwise
        Given the user navigates to a page that may show a popup or not
        When the user waits for 3 seconds
        Then the modal popup is not present in the page

Implementamos el paso que navega aleatoriamente entre dos páginas:

import { Given, When, Then } from "@badeball/cypress-cucumber-preprocessor";

const ENTRY_AD_URL = "entry_ad";
const FLOATING_MENU_URL = "floating_menu";

const locators = {
  modal: "#modal:visible",
};

Given("the user navigates to a page that may show a popup or not", () => {
  cy.visit(Math.random() > 0.5 ? ENTRY_AD_URL : FLOATING_MENU_URL);
});

Añadimos un paso que fuerce una espera explícita para, en caso de que exista un popup, pueda mostrarse en pantalla. Esta espera explícita se añade en el ejemplo para simular el tiempo que la prueba tomaría para seguir ejecutándose. Recuerda que en un entorno real, hay que evitar las esperas explícitas a toda costa:

When("the user waits for {int} seconds", (seconds: number) => {
  cy.log(`Waiting ${seconds} seconds...`);
  cy.wait(Math.ceil(seconds * 1000));
  cy.log(`Finished waiting`);
});

Finalmente, comprobamos que el Popup no existe:

Then("the modal popup is not present in the page", () => {
  cy.get(locators.modal).should("not.exist");
});

Para que el pooling se ejecute antes de cada prueba, añadimos la función al hook before() en el fichero e2e.ts:

const COOKIE_POPUP_BUTTON_LOCATOR = "#modal:visible .modal-footer p";

function tryToClickButtonUntilFound(
  buttonLocator: string,
  poolingIntervalMs: number
) {
  return new Cypress.Promise((resolve) => {
    const intervalId = setInterval(() => {
      const button = Cypress.$(buttonLocator);
      if (button.length) {
        button.trigger("click");
        clearInterval(intervalId);
        resolve("Button clicked");
      }
    }, poolingIntervalMs);
  });
}

before(() => {
  tryToClickButtonUntilFound(COOKIE_POPUP_BUTTON_LOCATOR, 2000);
});

Si el popup no se muestra, la prueba continúa sin más:

En caso contrario, el callback de nuestro interval se encargará de cerrar el popup y el test continuará:

Puedes encontrar el ejemplo de uso de esta técnica aquí.

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