{"id":120,"date":"2024-03-07T18:13:21","date_gmt":"2024-03-07T18:13:21","guid":{"rendered":"https:\/\/danigarcia.org\/?p=120"},"modified":"2024-03-07T18:13:21","modified_gmt":"2024-03-07T18:13:21","slug":"managing-random-dynamic-elements-in-cypress","status":"publish","type":"post","link":"https:\/\/danigarcia.org\/es\/2024\/03\/07\/managing-random-dynamic-elements-in-cypress\/","title":{"rendered":"Managing random dynamic elements in Cypress"},"content":{"rendered":"<p>En ocasiones podemos encontrarnos con elementos que pueden (o no) aparecer en el flujo de nuestra aplicaci\u00f3n, 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.<\/p>\n\n\n\n<p>Una posible soluci\u00f3n 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)<\/p>\n\n\n\n<p>Una soluci\u00f3n m\u00e1s 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\u00f3n y descarte el temporizador.<\/p>\n\n\n\n<p>Dado que no estamos \"exigiendo\" la presencia de un elemento mediante un cy.get(), la interacci\u00f3n \u00fanicamente se producir\u00e1 en paralelo a la prueba en caso de que dicho elemento aparezca.<\/p>\n\n\n\n<p>Quede claro que esta t\u00e9cnica es invasiva: estamos alterando el comportamiento original de la p\u00e1gina, por lo que es necesario estudiar el caso de uso en el que utilizarlo y asegurarnos de que no afecta al escenario. \u00dasese con precauci\u00f3n.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Comprobando en segundo plano<\/h2>\n\n\n\n<p>El truco es simple: declaramos un intervalo que, en cada ejecuci\u00f3n, compruebe si el bot\u00f3n 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\u00e1 a ejecutar pasados poolingTimeMs milisegundos.<\/p>\n\n\n\n<p>La pulsaci\u00f3n del bot\u00f3n har\u00e1 que las cookies se acepten, haciendo que el popup desaparezca y no se vuelva a mostrar hasta que las cookies se eliminen.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"typescript\" class=\"language-typescript line-numbers\">function tryToClickButtonUntilFound(\n  buttonLocator: string,\n  poolingIntervalMs: number\n) {\n  return new Cypress.Promise((resolve) => {\n    const interval = setInterval(() => {\n      const button = Cypress.$(buttonLocator);\n      if (button.length) {\n        button.trigger(\"click\");\n        clearInterval(interval);\n        resolve(\"Button clicked\");\n      }\n    }, poolingIntervalMs);\n  });\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Configuraci\u00f3n del hook<\/h2>\n\n\n\n<p>Para que nuestro snippet se ejecute en cada prueba, basta con invocarlo desde el hook before(), dentro de nuestro fichero e2e.js \/ e2e.ts:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"typescript\" class=\"language-typescript\">before(): {\n  tryToClickButtonUntilFound(\"accept-cookies-button\", 3000);\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Ejemplo<\/h2>\n\n\n\n<p>Vamos a crear un ejemplo real para ilustrar esta t\u00e9cnica. Crearemos un escenario en el que el usuario navega a una p\u00e1gina que puede (o no) mostrar un popup para aceptar las cookies.<\/p>\n\n\n\n<p>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\u00e1gina o, en su defecto, a otra p\u00e1gina que no muestre el popup, cerrando el popup en caso de que \u00e9ste aparezca pero no fallando en caso de que el popup nunca aparezca. <a href=\"https:\/\/the-internet.herokuapp.com\">https:\/\/the-internet.herokuapp.com<\/a> 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.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"\" class=\"line-numbers\">Feature: Element that can be present or not is dismissed\n    Scenario: Popup button is clicked if it appears, but it's not clicked otherwise\n        Given the user navigates to a page that may show a popup or not\n        When the user waits for 3 seconds\n        Then the modal popup is not present in the page<\/code><\/pre>\n\n\n\n<p>Implementamos el paso que navega aleatoriamente entre dos p\u00e1ginas:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"typescript\" class=\"language-typescript\">import { Given, When, Then } from \"@badeball\/cypress-cucumber-preprocessor\";\n\nconst ENTRY_AD_URL = \"entry_ad\";\nconst FLOATING_MENU_URL = \"floating_menu\";\n\nconst locators = {\n  modal: \"#modal:visible\",\n};\n\nGiven(\"the user navigates to a page that may show a popup or not\", () => {\n  cy.visit(Math.random() > 0.5 ? ENTRY_AD_URL : FLOATING_MENU_URL);\n});<\/code><\/pre>\n\n\n\n<p>A\u00f1adimos un paso que fuerce una espera expl\u00edcita para, en caso de que exista un popup, pueda mostrarse en pantalla. Esta espera expl\u00edcita se a\u00f1ade en el ejemplo para simular el tiempo que la prueba tomar\u00eda para seguir ejecut\u00e1ndose. Recuerda que en un entorno real, hay que evitar las esperas expl\u00edcitas a toda costa:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"typescript\" class=\"language-typescript\">When(\"the user waits for {int} seconds\", (seconds: number) => {\n  cy.log(`Waiting ${seconds} seconds...`);\n  cy.wait(Math.ceil(seconds * 1000));\n  cy.log(`Finished waiting`);\n});<\/code><\/pre>\n\n\n\n<p>Finalmente, comprobamos que el Popup no existe:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"typescript\" class=\"language-typescript\">Then(\"the modal popup is not present in the page\", () => {\n  cy.get(locators.modal).should(\"not.exist\");\n});<\/code><\/pre>\n\n\n\n<p>Para que el pooling se ejecute antes de cada prueba, a\u00f1adimos la funci\u00f3n al hook before() en el fichero e2e.ts:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"typescript\" class=\"language-typescript\">const COOKIE_POPUP_BUTTON_LOCATOR = \"#modal:visible .modal-footer p\";\n\nfunction tryToClickButtonUntilFound(\n  buttonLocator: string,\n  poolingIntervalMs: number\n) {\n  return new Cypress.Promise((resolve) => {\n    const intervalId = setInterval(() => {\n      const button = Cypress.$(buttonLocator);\n      if (button.length) {\n        button.trigger(\"click\");\n        clearInterval(intervalId);\n        resolve(\"Button clicked\");\n      }\n    }, poolingIntervalMs);\n  });\n}\n\nbefore(() => {\n  tryToClickButtonUntilFound(COOKIE_POPUP_BUTTON_LOCATOR, 2000);\n});<\/code><\/pre>\n\n\n\n<p>Si el popup no se muestra, la prueba contin\u00faa sin m\u00e1s:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"445\" src=\"https:\/\/danigarcia.org\/wp-content\/uploads\/2024\/03\/imagen-1024x445.png\" alt=\"\" class=\"wp-image-122\" srcset=\"https:\/\/danigarcia.org\/wp-content\/uploads\/2024\/03\/imagen-1024x445.png 1024w, https:\/\/danigarcia.org\/wp-content\/uploads\/2024\/03\/imagen-300x130.png 300w, https:\/\/danigarcia.org\/wp-content\/uploads\/2024\/03\/imagen-768x334.png 768w, https:\/\/danigarcia.org\/wp-content\/uploads\/2024\/03\/imagen-18x8.png 18w, https:\/\/danigarcia.org\/wp-content\/uploads\/2024\/03\/imagen.png 1356w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>En caso contrario, el callback de nuestro interval se encargar\u00e1 de cerrar el popup y el test continuar\u00e1:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"445\" src=\"https:\/\/danigarcia.org\/wp-content\/uploads\/2024\/03\/imagen-2-1024x445.png\" alt=\"\" class=\"wp-image-124\" srcset=\"https:\/\/danigarcia.org\/wp-content\/uploads\/2024\/03\/imagen-2-1024x445.png 1024w, https:\/\/danigarcia.org\/wp-content\/uploads\/2024\/03\/imagen-2-300x130.png 300w, https:\/\/danigarcia.org\/wp-content\/uploads\/2024\/03\/imagen-2-768x334.png 768w, https:\/\/danigarcia.org\/wp-content\/uploads\/2024\/03\/imagen-2-18x8.png 18w, https:\/\/danigarcia.org\/wp-content\/uploads\/2024\/03\/imagen-2.png 1354w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"446\" src=\"https:\/\/danigarcia.org\/wp-content\/uploads\/2024\/03\/imagen-1-1024x446.png\" alt=\"\" class=\"wp-image-123\" srcset=\"https:\/\/danigarcia.org\/wp-content\/uploads\/2024\/03\/imagen-1-1024x446.png 1024w, https:\/\/danigarcia.org\/wp-content\/uploads\/2024\/03\/imagen-1-300x131.png 300w, https:\/\/danigarcia.org\/wp-content\/uploads\/2024\/03\/imagen-1-768x335.png 768w, https:\/\/danigarcia.org\/wp-content\/uploads\/2024\/03\/imagen-1-18x8.png 18w, https:\/\/danigarcia.org\/wp-content\/uploads\/2024\/03\/imagen-1.png 1356w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Puedes encontrar el ejemplo de uso de esta t\u00e9cnica <a href=\"https:\/\/github.com\/GarciaDan\/cypress-ts-bdd\">aqu\u00ed<\/a>.<\/p>","protected":false},"excerpt":{"rendered":"<p>Sometimes we may encounter elements that may (or may not) appear in the flow of our application, but are not the object of our test. An example of this may [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[8,26],"class_list":["post-120","post","type-post","status-publish","format-standard","hentry","category-automation","tag-cypress","tag-tricks"],"gutentor_comment":14238,"_links":{"self":[{"href":"https:\/\/danigarcia.org\/es\/wp-json\/wp\/v2\/posts\/120","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/danigarcia.org\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/danigarcia.org\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/danigarcia.org\/es\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/danigarcia.org\/es\/wp-json\/wp\/v2\/comments?post=120"}],"version-history":[{"count":3,"href":"https:\/\/danigarcia.org\/es\/wp-json\/wp\/v2\/posts\/120\/revisions"}],"predecessor-version":[{"id":126,"href":"https:\/\/danigarcia.org\/es\/wp-json\/wp\/v2\/posts\/120\/revisions\/126"}],"wp:attachment":[{"href":"https:\/\/danigarcia.org\/es\/wp-json\/wp\/v2\/media?parent=120"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/danigarcia.org\/es\/wp-json\/wp\/v2\/categories?post=120"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/danigarcia.org\/es\/wp-json\/wp\/v2\/tags?post=120"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}