<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Sebastian Clavijo Suero</title>
    <description>The latest articles on Forem by Sebastian Clavijo Suero (@sebastianclavijo).</description>
    <link>https://forem.com/sebastianclavijo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1453622%2F5e79b768-40f4-411c-be62-94b82d72af43.jpg</url>
      <title>Forem: Sebastian Clavijo Suero</title>
      <link>https://forem.com/sebastianclavijo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/sebastianclavijo"/>
    <language>en</language>
    <item>
      <title>WICK-DOM-OBSERVER: The Deterministic Cypress Plugin for Fast Spinners, Blinking Toasts, Optional Overlays, and UI’s Most Wanted</title>
      <dc:creator>Sebastian Clavijo Suero</dc:creator>
      <pubDate>Sun, 22 Mar 2026 07:21:22 +0000</pubDate>
      <link>https://forem.com/sebastianclavijo/wick-dom-observer-the-deterministic-cypress-plugin-for-fast-spinners-blinking-toasts-optional-4e59</link>
      <guid>https://forem.com/sebastianclavijo/wick-dom-observer-the-deterministic-cypress-plugin-for-fast-spinners-blinking-toasts-optional-4e59</guid>
      <description>&lt;p&gt;&lt;em&gt;Because the most dangerous UI behaviors are the ones that leave almost no trace.&lt;/em&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  ACT 1: EXPOSITION
&lt;/h1&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;There are some UI elements that seem to exist for one noble purpose only: to make your Cypress tests miserable.&lt;/p&gt;

&lt;p&gt;I am talking about the spinner that appears and disappears so absurdly fast that by the time Cypress gets there, it is already gone.&lt;br&gt;&lt;br&gt;
The toast that politely informs the user that something happened, but only for a tiny and inconvenient window of time.&lt;br&gt;&lt;br&gt;
And, of course, the annoying modal or overlay that shows up on page load only when it feels like it, blocks the whole page, and leaves your test suite wondering whether it should close it... or pretend it never existed.&lt;/p&gt;

&lt;p&gt;These are not exotic edge cases. These are real UI behaviors we deal with all the time.&lt;/p&gt;

&lt;p&gt;And the worst part is not even testing them. The worst part is testing them &lt;strong&gt;reliably&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Because traditional approaches usually push you toward one of these painful paths:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;cy.wait(500)&lt;/code&gt; and pray.&lt;/li&gt;
&lt;li&gt;Force the UI to behave unnaturally just for the test.&lt;/li&gt;
&lt;li&gt;Add network intercepts and cross your fingers, hoping that somehow will be enough.&lt;/li&gt;
&lt;li&gt;Write fragile logic that still flakes the moment the DOM decides to move just a little faster than usual.&lt;/li&gt;
&lt;li&gt;And do not even get me started on what happens when all that reaches your project’s notoriously unpredictable CI pipeline.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is exactly why I built &lt;strong&gt;&lt;a href="https://github.com/sclavijosuero/wick-dom-observer" rel="noopener noreferrer"&gt;wick-dom-observer&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;⭐⭐⭐⭐⭐ Its mission is simple: help Cypress detect and validate those rebel UI elements that appear too fast, disappear too fast, or appear only sometimes, without turning your tests into a dramatic mess. ⭐⭐⭐⭐⭐&lt;/p&gt;


&lt;h1&gt;
  
  
  ACT 2: CONFRONTATION
&lt;/h1&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;Two commands. Same mission. Different entry point.&lt;/p&gt;

&lt;p&gt;When the UI feedback is triggered &lt;strong&gt;after a click&lt;/strong&gt;, use &lt;code&gt;clickAndWatchForElement()&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
When the element may appear &lt;strong&gt;on its own&lt;/strong&gt;, use &lt;code&gt;watchForElement()&lt;/code&gt;.&lt;br&gt;
 &lt;/p&gt;
&lt;h3&gt;
  
  
  1) &lt;code&gt;clickAndWatchForElement()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Use this one when the testing event starts with a user action: clicking a button, link, icon, or anything else that kicks off a spinner, toast, modal, or overlay.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;clickAndWatchForElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;clickAndWatchForElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;clickAndWatchForElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;clickAndWatchForElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;clickAndWatchForElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;clickAndWatchForElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2) &lt;code&gt;watchForElement()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Use this one when there is &lt;strong&gt;no click involved&lt;/strong&gt; and the element may show up by itself, like page-load overlays, announcements, promos, or optional blocking panels.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;watchForElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The shared &lt;code&gt;config&lt;/code&gt; object
&lt;/h3&gt;

&lt;p&gt;Both commands use the same &lt;code&gt;config&lt;/code&gt; shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$el&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$el&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;      &lt;span class="c1"&gt;// optional&lt;/span&gt;
  &lt;span class="nx"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                 &lt;span class="c1"&gt;// optional&lt;/span&gt;
  &lt;span class="nx"&gt;appear&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;optional&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;            &lt;span class="c1"&gt;// 'optional' | 'required'&lt;/span&gt;
  &lt;span class="nx"&gt;disappear&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;              &lt;span class="c1"&gt;// optional&lt;/span&gt;
  &lt;span class="nx"&gt;pollingInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;// optional&lt;/span&gt;
  &lt;span class="nx"&gt;mustLast&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                 &lt;span class="c1"&gt;// optional&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What each option does
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;selector&lt;/code&gt; → the element you want to watch in the DOM
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;assert&lt;/code&gt; → synchronous assertion callback executed when the element is found
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;action&lt;/code&gt; → optional synchronous callback to perform side effects after &lt;code&gt;assert&lt;/code&gt; passes
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;timeout&lt;/code&gt; → max time to wait for appear / disappear phases
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;appear: 'optional'&lt;/code&gt; → valid if the element appears or not
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;appear: 'required'&lt;/code&gt; → fail if the element never shows up
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;disappear: false&lt;/code&gt; → validate appearance only; do not wait for disappearance&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;disappear: true&lt;/code&gt; → also wait for the element to go away
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pollingInterval&lt;/code&gt; → how often the DOM is checked
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mustLast&lt;/code&gt; → minimum time the element must remain visible in the DOM
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; &lt;code&gt;assert()&lt;/code&gt; and &lt;code&gt;action()&lt;/code&gt; must stay synchronous.&lt;br&gt;&lt;br&gt;
No &lt;code&gt;cy.get()&lt;/code&gt;, no &lt;code&gt;cy.wrap()&lt;/code&gt;, no Cypress commands inside them.&lt;br&gt;&lt;br&gt;
This is plain JavaScript / jQuery territory.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The mental model
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;clickAndWatchForElement()&lt;/code&gt;&lt;/strong&gt; = &lt;em&gt;trigger the event, then watch the DOM&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;watchForElement()&lt;/code&gt;&lt;/strong&gt; = &lt;em&gt;just watch the DOM&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;mustLast&lt;/code&gt;&lt;/strong&gt; = &lt;em&gt;make sure the element did not vanish too quickly&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;disappear: true&lt;/code&gt;&lt;/strong&gt; = &lt;em&gt;validate the full lifecycle, not just the appearance&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Case 1: The super-fast spinner that mocks your test
&lt;/h2&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  The Mark
&lt;/h3&gt;

&lt;p&gt;You click a button to load data.&lt;/p&gt;

&lt;p&gt;A spinner appears, a loading label is shown, the button is disabled for a moment, the spinner disappears, and then the data is displayed.&lt;/p&gt;

&lt;p&gt;Sounds easy enough to test, right?&lt;/p&gt;

&lt;p&gt;What if the spinner only shows for 200ms? Or 100ms? Better yet, &lt;strong&gt;25ms&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;Because if that spinner is fast enough, this classic pattern can betray you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;shows the loading spinner and then displays the data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/your-page&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy="load-data-btn"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy="service-spinner"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;be.visible&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy="loading-label"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;be.visible&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;contain&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fetching latest campaign analytics&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy="load-data-btn"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;be.disabled&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy="service-spinner"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;not.exist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy="loading-label"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;not.exist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy="load-data-btn"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;be.enabled&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy="campaign-table"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;be.visible&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy="campaign-row"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;have.length.greaterThan&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why? Because Cypress starts looking &lt;strong&gt;after&lt;/strong&gt; the click has already happened, and if the spinner had a very short life in the DOM, you may miss it entirely.&lt;/p&gt;

&lt;p&gt;So now you have a flaky test for a UI behavior that absolutely happens.&lt;br&gt;
 &lt;/p&gt;
&lt;h3&gt;
  
  
  The Takedown
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;assertLoadCampaignDataSpinner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$el&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// The spinner must be visible when observed.&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$el&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="c1"&gt;// Move to a stable parent scope so we can inspect related elements&lt;/span&gt;
  &lt;span class="c1"&gt;// that participate in the same loading flow.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;$root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;closest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Validate the loading label shown while data is being fetched.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loadingLabel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy="loading-row"] .loading-label&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loadingLabel&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loadingLabel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fetching latest campaign analytics&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Validate that the trigger button is temporarily disabled&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loadDataBtn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy="load-data-btn"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loadDataBtn&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loadDataBtn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;disabled&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;shows the loading spinner and then displays the data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/modal-table-demo.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Click the trigger button and start observing the DOM beforehand to catch the spinner&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy="load-data-btn"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;clickAndWatchForElement&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="c1"&gt;// Element to observe after the click.&lt;/span&gt;
    &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy="service-spinner"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;// The spinner is expected to appear in this flow.&lt;/span&gt;
    &lt;span class="na"&gt;appear&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;required&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;// The spinner is also expected to disappear before the command finishes.&lt;/span&gt;
    &lt;span class="na"&gt;disappear&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;// Max time (ms) to wait for the spinner to appear or disappear.&lt;/span&gt;
    &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;// Run the custom assertions while the spinner is present.&lt;/span&gt;
    &lt;span class="na"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;assertLoadCampaignDataSpinner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy="loading-label"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;not.be.visible&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy="load-data-btn"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;be.enabled&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy="campaign-table"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;be.visible&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy="campaign-row"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;have.length.greaterThan&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt; &lt;/p&gt;
&lt;h3&gt;
  
  
  Why the Hit Lands 👊
&lt;/h3&gt;

&lt;p&gt;The key trick is that &lt;code&gt;clickAndWatchForElement()&lt;/code&gt; starts observing &lt;strong&gt;before&lt;/strong&gt; the click happens, and an additional &lt;em&gt;secret sauce&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;And that matters a lot.&lt;/p&gt;

&lt;p&gt;Cypress is already playing its own game here: its command queue runs asynchronously and processes commands at its own pace, while plain JavaScript, jQuery, and the DOM keep moving on a different timeline. So if you click first and only then ask Cypress to check for the spinner, there is a very real chance the DOM already did its thing and moved on.&lt;/p&gt;

&lt;p&gt;That is why &lt;strong&gt;wick-dom-observer&lt;/strong&gt; flips the sequence around: it prepares the observation flow first, performs the click, and then keeps observing the DOM to catch the exact moment the spinner appears and the exact moment it disappears.&lt;/p&gt;

&lt;p&gt;What is the &lt;em&gt;secret sauce&lt;/em&gt;? The &lt;strong&gt;MutationObserver&lt;/strong&gt; interface.&lt;/p&gt;

&lt;p&gt;It is the browser API designed to watch for changes in the DOM tree, which makes it a perfect fit for tracking fast UI elements.&lt;/p&gt;

&lt;p&gt;In other words, this is not just about watching earlier. It is about watching on the right timeline.&lt;/p&gt;

&lt;p&gt;So the example above using &lt;code&gt;clickAndWatchForElement()&lt;/code&gt; validates the full user-facing behavior:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the spinner appears,&lt;/li&gt;
&lt;li&gt;the loading label is visible and contains the expected text,&lt;/li&gt;
&lt;li&gt;the action button is temporarily disabled,&lt;/li&gt;
&lt;li&gt;and finally, the spinner disappears and the button is enabled again.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjrqkvy0tecqxa3oa802n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjrqkvy0tecqxa3oa802n.png" alt=" " width="800" height="423"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That is much closer to testing the &lt;strong&gt;real visual completion of the interaction&lt;/strong&gt; than simply stubbing a network call and assuming everything on screen behaved correctly.&lt;/p&gt;

&lt;p&gt;If you want to inspect how Cypress queued and executed everything around that flow, the &lt;strong&gt;&lt;a href="https://github.com/sclavijosuero/cypress-flaky-test-audit" rel="noopener noreferrer"&gt;cypress-flaky-test-audit&lt;/a&gt;&lt;/strong&gt; plugin is a very good partner in crime.&lt;/p&gt;

&lt;p&gt;If you feel even more reckless and want to understand how those two timelines actually interact — the Cypress queue and plain JavaScript — then you may want to venture into &lt;a href="https://dev.to/sebastianclavijo/the-async-nature-of-cypress-dont-mess-with-the-timelines-in-your-cypress-tests-dual-verse-3ehh"&gt;The Async Nature of Cypress: Don't Mess with the Timelines in Your Cypress Tests 'Dual-Verse'&lt;/a&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  Case 2: The toast that tries to disappear before the witness sees it
&lt;/h2&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h3&gt;
  
  
  The Mark
&lt;/h3&gt;

&lt;p&gt;A toast is supposed to confirm that something important happened.&lt;/p&gt;

&lt;p&gt;But if it appears and disappears too quickly, then from a user perspective it might as well have whispered the message into the void.&lt;/p&gt;

&lt;p&gt;And from a testing perspective, this creates a second problem: it is not enough to prove the toast &lt;strong&gt;appeared&lt;/strong&gt;. You may also want to prove it stayed visible &lt;strong&gt;long enough to actually matter&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That is exactly where &lt;code&gt;mustLast&lt;/code&gt; enters the room.&lt;br&gt;
 &lt;/p&gt;
&lt;h3&gt;
  
  
  The Takedown
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Assertion helper executed when the toast is detected.&lt;/span&gt;
&lt;span class="c1"&gt;// It validates the toast visibility and its success message.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;assertSuccessToast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$el&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// The toast must be visible while it is being observed.&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$el&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="c1"&gt;// The toast should contain the expected success message.&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Campaign saved successfully&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;validates that the success toast stays visible long enough&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/your-page&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Click the button and start observing the DOM beforehand&lt;/span&gt;
  &lt;span class="c1"&gt;// so the plugin can catch the toast as soon as it appears.&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy="save-campaign-btn"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;clickAndWatchForElement&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="c1"&gt;// Element to observe after the click.&lt;/span&gt;
    &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy="success-toast"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;// The toast is expected to appear in this flow.&lt;/span&gt;
    &lt;span class="na"&gt;appear&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;required&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;// The toast is also expected to disappear before the command finishes.&lt;/span&gt;
    &lt;span class="na"&gt;disappear&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;// Maximum time allowed for the full toast lifecycle.&lt;/span&gt;
    &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;// Check the DOM very frequently so short-lived toasts are not missed.&lt;/span&gt;
    &lt;span class="na"&gt;pollingInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;// Ensure the toast remains visible for at least 3 seconds.&lt;/span&gt;
    &lt;span class="c1"&gt;// This helps validate meaningful user-visible feedback, not just a blink.&lt;/span&gt;
    &lt;span class="na"&gt;mustLast&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;// Run the custom assertions while the toast is present.&lt;/span&gt;
    &lt;span class="na"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;assertSuccessToast&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// After the lifecycle is complete, the toast should no longer exist in the DOM.&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy="success-toast"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;not.exist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt; &lt;/p&gt;
&lt;h3&gt;
  
  
  Why the Hit Lands 🔨
&lt;/h3&gt;

&lt;p&gt;This one is different from the spinner case.&lt;/p&gt;

&lt;p&gt;With a spinner, the challenge is often just to catch it at all before it disappears.&lt;/p&gt;

&lt;p&gt;With a toast, the challenge is more subtle: you also want to prove it did not vanish &lt;strong&gt;too soon&lt;/strong&gt;. In other words, the problem is not merely appearance, but &lt;strong&gt;meaningful visibility&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That is exactly what &lt;code&gt;mustLast&lt;/code&gt; is for.&lt;/p&gt;

&lt;p&gt;So this test does not just verify that the toast showed up. It verifies something far more useful: that it stayed visible for at least a minimum amount of time before disappearing.&lt;/p&gt;

&lt;p&gt;That is important, because a toast that flashes too fast may technically exist in the DOM, but from a UX perspective it is almost the same as not being there at all.&lt;/p&gt;

&lt;p&gt;So in this case, &lt;code&gt;clickAndWatchForElement()&lt;/code&gt; validates the full behavior:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the toast appears,&lt;/li&gt;
&lt;li&gt;it contains the expected message,&lt;/li&gt;
&lt;li&gt;it remains visible for at least the required duration,&lt;/li&gt;
&lt;li&gt;and then it disappears.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F20yub34da8lras59nkg7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F20yub34da8lras59nkg7.png" alt=" " width="800" height="423"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That is far more aligned with actual user-facing feedback than just confirming that a success event happened somewhere in the background.&lt;/p&gt;


&lt;h2&gt;
  
  
  Case 3: The optional page-load overlay that shows up only when it feels like it
&lt;/h2&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h3&gt;
  
  
  The Mark
&lt;/h3&gt;

&lt;p&gt;Now let’s talk about one of the most annoying UI patterns ever created:&lt;/p&gt;

&lt;p&gt;A &lt;em&gt;modal&lt;/em&gt; / &lt;em&gt;overlay&lt;/em&gt; / &lt;em&gt;promo banner&lt;/em&gt; / &lt;em&gt;“helpful” announcement panel&lt;/em&gt; that appears on page load only &lt;strong&gt;some&lt;/strong&gt; of the time.&lt;/p&gt;

&lt;p&gt;Sometimes it shows up.&lt;br&gt;&lt;br&gt;
Sometimes it does not.&lt;br&gt;&lt;br&gt;
When it does, it blocks the page, and you need to discard.&lt;br&gt;
When it does not, your test should continue normally.&lt;/p&gt;

&lt;p&gt;This is exactly the kind of behavior that makes tests nondeterministic if you handle it with normal Cypress assertions.&lt;/p&gt;

&lt;p&gt;Because this will fail when the overlay does not exist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy="ad-overlay"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;be.visible&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this is also not enough by itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;$body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy="ad-overlay"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy="close-ad-btn"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That kind of conditional logic often becomes ugly, fragile, and inconsistent very quickly.&lt;br&gt;
 &lt;/p&gt;
&lt;h3&gt;
  
  
  The Takedown
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This overlay is one of those annoying guests that may or may not show up.&lt;/span&gt;
&lt;span class="c1"&gt;// If it appears, handle it. If it does not, move on without failing the test.&lt;/span&gt;

&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;handles an optional startup overlay without flaking&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/modal-table-demo.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Observe startup banner modal that may or may not appear on page load (about 50%)&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;watchForElement&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="c1"&gt;// Element to observe in the DOM.&lt;/span&gt;
    &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy="ad-overlay"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;// The overlay may or may not appear, and both outcomes are valid&lt;/span&gt;
    &lt;span class="na"&gt;appear&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;optional&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;// We do not require the plugin itself to wait for the overlay to disappear&lt;/span&gt;
    &lt;span class="c1"&gt;// as part of the command lifecycle&lt;/span&gt;
    &lt;span class="na"&gt;disappear&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;// Maximum time to wait for the optional overlay to show up&lt;/span&gt;
    &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;// Check the DOM very frequently so the plugin can catch the overlay&lt;/span&gt;
    &lt;span class="c1"&gt;// as soon as it appears&lt;/span&gt;
    &lt;span class="na"&gt;pollingInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;// If the overlay appears, validate its visible state and confirm&lt;/span&gt;
    &lt;span class="c1"&gt;// the close button is present&lt;/span&gt;
    &lt;span class="na"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$el&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// If the overlay appears, validate its visible state and confirm&lt;/span&gt;
      &lt;span class="c1"&gt;// the close button is present&lt;/span&gt;
      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$el&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy="close-ad-btn"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$el&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Close the overlay via DOM click and verify the same it is no longer visible&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;closeBtnEl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy="close-ad-btn"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;closeBtnEl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;closeBtnEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$el&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// Rest of your test code&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy="load-data-btn"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;be.visible&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;// [...]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt; &lt;/p&gt;
&lt;h3&gt;
  
  
  Why the Hit Lands 🪏
&lt;/h3&gt;

&lt;p&gt;This is where &lt;code&gt;watchForElement()&lt;/code&gt; shines.&lt;/p&gt;

&lt;p&gt;There is no click involved here. The command simply starts observing the DOM and waits to see whether the target element appears within the configured timeout.&lt;/p&gt;

&lt;p&gt;And the crucial part is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;appear&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;optional&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That means &lt;strong&gt;both outcomes are valid&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;if the overlay appears, the plugin runs the &lt;code&gt;assert&lt;/code&gt; callback and then the &lt;code&gt;action&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;if the overlay never appears, the test still passes and continues normally.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is huge, because it lets you model a truly real-world UI behavior without lying to the test and without introducing flaky branching logic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9vw9e4q2l43x33d743wp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9vw9e4q2l43x33d743wp.png" alt=" " width="800" height="423"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also, notice the &lt;code&gt;action&lt;/code&gt; callback. That is very convenient here because when the overlay is actually found and validated, you can immediately close it in a deterministic way and unblock the rest of the page.&lt;/p&gt;

&lt;p&gt;So instead of asking &lt;em&gt;“will the overlay appear this time?”&lt;/em&gt;, your test says:&lt;br&gt;&lt;br&gt;
&lt;em&gt;“If it appears, validate it and close it. If it does not, fine, move on.”&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Case 4: The full real-world mess — optional overlay + required spinner flow
&lt;/h2&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  The Mark
&lt;/h3&gt;

&lt;p&gt;Now let’s combine some of these problems into one realistic test flow.&lt;/p&gt;

&lt;p&gt;You open a page.&lt;/p&gt;

&lt;p&gt;Half of the time, an annoying overlay appears and blocks the page.&lt;br&gt;&lt;br&gt;
After dealing with that, the user clicks a button that triggers a loading spinner and a temporary loading state.&lt;/p&gt;

&lt;p&gt;In other words:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one UI element is &lt;strong&gt;optional&lt;/strong&gt;,&lt;/li&gt;
&lt;li&gt;another one is &lt;strong&gt;required&lt;/strong&gt;,&lt;/li&gt;
&lt;li&gt;one appears automatically,&lt;/li&gt;
&lt;li&gt;the other appears after a click,&lt;/li&gt;
&lt;li&gt;and both need to be handled in one deterministic test.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the kind of scenario where many tests start becoming fragile little monsters.&lt;br&gt;
 &lt;/p&gt;

&lt;h3&gt;
  
  
  The Takedown
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Assertion helper for the spinner/loading state.&lt;/span&gt;
&lt;span class="c1"&gt;// It validates the spinner itself plus the related UI signals.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;assertLoadCampaignDataSpinner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$el&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$el&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;$root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;closest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;labelFetching&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy="loading-row"] .loading-label&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;labelFetching&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;labelFetching&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fetching latest campaign analytics&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loadDataBtn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy="load-data-btn"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loadDataBtn&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loadDataBtn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;disabled&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;handles optional overlay and validates the spinner flow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Open the demo page that contains both behaviors:&lt;/span&gt;
  &lt;span class="c1"&gt;// an optional blocking overlay on load and a spinner triggered by a button click.&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/modal-table-demo.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// First, watch for the optional overlay that may or may not appear on page load.&lt;/span&gt;
  &lt;span class="c1"&gt;// If it appears, validate it and close it so the rest of the test can continue.&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;watchForElement&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy="ad-overlay"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;appear&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;optional&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;disappear&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;pollingInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$el&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$el&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy="close-ad-btn"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$el&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;closeBtnEl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy="close-ad-btn"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;closeBtnEl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;closeBtnEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$el&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// Then, trigger the load-data action and observe the spinner lifecycle.&lt;/span&gt;
  &lt;span class="c1"&gt;// The spinner is required to appear and later disappear, while the custom&lt;/span&gt;
  &lt;span class="c1"&gt;// assertion helper validates the loading state during that window.&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy="load-data-btn"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;clickAndWatchForElement&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy="service-spinner"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;appear&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;required&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;disappear&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;assertLoadCampaignDataSpinner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// Finally, confirm the loading flow completed successfully&lt;/span&gt;
  &lt;span class="c1"&gt;// and the UI returned to its interactive state.&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.loading-label&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;not.be.visible&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy="load-data-btn"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;be.enabled&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Why the Hit Lands 🪦
&lt;/h3&gt;

&lt;p&gt;This example is the one I like the most because it represents the kind of ugly, imperfect, wonderfully realistic UI flow we often have to automate.&lt;/p&gt;

&lt;p&gt;The plugin allows each moving part to be described according to its real behavior:&lt;/p&gt;

&lt;h4&gt;
  
  
  1) The overlay is treated as optional
&lt;/h4&gt;

&lt;p&gt;It may appear or not, and the test remains stable in both cases.&lt;/p&gt;

&lt;h4&gt;
  
  
  2) The spinner is treated as required
&lt;/h4&gt;

&lt;p&gt;Once the user clicks the button, the spinner must show up, pass the assertions, and disappear correctly.&lt;/p&gt;

&lt;h4&gt;
  
  
  3) The test still reflects what the user experiences
&lt;/h4&gt;

&lt;p&gt;This is not a fake synchronization trick.&lt;br&gt;&lt;br&gt;
It is validating actual DOM behavior that the user would perceive:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a blocking overlay may interrupt the flow,&lt;/li&gt;
&lt;li&gt;the user closes it,&lt;/li&gt;
&lt;li&gt;the user click a button&lt;/li&gt;
&lt;li&gt;then a loading state appears,&lt;/li&gt;
&lt;li&gt;then it goes away,&lt;/li&gt;
&lt;li&gt;then the UI becomes interactive again.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You are testing that the interface behaved properly under realistic conditions.&lt;/p&gt;

&lt;p&gt;And yes, &lt;strong&gt;that includes those fast DOM transitions that traditional Cypress chaining can miss&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  ACT3: RESOLUTION
&lt;/h1&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;Some UI elements are simply built like tiny rebels.&lt;/p&gt;

&lt;p&gt;They show up too fast.&lt;br&gt;&lt;br&gt;
They disappear too fast.&lt;br&gt;&lt;br&gt;
They block your page unexpectedly.&lt;br&gt;&lt;br&gt;
They appear only half the time.&lt;br&gt;&lt;br&gt;
And then they sit there, very proudly, making your tests flaky and your patience shorter.&lt;/p&gt;

&lt;p&gt;This is where &lt;strong&gt;&lt;code&gt;wick-dom-observer&lt;/code&gt;&lt;/strong&gt; turns those flaky little ghosts into observable DOM evidence.&lt;/p&gt;

&lt;p&gt;It gives Cypress a much better chance to deal with these troublesome UI behaviors in a deterministic and elegant way, without forcing you into &lt;code&gt;cy.wait(...)&lt;/code&gt; nonsense, awkward conditionals, intersects, or synthetic workarounds that do not really test real user experience.&lt;/p&gt;

&lt;p&gt;So if your application contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fast spinners,&lt;/li&gt;
&lt;li&gt;short-lived toasts,&lt;/li&gt;
&lt;li&gt;startup overlays,&lt;/li&gt;
&lt;li&gt;optional modals,&lt;/li&gt;
&lt;li&gt;or any UI element that behaves like it does not respect authority...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;then &lt;strong&gt;this plugin was made for you&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Because sometimes what your test suite really needs is not more waiting, not more retries, and definitely not more hope.&lt;/p&gt;

&lt;p&gt;It needs a proper DOM observer.&lt;/p&gt;

&lt;p&gt;You can find the plugin here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub repo:&lt;/strong&gt; &lt;a href="https://github.com/sclavijosuero/wick-dom-observer" rel="noopener noreferrer"&gt;https://github.com/sclavijosuero/wick-dom-observer&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;npm:&lt;/strong&gt; &lt;a href="https://www.npmjs.com/package/wick-dom-observer" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/wick-dom-observer&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;strong&gt;I'd love to hear from you! Please don't forget to follow me, leave a comment, or a reaction if you found this article useful or insightful.&lt;/strong&gt;&lt;/em&gt; ❤️ 🦄 🤯 🙌 🔥&lt;/p&gt;

&lt;p&gt;You can also connect with me on my new &lt;strong&gt;YouTube channel&lt;/strong&gt;: &lt;a href="https://www.youtube.com/@SebastianClavijoSuero" rel="noopener noreferrer"&gt;https://www.youtube.com/@SebastianClavijoSuero&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you'd like to support my work, consider buying me a coffee or contributing to a training session, so I can keep learning and sharing cool stuff with all of you.&lt;br&gt;
Thank you for your support!&lt;br&gt;
&lt;a href="https://www.buymeacoffee.com/sclavijosuero" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.buymeacoffee.com%2Fbuttons%2Fv2%2Fdefault-yellow.png" alt="Buy Me A Coffee" width="545" height="153"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cypress</category>
      <category>qa</category>
      <category>automation</category>
      <category>testing</category>
    </item>
    <item>
      <title>WICK-A11Y v3.0.1: Cypress v16 Ready - Upgrade Without Fear (Fully Backward Compatible)</title>
      <dc:creator>Sebastian Clavijo Suero</dc:creator>
      <pubDate>Sun, 15 Mar 2026 00:15:52 +0000</pubDate>
      <link>https://forem.com/sebastianclavijo/wick-a11y-v301-cypress-v16-ready-upgrade-without-fear-fully-backward-compatible-77m</link>
      <guid>https://forem.com/sebastianclavijo/wick-a11y-v301-cypress-v16-ready-upgrade-without-fear-fully-backward-compatible-77m</guid>
      <description>&lt;h1&gt;
  
  
  ACT 1: EXPOSITION
&lt;/h1&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;Ok, Cypress v16 is coming, and our always-friendly Cypress environment variables are going to be revamped.&lt;/p&gt;

&lt;p&gt;Why? Security reasons. &lt;code&gt;Cypress.env()&lt;/code&gt; variables are exposed in the browser, so they could potentially be accessed by "anyone."&lt;/p&gt;

&lt;p&gt;Starting in Cypress v15.10.0, two new types of environment variables have been introduced: &lt;code&gt;cy.env()&lt;/code&gt; (for private stuff) and &lt;code&gt;Cypress.expose()&lt;/code&gt; (for non-sensitive information). The almighty &lt;code&gt;Cypress.env()&lt;/code&gt; has been deprecated and &lt;strong&gt;will be gone starting in Cypress v16&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This article is not meant to explain what the change is in detail, or how you can migrate your existing Cypress framework to the new environment model. For that, you can (and will) find extremely helpful information in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cypress official documentation: &lt;a href="https://docs.cypress.io/app/references/migration-guide" rel="noopener noreferrer"&gt;Migrating away from Cypress.env()&lt;/a&gt; and &lt;a href="https://docs.cypress.io/app/guides/environment-variables" rel="noopener noreferrer"&gt;Environment Variables &amp;amp; Secrets&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Gleb Bahmutov's article: &lt;a href="https://glebbahmutov.com/blog/cypress-expose/" rel="noopener noreferrer"&gt;Migrating From Cypress.env To cy.env and Cypress.expose Methods&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, because of this change, many open-source community plugins that rely on env variables for configuration or usage will need to be updated for the new environment variable model.&lt;/p&gt;

&lt;p&gt;And the powerful and beloved plugin WICK-A11Y for accessibility in Cypress is no different, because it uses a couple of environment variables for configuration: &lt;code&gt;enableAccessibilityVoice&lt;/code&gt; and &lt;code&gt;generateReport&lt;/code&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  ACT 2: CONFRONTATION
&lt;/h1&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;Since both of those environment variables are not storing sensitive information, they totally could be exposed ones. No biggie.&lt;/p&gt;

&lt;p&gt;However, in your &lt;strong&gt;wick-a11y&lt;/strong&gt; configuration inside your Cypress framework, you might have configured those variables in your &lt;code&gt;cypress.env.json&lt;/code&gt; file, just like you may have done with all the other env variables in your system. Yeah, why not!&lt;/p&gt;

&lt;p&gt;The thing is, &lt;strong&gt;in Cypress’s new env variable model, anything configured in cypress.env.json is automatically considered a private env variable.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That means it would need to be read with &lt;code&gt;cy.env()&lt;/code&gt;, which is actually a Cypress command that can only be executed within a hook or a test.&lt;/p&gt;

&lt;p&gt;So yeah, forget about accessing anything you defined in your &lt;code&gt;cypress.env.json&lt;/code&gt; synchronously at the beginning of your test suite using the old &lt;code&gt;Cypress.env()&lt;/code&gt; like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Cypress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;apiUrl&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Will disappear&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And no, you cannot do this either:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;apiUrl&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Ouch. Right, it is a Cypress command!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can only access environment variables asynchronously if they were defined as exposed variables in &lt;code&gt;cypress.config.js&lt;/code&gt; under the &lt;code&gt;expose&lt;/code&gt; property, or passed through the CLI with the &lt;code&gt;--expose&lt;/code&gt; parameter.&lt;/p&gt;

&lt;p&gt;Yeah... it makes sense, but this is not clearly warned anywhere in the Cypress documentation, so I learned it the hard way while migrating wick-a11y to be ready for Cypress v16.&lt;/p&gt;

&lt;p&gt;And that was... interesting.&lt;/p&gt;

&lt;p&gt;The thing is, &lt;strong&gt;wick-a11y&lt;/strong&gt;, to do its wicked magic, accesses those two environment variables (&lt;code&gt;enableAccessibilityVoice&lt;/code&gt; and &lt;code&gt;generateReport&lt;/code&gt;) inside a Cypress event, not inside a Cypress hook or test.&lt;/p&gt;

&lt;p&gt;This means they cannot be accessed using the &lt;code&gt;cy.env()&lt;/code&gt; command, and instead have to be accessed synchronously with &lt;code&gt;Cypress.expose()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Again, no biggie. I can make them exposed variables, but this might become an little inconvenience for &lt;strong&gt;YOU&lt;/strong&gt;, my friend, using wick-a11y.&lt;/p&gt;

&lt;p&gt;If you have configured those env variables in your wick-a11y implementation inside &lt;code&gt;cypress.env.json&lt;/code&gt; and/or passed them through the CLI as &lt;code&gt;--env&lt;/code&gt; parameters, then for wick-a11y to use them as exposed variables, you would need to move them into &lt;code&gt;cypress.config.js&lt;/code&gt; or pass them through the CLI as &lt;code&gt;--expose&lt;/code&gt; parameters instead.&lt;/p&gt;

&lt;p&gt;And that means changing your CLI script definitions, messing around with your Cypress configuration, and cleaning up your &lt;code&gt;cypress.env.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To avoid that, I prepared the wick-a11y accessibility plugin to accept both environment variables (&lt;code&gt;enableAccessibilityVoice&lt;/code&gt; and &lt;code&gt;generateReport&lt;/code&gt;) as either exposed or non-exposed. And that’s totally fine, because these are just configuration variables, no secrets hiding in the shadows here.&lt;/p&gt;

&lt;p&gt;That means... &lt;strong&gt;you can migrate your project to Cypress v15.10.0 (or v16.0 when it comes out) while using the &lt;code&gt;wick-a11y&lt;/code&gt; plugin WITHOUT ANY CHANGE in your Cypress framework.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;WICK-A11Y v3.0.1 is fully backward compatible for Cypress v15.10.0 projects moving forward.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No fear!&lt;/strong&gt; 🙂&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note that wick-a11y v3.0.1 is not intended for Cypress versions prior to 15.10.0.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  ACT3: RESOLUTION
&lt;/h1&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;So, if you are already on Cypress v15.10.0+ and using the open-source wick-a11y accessibility plugin for Cypress... come on... install wick-a11y v3.0.1!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;More cool features are coming soon in wick-a11y v3.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I also have updated the &lt;a href="https://github.com/sclavijosuero/wick-a11y-sample-project" rel="noopener noreferrer"&gt;wick-a11y-sample-project&lt;/a&gt; to use Cypress v15.11.0 and wick-a11y v3.0.1&lt;/p&gt;

&lt;p&gt;Cheers!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;strong&gt;I'd love to hear from you! Please don't forget to follow me, leave a comment, or a reaction if you found this article useful or insightful.&lt;/strong&gt;&lt;/em&gt; ❤️ 🦄 🤯 🙌 🔥&lt;/p&gt;

&lt;p&gt;You can also connect with me on my new &lt;strong&gt;YouTube channel&lt;/strong&gt;: &lt;a href="https://www.youtube.com/@SebastianClavijoSuero" rel="noopener noreferrer"&gt;https://www.youtube.com/@SebastianClavijoSuero&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you'd like to support my work, consider buying me a coffee or contributing to a training session, so I can keep learning and sharing cool stuff with all of you.&lt;br&gt;
Thank you for your support!&lt;br&gt;
&lt;a href="https://www.buymeacoffee.com/sclavijosuero" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.buymeacoffee.com%2Fbuttons%2Fv2%2Fdefault-yellow.png" alt="Buy Me A Coffee" width="545" height="153"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cypress</category>
      <category>qa</category>
      <category>a11y</category>
      <category>automation</category>
    </item>
    <item>
      <title>Cypress.Promise.all() and cy.mapChain(): Two Hidden Gems for Cypress You Won’t Let Go Once You Try Them</title>
      <dc:creator>Sebastian Clavijo Suero</dc:creator>
      <pubDate>Wed, 21 Jan 2026 03:55:43 +0000</pubDate>
      <link>https://forem.com/sebastianclavijo/cypresspromiseall-and-cymapchain-two-hidden-gems-for-cypress-you-wont-let-go-once-you-try-20mj</link>
      <guid>https://forem.com/sebastianclavijo/cypresspromiseall-and-cymapchain-two-hidden-gems-for-cypress-you-wont-let-go-once-you-try-20mj</guid>
      <description>&lt;p&gt;&lt;em&gt;&lt;strong&gt;Cleaner async patterns for sharper Cypress tests!&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  ACT 1: EXPOSITION
&lt;/h1&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;Most of us (if not all) are familiar with the JavaScript Array &lt;code&gt;map()&lt;/code&gt; method. There is hardly anything better when you want to process or transform every element of an array into a new one based on a transformation function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// Expected output: Array [3, 11, 27, 51]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example is trivial, of course, but that transformation function can be anything, from a simple calculation to something like a 90° clockwise matrix rotation. You name it!&lt;/p&gt;

&lt;p&gt;I am sure many of us are also familiar with a very similar concept, but this time in the &lt;strong&gt;Cypress&lt;/strong&gt; world. I am talking about the fantastic &lt;code&gt;cy.map()&lt;/code&gt; command from the multifaceted &lt;a href="https://github.com/bahmutov/cypress-map" rel="noopener noreferrer"&gt;cypress-map&lt;/a&gt; plugin by &lt;a href="https://www.linkedin.com/in/bahmutov" rel="noopener noreferrer"&gt;Gleb Bahmutov&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You pass a Cypress chainable (yielding an array or a jQuery object) as the subject, apply a JavaScript function to its yielded value, and it returns a new chainable whose elements are the result of that transformation. It can also map each object directly to one of its properties.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// Expected output: Cypress Chainable [3, 11, 27, 51]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But what if what you want to apply to all the elements of the subject chainable is not a function, but another Cypress command? Or worse… what if you want to merge the results of multiple custom commands into a single output?&lt;/p&gt;

&lt;p&gt;Yeah… now we’re talking. 😏&lt;/p&gt;




&lt;h1&gt;
  
  
  ACT 2: CONFRONTATION
&lt;/h1&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;A few months ago, I was working on a new plugin to handle internationalized messages in a Cypress framework (I will probably write an article about it in the future).&lt;/p&gt;

&lt;p&gt;Without going into too much detail, it leverages the powerful JavaScript library &lt;strong&gt;i18next&lt;/strong&gt; to manage multilingual message files, fallback languages, fallback translations, and many other features that simplify the often cumbersome task of multilingual management.&lt;/p&gt;

&lt;p&gt;To provide a bit of context, translations for a given message ID and its arguments were returned by a Cypress custom command in the plugin, along with support and helper utilities for multilingual assertions.&lt;/p&gt;

&lt;p&gt;You define all the translations of a message for each supported language, and then use a command like &lt;code&gt;cy.getIntlMessage()&lt;/code&gt; to retrieve the appropriate one from those resources for the desired language.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Messages definition&lt;/span&gt;
&lt;span class="nx"&gt;i18next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-US &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-US&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;translation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hello&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my.age&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;My age… {{age}} years!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tell.me.your.age&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;How old are you, {{name}}?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;es-ES&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;translation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hello&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hola.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my.age&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Mi edad… ¡{{age}} años!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tell.me.your.age&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;¿Cual es tu edad, {{name}}?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;el-GR&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;translation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hello&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Γεια σου.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my.age&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Η ηλικία μου… {{age}} χρονών!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tell.me.your.age&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Πόσων χρονών είσαι, {{name}};&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="c1"&gt;// Rest of languages [...]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// Rest of i18next properties [...]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So a call to &lt;code&gt;cy.getIntlMessage()&lt;/code&gt; like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// "my.age" is the message key, age is the interpolation key&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getIntlMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my.age&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;es-ES&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It would yield a Cypress chainable holding the value &lt;strong&gt;&lt;code&gt;"Mi edad… ¡100 años!"&lt;/code&gt;&lt;/strong&gt; &lt;em&gt;(in plain English: "My age… 100 years!")&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// "tell.me.your.age" is the message key, name is he interpolation key&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getIntlMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tell.me.your.age&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;el-GR&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Toúla&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It would yield a Cypress chainable holding the text &lt;strong&gt;&lt;code&gt;"Πόσο χρονών είσαι, Toúla;"&lt;/code&gt;&lt;/strong&gt; &lt;em&gt;(or in English: "What's your age, Toúla?")&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Nice, right?&lt;/p&gt;

&lt;p&gt;Now let’s say we need to concatenate multiple internationalized messages into a single string when the translations come from a custom command, maybe to make an assertion or to fill an input field.&lt;/p&gt;

&lt;p&gt;How would you do it?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Hmmm..."&lt;/em&gt; 🤔&lt;br&gt;
 &lt;/p&gt;
&lt;h2&gt;
  
  
  Using The All Times "Classic" Way
&lt;/h2&gt;

&lt;p&gt;Given that Cypress code runs asynchronously in its own command queue, the chainable returned by a Cypress command &lt;strong&gt;cannot&lt;/strong&gt; be stored in a regular JavaScript variables like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Forget about it! 🚫&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myAge&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getIntlMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my.age&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;es-ES&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tellMeYourAge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getIntlMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tell.me.your.age&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;el-GR&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Toúla&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a big &lt;strong&gt;no-no&lt;/strong&gt; in Cypress, and if you somehow still don’t believe me, check out my article "&lt;a href="https://dev.to/sebastianclavijo/the-async-nature-of-cypress-dont-mess-with-the-timelines-in-your-cypress-tests-dual-verse-3ehh"&gt;The Async Nature of Cypress: Don't Mess with the Timelines in Your Cypress Tests 'Dual-Verse'&lt;/a&gt;".&lt;/p&gt;

&lt;p&gt;Sure, you can store the value in a Cypress alias or yield it to the next chained command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Stored in an alias (a single message)&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getIntlMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my.age&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;es-ES&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;myAgeMsg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But how can you combine two messages returned by &lt;code&gt;cy.getIntlMessage()&lt;/code&gt; into a single one?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Why, you ask? That should be pretty easy-peasy!"&lt;/em&gt; 🥱&lt;/p&gt;

&lt;p&gt;Yeah… fair enough. I bet most Cypress automation folks would instinctively write something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getIntlMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my.age&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;es-ES&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message1&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getIntlMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tell.me.your.age&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;el-GR&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Toúla&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message2&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;message1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;message2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;// Output in Cypress log: "Mi edad… ¡100 años! Πόσων χρονών είσαι, Toúla;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The classic way!&lt;/p&gt;

&lt;p&gt;Or, if you decide to go the alias route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getIntlMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my.age&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;es-ES&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;myAgeMsg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getIntlMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tell.me.your.age&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;el-GR&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Toúla&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tellMeYourName&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;myAgeMsg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message1&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tellMeYourName&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message2&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;message1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;message2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;// Output in Cypress log: "Mi edad… ¡100 años! Πόσων χρονών είσαι, Toúla;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We could even create a custom Cypress command to simplify the concatenation, something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Command to concat intl messages&lt;/span&gt;
&lt;span class="nx"&gt;Cypress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;concatIntlMessages_classic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arg1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;arg2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getIntlMessage&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;arg1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg1&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getIntlMessage&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;arg2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg2&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;msg1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;msg2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concatIntlMessages_classic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my.age&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;es-ES&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tell.me.your.age&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;el-GR&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Toúla&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;// Output in Cypress log: "Mi edad… ¡100 años! Πόσων χρονών είσαι, Toúla;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the Cypress command, the test code looks &lt;strong&gt;cleaner&lt;/strong&gt;. Very nice!&lt;/p&gt;

&lt;p&gt;Ehhh… I’d say &lt;strong&gt;cleaner-ish&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Why is that?"&lt;/em&gt; 🤨&lt;/p&gt;

&lt;p&gt;And here is where I become the buzzkill…&lt;br&gt;
What if it is not two messages, but three?&lt;br&gt;
Four?&lt;br&gt;
Five?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Hmmm… just keep going. Nest as many .then() calls as you need!"&lt;/em&gt; 🥱🥱&lt;/p&gt;

&lt;p&gt;So… let me get this straight.&lt;br&gt;
You are suggesting we create a command chain that just &lt;strong&gt;keeps going&lt;/strong&gt;…?&lt;br&gt;
Something that, for &lt;em&gt;four&lt;/em&gt; messages, looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Cypress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;concatIntlMessages_classic_fantastic4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arg1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;arg2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;arg3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;arg4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getIntlMessage&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;arg1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg1&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getIntlMessage&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;arg2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg2&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
          &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getIntlMessage&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;arg3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg3&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
              &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getIntlMessage&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;arg4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg4&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;msg1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;msg2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;msg3&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;msg4&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concatIntlMessages_classic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hello&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en-US&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; 🙂 &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my.age&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;es-ES&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tell.me.your.age&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;el-GR&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Toúla&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;// Output in Cypress log: "Hello. 🙂 Mi edad… ¡100 años! Πόσων χρονών είσαι, Toúla;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I do not think so. I’m pretty sure we can do better than that.&lt;br&gt;
 &lt;/p&gt;
&lt;h2&gt;
  
  
  Using Cypress.Promise.all()
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Cypress.Promise.all&lt;/code&gt; is Cypress’s version of &lt;code&gt;Promise.all&lt;/code&gt;, built on Bluebird. It lets you run multiple asynchronous operations at the same time and waits until &lt;strong&gt;all&lt;/strong&gt; of them finish (or stops as soon as &lt;strong&gt;one&lt;/strong&gt; fails).&lt;/p&gt;

&lt;p&gt;You can learn about &lt;code&gt;Cypress.Promise&lt;/code&gt; utility in the Cypress product documentation &lt;a href="https://docs.cypress.io/api/utilities/promise" rel="noopener noreferrer"&gt;https://docs.cypress.io/api/utilities/promise&lt;/a&gt;. However, you will not find dedicated section showing &lt;code&gt;Cypress.Promise.all&lt;/code&gt;, so let’s talk about that here.&lt;/p&gt;

&lt;p&gt;At its core, Bluebird’s &lt;code&gt;Promise.all()&lt;/code&gt; takes an iterable (usually an array) of Promises and returns one single Promise:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If &lt;strong&gt;every&lt;/strong&gt; Promise resolves, the returned Promise resolves with an array of results (same order as the input).&lt;/li&gt;
&lt;li&gt;If &lt;strong&gt;any&lt;/strong&gt; Promise rejects, the returned Promise immediately rejects with the first error.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And that is &lt;strong&gt;exactly&lt;/strong&gt; what we need to concatenate multiple translated messages returned by a Cypress command: gather them all, then merge them into a single string. After all, Cypress chainables &lt;em&gt;walk&lt;/em&gt;, &lt;em&gt;talk&lt;/em&gt;, &lt;em&gt;and quack like promises&lt;/em&gt;. And that is precisely the universe in which &lt;code&gt;Cypress.Promise.all&lt;/code&gt; was born. It takes an array of Cypress commands invocations, and returns a Premise that resolves to an array containing all resolved results, in order.&lt;/p&gt;

&lt;p&gt;So let’s summon a custom command that does what we need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Cypress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;concatIntlMessages_promiseAll&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;Cypress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;log&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the test code that uses it ends up looking something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concatIntlMessages_promiseAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getIntlMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hello&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en-US&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; 🙂 &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getIntlMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my.age&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;es-ES&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getIntlMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tell.me.your.age&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;el-GR&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Toúla&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;// Output in Cypress log: "Hello. 🙂 Mi edad… ¡100 años! Πόσων χρονών είσαι, Toúla;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s break this thing down plain and simple.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;In the test code, we pass to our custom command &lt;code&gt;cy.concatIntlMessages_promiseAll()&lt;/code&gt; a mix of inputs: several &lt;code&gt;cy.getIntlMessage()&lt;/code&gt; chainables and even plain synchronous values (like [" 🙂 "] - why not?).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Cypress.Promise.all(commands)&lt;/code&gt; waits until every item has "settled" into a resolved value, then yields an array of results in the same order.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Finally, our &lt;code&gt;.then((text) =&amp;gt; text.join(' '))&lt;/code&gt; concatenates that array into a single string, which is yielded as the result of the custom command.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;"There you go!"&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmzo8dmemwplte1ax6mlw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmzo8dmemwplte1ax6mlw.png" alt=" " width="490" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;DISCLAIMER: I borrowed the line from &lt;em&gt;My Big Fat Greek Wedding&lt;/em&gt; and the image from &lt;em&gt;The New York Times&lt;/em&gt; &lt;a href="https://www.nytimes.com/2021/09/08/arts/michael-constantine-father-in-my-big-fat-greek-wedding-dies-at-94.html" rel="noopener noreferrer"&gt;article&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Anything else?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Please, be patient, we are not quite done yet.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Using cy.mapChain()
&lt;/h2&gt;

&lt;p&gt;This Cypress command, &lt;code&gt;cy.mapChain()&lt;/code&gt;, is available in the &lt;a href="https://github.com/bahmutov/cypress-map" rel="noopener noreferrer"&gt;cypress-map&lt;/a&gt; plugin, and it was not familiar to me until quite recently. Once I stumbled upon it, I was pretty upset I had not discovered it earlier. That is what happens when &lt;strong&gt;you don’t read a plugin’s documentation all the way to the end&lt;/strong&gt;. You miss these little gems!&lt;/p&gt;

&lt;p&gt;After this "mea culpa", we are going to know what this command ("&lt;em&gt;not a query!&lt;/em&gt;" as the own author stated) does:&lt;/p&gt;

&lt;p&gt;It processes each element of the provided array-like chainable (an array or a jQuery object) individually, applies &lt;em&gt;&lt;strong&gt;Cypress command&lt;/strong&gt; functions&lt;/em&gt; to each one, and returns an array-like chainable with the same number of elements.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"What?! Apply a Cypress command? Not a regular function?"&lt;/em&gt; 🫨&lt;/p&gt;

&lt;p&gt;Actually, you can also use &lt;em&gt;synchronous&lt;/em&gt;, or even &lt;em&gt;asynchronous&lt;/em&gt;, functions as well! 🤯&lt;/p&gt;

&lt;p&gt;Cool, right? So, in a few words, it is like a &lt;code&gt;cy.map()&lt;/code&gt; command whose mapping function can run Cypress commands, and it does not yield a result until all elements have been processed.&lt;/p&gt;

&lt;p&gt;And to me, it seems like we could use this precisely to concatenate the results of our multiple &lt;code&gt;cy.getIntlMessage()&lt;/code&gt; calls.&lt;/p&gt;

&lt;p&gt;So the new custom command, and its invocation in a test, could look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Custom command using cy.mapChain()&lt;/span&gt;
&lt;span class="nx"&gt;Cypress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;concatIntlMessages_mapChain&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// args should be arrays like ["hello", {lng:...}]&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;log&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mapChain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arg&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getIntlMessage&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Command invocation&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concatIntlMessages_mapChain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hello&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en-US&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; 🙂 &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my.age&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;es-ES&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tell.me.your.age&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;el-GR&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Toúla&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
 &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;// Output in Cypress log: "Hello. 🙂 Mi edad… ¡100 años! Πόσων χρονών είσαι, Toúla;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;In the test code, we call &lt;code&gt;cy.concatIntlMessages_mapChain()&lt;/code&gt; with several arguments, each representing the input needed to retrieve an internationalized message (for example, a message key, its language and options). These arguments define what messages we want and in which order.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Inside the custom command, &lt;code&gt;cy.mapChain()&lt;/code&gt; is the key player. It takes that wrapped list of arguments and processes them one by one, running a Cypress command &lt;code&gt;cy.getIntlMessage()&lt;/code&gt; for each element instead of a plain JavaScript function.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Finally, once &lt;code&gt;mapChain()&lt;/code&gt; has processed all elements, the resulting array of messages is joined into a single string with &lt;code&gt;.then((text) =&amp;gt; text.join(' '))&lt;/code&gt;, and yielded as the result of the custom command.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;cy.mapChain()&lt;/code&gt; waits until all those Cypress commands have finished and then yields an array with all the resolved values, in the same order, to be combined in this case in single message string.&lt;/p&gt;

&lt;p&gt;And again... "there you go!"&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmzo8dmemwplte1ax6mlw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmzo8dmemwplte1ax6mlw.png" alt=" " width="490" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;⚠️ DISCLAIMER: Yeah, sorry! Using the same image twice in the same article might be a bit much for some, but I couldn’t help myself. I really love the movie, and especially Gus Portokalos. Still, probably not quite as powerful as what we’ll get in &lt;em&gt;John Wick: Chapter 5&lt;/em&gt;. 😄&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Ok, fine. You win."&lt;/em&gt; 😜&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;




&lt;h1&gt;
  
  
  ACT3: RESOLUTION
&lt;/h1&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;Ok, we have finally reached the end of our Greek movie detour, where we learned new expressions in all three languages and, along the way, a couple of really &lt;em&gt;útiles&lt;/em&gt; Cypress &lt;em&gt;εργαλεία&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Cypress.Promise.all()&lt;/code&gt; is ideal when we want to run multiple Cypress commands and wait for all of them to finish, with all the yielded values returned as an array-like chainable.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cy.mapChain()&lt;/code&gt; excels when we want to apply a custom command to all the elements of an array-like Cypress chainable and yield the results, also as an array-like chainable.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Cypress.Promise.all()&lt;/code&gt; and &lt;code&gt;cy.mapChain()&lt;/code&gt;, although quite different in how they work, both helped solve the same problem, just with a little imagination.&lt;/p&gt;

&lt;p&gt;One approaches it from the Promise side, coordinating multiple async operations and resolving them together, while the other stays firmly in Cypress territory, transforming values as they move through the command chain. Different mental models, same outcome: &lt;strong&gt;cleaner&lt;/strong&gt;, &lt;strong&gt;more expressive tests&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You can find the examples of this article in GitHub &lt;a href="https://github.com/sclavijosuero/cypressPromise-mapChain" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now it is up to you to decide how you want to use them to make your automation experience a bit more &lt;em&gt;pleasant&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Or maybe not... Your call, no judgment. 😊&lt;/p&gt;

&lt;p&gt;And... "&lt;strong&gt;THERE YOU GO&lt;/strong&gt;!!!" (one last time)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmskhqfdyetjmqmi8100x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmskhqfdyetjmqmi8100x.png" alt=" " width="192" height="191"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;😆&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;strong&gt;I'd love to hear from you! Please don't forget to follow me, leave a comment, or a reaction if you found this article useful or insightful.&lt;/strong&gt;&lt;/em&gt; ❤️ 🦄 🤯 🙌 🔥&lt;/p&gt;

&lt;p&gt;You can also connect with me on my new &lt;strong&gt;YouTube channel&lt;/strong&gt;: &lt;a href="https://www.youtube.com/@SebastianClavijoSuero" rel="noopener noreferrer"&gt;https://www.youtube.com/@SebastianClavijoSuero&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you are feeling especially generous and enjoy my articles, you can buy me a coffee or contribute to a training session. In both cases, my brain will definitely thank you for it! ☕😄 &lt;br&gt;
&lt;a href="https://www.buymeacoffee.com/sclavijosuero" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.buymeacoffee.com%2Fbuttons%2Fv2%2Fdefault-yellow.png" alt="Buy Me A Coffee" width="545" height="153"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cypress</category>
      <category>qa</category>
      <category>automation</category>
    </item>
    <item>
      <title>CYPRESS-FLAKY-TEST-AUDIT: thriving in the Cypress 'Dual-Verse' for once!</title>
      <dc:creator>Sebastian Clavijo Suero</dc:creator>
      <pubDate>Fri, 02 Jan 2026 01:37:00 +0000</pubDate>
      <link>https://forem.com/sebastianclavijo/cypress-flaky-testaudit-thriving-in-the-cypress-dual-verse-for-once-l4o</link>
      <guid>https://forem.com/sebastianclavijo/cypress-flaky-testaudit-thriving-in-the-cypress-dual-verse-for-once-l4o</guid>
      <description>&lt;p&gt;When flaky tests stop being &lt;em&gt;random&lt;/em&gt; and start being a &lt;em&gt;pattern&lt;/em&gt;, it is time to stop guessing and start auditing. This is the story of how to make peace with &lt;em&gt;Cypress’ Dual-Verse&lt;/em&gt;... and actually win.&lt;/p&gt;




&lt;h1&gt;
  
  
  ACT 1: EXPOSITION
&lt;/h1&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;At some point in every Cypress project, a test fails.&lt;/p&gt;

&lt;p&gt;You re-run it.&lt;br&gt;
It passes.&lt;/p&gt;

&lt;p&gt;You shrug, blame CI, and move on with your life.&lt;/p&gt;

&lt;p&gt;Then it happens again. And again. And somehow only on Tuesdays, only in CI, or only when Mercury is in retrograde.&lt;/p&gt;

&lt;p&gt;Welcome back to the Cypress Dual-Verse: the place where synchronous JavaScript and Cypress’ asynchronous command queue coexist peacefully… until they absolutely don’t.&lt;/p&gt;

&lt;p&gt;If this sounds familiar, you are not alone. In one of my previous articles, I went deep (very deep) into why Cypress behaves this way and how mixing sync JS with async commands can quietly sabotage your tests:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://dev.to/sebastianclavijo/the-async-nature-of-cypress-dont-mess-with-the-timelines-in-your-cypress-tests-dual-verse-3ehh"&gt;The Async Nature of Cypress: Don’t Mess With the Timelines&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I definitely wanted to write an article about how this asynchronous command queue works, but I wanted to approach it in a way that is easier to understand and more intuitive, without sacrificing depth or the practical knowledge you actually need to tackle it effectively.&lt;/p&gt;

&lt;p&gt;That is when I came up with the idea of presenting it as a quirky 'dual-universe' concept, using a very visual diagram so you can clearly see what is going on behind the scenes.&lt;/p&gt;

&lt;p&gt;For a long time after publishing the article, a (somewhat crazy) idea kept spinning in my head… What if I could see a visual representation while implementing and running those rebellious tests that keep failing or behaving flakily, tests where I can’t quite put my finger on why, but I strongly suspect (and I’m usually right) that race conditions are to blame?&lt;/p&gt;

&lt;p&gt;But man! This is not an easy task. It would mean diving deep into low-level test details and command interactions, then somehow turning all that chaos into something clear, understandable, and fully portable to a CI/CD pipeline.&lt;/p&gt;

&lt;p&gt;And why stop there? Why not visualize it all with intuitive graphs (like in the 'dual-verse' article) that the human brain can actually enjoy digesting?&lt;/p&gt;

&lt;p&gt;After all… you know the saying: a 🖼️ is worth more than a thousand long 📜📜📜. 😅&lt;/p&gt;

&lt;p&gt;That is where CYPRESS-FLAKY-TEST-AUDIT plugin comes in.&lt;/p&gt;




&lt;h1&gt;
  
  
  ACT 2: CONFRONTATION
&lt;/h1&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;Flaky tests &lt;strong&gt;rarely announce themselves politely&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;They &lt;em&gt;don’t fail everywhere&lt;/em&gt;.&lt;br&gt;
They &lt;em&gt;don’t fail consistently&lt;/em&gt;.&lt;br&gt;
And they definitely &lt;em&gt;don’t fail when you are watching&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Most of the time, the root cause is subtle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cypress commands mixed with sync control flow&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;State&lt;/em&gt; captured too early&lt;/li&gt;
&lt;li&gt;Variables that &lt;em&gt;look&lt;/em&gt; populated… but aren’t (yet)&lt;/li&gt;
&lt;li&gt;Assertions running outside the Cypress command chain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words: &lt;strong&gt;Dual-Verse violations&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The problem? These patterns are easy to write and painfully hard to spot, especially once your test suite grows beyond a handful of specs, or when you are staring at code written by someone else.&lt;br&gt;
 &lt;/p&gt;

&lt;h2&gt;
  
  
  Presenting &lt;code&gt;cypress-flaky-test-audit&lt;/code&gt;!
&lt;/h2&gt;

&lt;p&gt;This plugin does one simple but powerful thing:&lt;/p&gt;

&lt;p&gt;It scans your Cypress tests as they run and shows you (clearly and intuitively, no kidding!) what is running at every moment and exactly where things go wrong, &lt;strong&gt;making flakiness-prone patterns much easier to spot&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Not &lt;em&gt;hypotheticals&lt;/em&gt;.&lt;br&gt;
Not &lt;em&gt;style opinions&lt;/em&gt;.&lt;br&gt;
Real "this will bite you later" code smells.&lt;/p&gt;

&lt;p&gt;And as someone wise once said…&lt;br&gt;
"&lt;em&gt;No gimmicks. No AI. Just the information you need to understand why a test is flaky&lt;/em&gt;". 🙂&lt;/p&gt;

&lt;p&gt;It &lt;em&gt;doesn’t rewrite your tests&lt;/em&gt;.&lt;br&gt;
It &lt;em&gt;doesn’t block your CI&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;It simply shines a flashlight into the dark corners of your test suite.&lt;/p&gt;

&lt;p&gt;You can run it locally, in CI, or as part of a quality gate, and decide how strict you want to be.&lt;br&gt;
 &lt;/p&gt;

&lt;h2&gt;
  
  
  What the plugin &lt;em&gt;actually&lt;/em&gt; does (and how that helps you)
&lt;/h2&gt;

&lt;p&gt;Unlike a static pattern detector or flake predictor, &lt;strong&gt;cypress-flaky-test-audit&lt;/strong&gt; gives you &lt;strong&gt;runtime insights into what your tests *actually did&lt;/strong&gt;*, command by command.&lt;/p&gt;

&lt;p&gt;In other words, it does not guess where flakiness &lt;em&gt;might&lt;/em&gt; be; it shows you exactly what happened when your tests ran.&lt;/p&gt;

&lt;p&gt;At a practical level, it provides:&lt;/p&gt;

&lt;h3&gt;
  
  
  🧠 Per-command execution details
&lt;/h3&gt;

&lt;p&gt;For every Cypress command in a run, you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the exact &lt;strong&gt;execution order&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;timing information&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;how many &lt;strong&gt;retries&lt;/strong&gt; happened&lt;/li&gt;
&lt;li&gt;whether each command &lt;strong&gt;passed or failed&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can immediately see which commands took longer than expected, retried often, or didn’t execute at all. This is much more actionable than a static linter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it helps:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
When a test flops in CI but works locally, you can compare these metrics and see &lt;em&gt;what changed&lt;/em&gt;, instead of guessing.&lt;/p&gt;

&lt;h3&gt;
  
  
  ⏱ Timing and latency observations
&lt;/h3&gt;

&lt;p&gt;The plugin highlights command durations — which can be telling when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;network responses are slow&lt;/li&gt;
&lt;li&gt;UI takes time to load&lt;/li&gt;
&lt;li&gt;intermittent server delays impact your tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why it helps:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Instead of "this fails sometimes", you see "this command took 4× longer in CI than locally."&lt;/p&gt;

&lt;h3&gt;
  
  
  🔁 Retry tracking
&lt;/h3&gt;

&lt;p&gt;Cypress retries certain commands automatically, and you can now see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;which commands retried&lt;/li&gt;
&lt;li&gt;how many times&lt;/li&gt;
&lt;li&gt;whether retries &lt;em&gt;eventually succeeded or still failed&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is especially useful for flaky assertions or slow elements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it helps:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
You will stop writing defensive &lt;code&gt;cy.wait()&lt;/code&gt; hacks and start understanding &lt;em&gt;when and why&lt;/em&gt; Cypress is retrying under the hood.&lt;/p&gt;

&lt;h3&gt;
  
  
  🚦 Clear pass/fail status per command
&lt;/h3&gt;

&lt;p&gt;Every command is annotated with its success/failure state in the audit output, giving you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;confidence this test truly passed&lt;/li&gt;
&lt;li&gt;insight into hidden failures that were masked by retries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why it helps:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
No more "it failed once but passed twice so I think it’s fine". You &lt;em&gt;actually see the results&lt;/em&gt;, not just the final green light.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔍 Comparing retries within the same test run
&lt;/h3&gt;

&lt;p&gt;And this is my personal favorite!&lt;/p&gt;

&lt;p&gt;One of the most underrated features is the ability to compare what happened between retries of the same test, especially when the first attempt fails and the retry somehow passes (ah yes… CI/CD, we meet again).  😅&lt;/p&gt;

&lt;p&gt;Instead of seeing a single green checkmark and moving on, the audit lets you inspect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what commands ran in the failed attempt&lt;/li&gt;
&lt;li&gt;what commands ran in the successful retry&lt;/li&gt;
&lt;li&gt;timing differences between both executions&lt;/li&gt;
&lt;li&gt;commands that retried more aggressively in one attempt than the other&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why it helps:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Because "it passed on retry" is not a root cause.&lt;/p&gt;

&lt;p&gt;By comparing both attempts side by side, you can spot subtle timing shifts, delayed elements, or network-dependent behavior that only shows up under certain conditions, all without re-running the test or adding guesswork.&lt;/p&gt;

&lt;p&gt;This is where flaky tests stop being mysterious and start being &lt;em&gt;debuggable&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In short, &lt;strong&gt;cypress-flaky-test-audit&lt;/strong&gt; &lt;strong&gt;turns your test run into an observable timeline rather than a black box&lt;/strong&gt;. Instead of &lt;em&gt;hoping something is flaky&lt;/em&gt;, you get &lt;strong&gt;real data&lt;/strong&gt; on what Cypress did and where your timing assumptions broke down.&lt;br&gt;
 &lt;/p&gt;

&lt;h2&gt;
  
  
  How this actually helps you (and where you see the results)
&lt;/h2&gt;

&lt;p&gt;All this data would be pointless if it stayed hidden.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CYPRESS-FLAKY-TEST-AUDIT&lt;/strong&gt; is intentionally noisy in the &lt;em&gt;right&lt;/em&gt; places and visual where it matters, so you can consume the results in whatever way fits your workflow.&lt;/p&gt;

&lt;p&gt;Here is how.&lt;br&gt;
 &lt;/p&gt;

&lt;h3&gt;
  
  
  🖥 Browser console output
&lt;/h3&gt;

&lt;p&gt;During test execution, the plugin logs detailed audit information directly in the &lt;strong&gt;browser console&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You can see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;each Cypress command as it executes (in the precise order)&lt;/li&gt;
&lt;li&gt;timing and retry information&lt;/li&gt;
&lt;li&gt;which test retry you are currently looking at&lt;/li&gt;
&lt;li&gt;subtle differences between attempts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why this helps:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
When debugging locally, you don’t need to leave the test runner. You can watch the Dual-Verse unfold in real time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq9blkrrz4n3q0tbffymc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq9blkrrz4n3q0tbffymc.png" alt=" " width="800" height="214"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  🧾 Terminal output (CI-friendly)
&lt;/h3&gt;

&lt;p&gt;The same audit information is also available in the &lt;strong&gt;terminal output&lt;/strong&gt;, making it CI-friendly by default.&lt;/p&gt;

&lt;p&gt;This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no screenshots required&lt;/li&gt;
&lt;li&gt;no guesswork when a test flakes in CI&lt;/li&gt;
&lt;li&gt;actionable logs even when the test eventually passes on retry&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why this helps:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Because flaky tests don’t stop being flaky just because the pipeline turned green.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9tu6juv9w9k4iftgwyhe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9tu6juv9w9k4iftgwyhe.png" alt=" " width="800" height="147"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  📊 HTML test audit report (where everything clicks)
&lt;/h3&gt;

&lt;p&gt;This is where things really come together.&lt;/p&gt;

&lt;p&gt;The plugin generates an &lt;strong&gt;HTML audit report&lt;/strong&gt; that visualizes for a test suite:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;every test&lt;/li&gt;
&lt;li&gt;every retry&lt;/li&gt;
&lt;li&gt;every command&lt;/li&gt;
&lt;li&gt;execution timelines per attempt&lt;/li&gt;
&lt;li&gt;pass/fail status and duration&lt;/li&gt;
&lt;li&gt;what gets executed, and what is never reached&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each retry is displayed individually, and when a test fails first and passes on retry, you can &lt;strong&gt;compare both executions side by side&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff60xp99alnbdrn0kshhr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff60xp99alnbdrn0kshhr.png" alt=" " width="800" height="530"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And yes, if this looks familiar, that is intentional.&lt;/p&gt;

&lt;p&gt;The graphs purposely resemble the ones used in &lt;a href="https://dev.to/sebastianclavijo/the-async-nature-of-cypress-dont-mess-with-the-timelines-in-your-cypress-tests-dual-verse-3ehh"&gt;The Async Nature of Cypress: Don’t Mess with the Timelines in Your Cypress Tests 'Dual-Verse'&lt;/a&gt;, because they are designed to expose exactly the same thing: &lt;strong&gt;&lt;em&gt;what Cypress did VS when you thought it did it&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this helps:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Seeing both timelines next to each other turns flakiness from a feeling into evidence. Timing gaps, delayed commands, and retry-heavy steps stop hiding behind a green checkmark.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fofze2tfdce4pzm1zqqyf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fofze2tfdce4pzm1zqqyf.png" alt=" " width="705" height="783"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In short, the plugin does not just tell you &lt;em&gt;that&lt;/em&gt; a test retried. It shows you &lt;strong&gt;how each retry actually behaved&lt;/strong&gt;, across the browser, the terminal, and a visual report that makes the Dual-Verse impossible to ignore.&lt;/p&gt;

&lt;p&gt;You can interact with a fully functional HTML report here: &lt;a href="https://sclavijosuero.github.io/samples/flaky-demo.html" rel="noopener noreferrer"&gt;https://sclavijosuero.github.io/samples/flaky-demo.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Who this is for (and who it is not)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;This is for you if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You maintain a medium-to-large Cypress test suite.&lt;/li&gt;
&lt;li&gt;You’ve seen tests fail in CI but magically pass locally.&lt;/li&gt;
&lt;li&gt;You’ve ever said "just re-run it" and felt a &lt;em&gt;tiny&lt;/em&gt; bit guilty.&lt;/li&gt;
&lt;li&gt;You want guardrails, not lectures, around Cypress async behavior.&lt;/li&gt;
&lt;li&gt;You’re a &lt;strong&gt;new QA engineer&lt;/strong&gt; getting started with Cypress &lt;em&gt;or&lt;/em&gt; a &lt;strong&gt;seasoned professional&lt;/strong&gt;, because we &lt;em&gt;all&lt;/em&gt; write flaky tests at some point, specially after we forget (again) about the Cypress &lt;strong&gt;'Dual-Verse' gotcha&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;This is probably &lt;em&gt;not&lt;/em&gt; for you if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You’re just getting started with Cypress and only have a handful of tests.&lt;/li&gt;
&lt;li&gt;You’re intentionally ignoring Cypress’ command queue (no judgment… okay, maybe a little).&lt;/li&gt;
&lt;li&gt;Your tests are already rock-solid and boring in the best possible way.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  ACT3: RESOLUTION
&lt;/h1&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;The goal here is not to shame your tests.&lt;br&gt;
We have all written code that works perfectly… &lt;strong&gt;until it doesn’t&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The real win is shifting from:&lt;br&gt;
"&lt;em&gt;Why is this flaky?&lt;/em&gt;"&lt;br&gt;
to&lt;br&gt;
"&lt;em&gt;We know exactly where flakiness comes from.&lt;/em&gt;"&lt;/p&gt;

&lt;p&gt;Once you internalize and fully understand the Cypress execution model and back it up with automated auditing, something nice happens:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tests become predictable&lt;br&gt;
CI becomes boring (the good kind)&lt;br&gt;
Failures start meaning something again&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is what I mean by thriving in the Cypress Dual-Verse.&lt;/p&gt;

&lt;p&gt;Not fighting Cypress.&lt;br&gt;
Not outsmarting it.&lt;br&gt;
Just respecting the timeline, and letting a tool point out when you don’t.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where to find it?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;npm: &lt;a href="https://www.npmjs.com/package/cypress-flaky-test-audit" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/cypress-flaky-test-audit&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/sclavijosuero/cypress-flaky-test-audit" rel="noopener noreferrer"&gt;https://github.com/sclavijosuero/cypress-flaky-test-audit&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;&lt;strong&gt;I'd love to hear from you! Please don't forget to follow me, leave a comment, or a reaction if you found this article useful or insightful.&lt;/strong&gt;&lt;/em&gt; ❤️ 🦄 🤯 🙌 🔥&lt;/p&gt;

&lt;p&gt;You can also connect with me on my new &lt;strong&gt;YouTube channel&lt;/strong&gt;: &lt;a href="https://www.youtube.com/@SebastianClavijoSuero" rel="noopener noreferrer"&gt;https://www.youtube.com/@SebastianClavijoSuero&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you'd like to support my work, consider buying me a coffee or contributing to a training session, so I can keep learning and sharing cool stuff with all of you.&lt;br&gt;
Thank you for your support!&lt;br&gt;
&lt;a href="https://www.buymeacoffee.com/sclavijosuero" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.buymeacoffee.com%2Fbuttons%2Fv2%2Fdefault-yellow.png" alt="Buy Me A Coffee" width="545" height="153"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cypress</category>
      <category>qa</category>
      <category>automation</category>
      <category>testing</category>
    </item>
    <item>
      <title>WICK-A11Y v2.3.0: A Dazzling New Report, WCAG 2.2 AAA, and More Goodies</title>
      <dc:creator>Sebastian Clavijo Suero</dc:creator>
      <pubDate>Mon, 08 Sep 2025 07:45:13 +0000</pubDate>
      <link>https://forem.com/sebastianclavijo/wick-a11y-v230-a-dazzling-new-report-wcag-22-aaa-and-more-goodies-hd0</link>
      <guid>https://forem.com/sebastianclavijo/wick-a11y-v230-a-dazzling-new-report-wcag-22-aaa-and-more-goodies-hd0</guid>
      <description>&lt;h1&gt;
  
  
  ACT 1: EXPOSITION
&lt;/h1&gt;

&lt;p&gt;Yes, it was time to make a major improvement to the cooler plugin at the Round Table: the wicked 'Mr. Wick-A11y'.&lt;/p&gt;

&lt;p&gt;So I’m not going to bore you with blah, blah, blah 📜📜📜,... let’s get straight to the point. 🎯&lt;/p&gt;




&lt;h1&gt;
  
  
  ACT 2: CONFRONTATION
&lt;/h1&gt;

&lt;p&gt;There are a bunch of cool new features!&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Support of WCAG 2.2 AAA
&lt;/h2&gt;

&lt;p&gt;Yep, we've got the whole enchilada, enabling accessibility analyses all the way up to WCAG 2.2 AAA (whatever axe-core® supports).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frzs9che2h0sk9vm9hidl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frzs9che2h0sk9vm9hidl.png" alt=" " width="462" height="333"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Image from &lt;a href="https://www.sweans.com" rel="noopener noreferrer"&gt;https://www.sweans.com&lt;/a&gt;&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Mega Revamp of the HTML Report
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Improved design and usability.&lt;/li&gt;
&lt;li&gt;Fully mobile-responsive.&lt;/li&gt;
&lt;li&gt;The report itself is WCAG 2.2 AAA compliant (or as close as possible).&lt;/li&gt;
&lt;li&gt;Full keyboard navigation (no mouse required). 🚫🐭&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Want proof?&lt;/p&gt;

&lt;p&gt;Okay, this is what the &lt;strong&gt;old report&lt;/strong&gt; looked like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7x6cnwye2dp9en27h1km.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7x6cnwye2dp9en27h1km.png" alt=" " width="800" height="638"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;And this is the design of the new, &lt;strong&gt;dazzling report&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fulfvuuq5by0agt1o6ej7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fulfvuuq5by0agt1o6ej7.png" alt=" " width="800" height="638"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pretty sassy, eh? That’s a mood!&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Improved Reported Selectors
&lt;/h2&gt;

&lt;p&gt;Uses &lt;code&gt;data-cy&lt;/code&gt;, &lt;code&gt;data-testid&lt;/code&gt;, &lt;code&gt;data-test&lt;/code&gt;, &lt;code&gt;data-qa&lt;/code&gt;, and &lt;code&gt;data-test-id&lt;/code&gt; selectors for accessibility violations when available.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F183fy45s7ih87eisyfrt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F183fy45s7ih87eisyfrt.png" alt=" " width="551" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Cypress v15+
&lt;/h2&gt;

&lt;p&gt;Supercharged Cypress support! WICK-A11Y v2.2.0+ now fully embraces Cypress v15.0.0+ powerful new features and its improved Cypress runner.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ucpuyupu98bn6icr57a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ucpuyupu98bn6icr57a.png" alt=" " width="800" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Image from &lt;a href="https://www.cypress.io/blog" rel="noopener noreferrer"&gt;https://www.cypress.io/blog&lt;/a&gt;&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  ACT3: RESOLUTION (wick-a11y 2.3.0)
&lt;/h1&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;Final word from our friend Wick: WCAG 2.2 AAA locked, HTML revamp in place, and a report that seals the deal. The web just got upgraded. The web just got upgraded.&lt;/p&gt;

&lt;p&gt;You find it here &lt;a href="https://www.npmjs.com/package/wick-a11y" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/wick-a11y&lt;/a&gt;, and here &lt;a href="https://github.com/sclavijosuero/wick-a11y" rel="noopener noreferrer"&gt;https://github.com/sclavijosuero/wick-a11y&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Cheers, my QA friends!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;strong&gt;Come on, friend! Follow me, leave a comment, or leave a reaction. Please don’t be shy!&lt;/strong&gt;&lt;/em&gt; ❤️ 🦄 🤯 🙌 🔥&lt;/p&gt;

&lt;p&gt;Also on my new &lt;strong&gt;YouTube channel&lt;/strong&gt;: &lt;a href="https://www.youtube.com/@SebastianClavijoSuero" rel="noopener noreferrer"&gt;https://www.youtube.com/@SebastianClavijoSuero&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Want to support my work? Buy me a coffee or chip in for a training, so I can keep learning and sharing cool stuff. Thanks for the support!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.buymeacoffee.com/sclavijosuero" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.buymeacoffee.com%2Fbuttons%2Fv2%2Fdefault-yellow.png" alt="Buy Me A Coffee" width="545" height="153"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cypress</category>
      <category>qa</category>
      <category>a11y</category>
      <category>automation</category>
    </item>
    <item>
      <title>WICK-A11Y v2.2.0 x CYPRESS v15: Parabellum for Axe-ssibility Barriers</title>
      <dc:creator>Sebastian Clavijo Suero</dc:creator>
      <pubDate>Mon, 01 Sep 2025 00:52:46 +0000</pubDate>
      <link>https://forem.com/sebastianclavijo/wick-a11y-v220-x-cypress-v15-parabellum-for-axe-essibility-barriers-2fam</link>
      <guid>https://forem.com/sebastianclavijo/wick-a11y-v220-x-cypress-v15-parabellum-for-axe-essibility-barriers-2fam</guid>
      <description>&lt;p&gt;&lt;em&gt;(Cover image from pexels.com by Bruna Sonore)&lt;/em&gt;&lt;br&gt;
 &lt;/p&gt;
&lt;h1&gt;
  
  
  ACT 1: EXPOSITION
&lt;/h1&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;It has been just over a year since the &lt;strong&gt;WICK-A11Y&lt;/strong&gt; open‑source accessibility plugin for Cypress saw the light, and became one of the most powerful and beloved tools in the Cypress community for fighting accessibility barriers in web apps.&lt;/p&gt;

&lt;p&gt;Since then, there have been 14 releases, keeping the plugin at the top of accessibility tooling for Cypress test frameworks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz9v2rm61ohcxez5t82vg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz9v2rm61ohcxez5t82vg.png" alt=" " width="800" height="569"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;(Image from pexels.com by icon0 com)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;And now, the brand‑new &lt;strong&gt;wick‑a11y v2.2.0&lt;/strong&gt; is fully supported in &lt;strong&gt;Cypress v15&lt;/strong&gt;, &lt;em&gt;just 10 days&lt;/em&gt; after the new Cypress version was officially released.&lt;/p&gt;

&lt;p&gt;That’s because I saw this coming even before Cypress 15 dropped. I already knew two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The latest cypress‑axe v1.6.0 supported up to Cypress 14, so a new release of the library was likely imminent (they typically keep pace with the latest Cypress within days).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Cypress Runner UX was going to change, and to keep wick‑a11y’s voice‑feedback feature working, I would need to dive back into the Runner UI.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;As of Aug 30, 2025, Wick‑A11y v2.2.0 is live for Cypress v15.&lt;/strong&gt;&lt;br&gt;
 &lt;/p&gt;


&lt;h1&gt;
  
  
  ACT 2: CONFRONTATION
&lt;/h1&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h2&gt;
  
  
  ✨ What's new?
&lt;/h2&gt;

&lt;p&gt;Basically 3 things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;All the existing wick-a11y capabilities are fully supported&lt;/strong&gt; in Cypress v15:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Comprehensive Accessibility Analysis&lt;/li&gt;
&lt;li&gt;Easy to Use and Highly Configurable&lt;/li&gt;
&lt;li&gt;Summary of Violations&lt;/li&gt;
&lt;li&gt;Detailed Violation Information&lt;/li&gt;
&lt;li&gt;Custom Styling&lt;/li&gt;
&lt;li&gt;Interactive Console&lt;/li&gt;
&lt;li&gt;HTML Reports&lt;/li&gt;
&lt;li&gt;Terminal Reports&lt;/li&gt;
&lt;li&gt;Voice Feedback&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Support for cypress-axe v1.7.0&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Bug fixes&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fixed an issue where violation screenshots could not be copied to the HTML report folder when test folders were deeply nested.&lt;/li&gt;
&lt;li&gt;Other minor fixes.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h2&gt;
  
  
  👀 What's look like?
&lt;/h2&gt;

&lt;p&gt;Still looks very cool, probably even cooler! 😎&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cypress Log and Interactive console&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fguipm8dvckzlvt3tzqds.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fguipm8dvckzlvt3tzqds.png" alt=" " width="800" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HTML Report&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F682ro0524lri7ilbmk2w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F682ro0524lri7ilbmk2w.png" alt=" " width="800" height="1314"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Voice Feedback&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fekgvof3ef0lanp7ow14c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fekgvof3ef0lanp7ow14c.png" alt=" " width="800" height="276"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h2&gt;
  
  
  🤔 How to use it?
&lt;/h2&gt;

&lt;p&gt;Just install the new wick-a11y v2.2.0 as a Dev Dependency in your Cypress v15 project (a new project or an existing one):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;wick-a11y &lt;span class="nt"&gt;-D&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  ⚙️ Compatibility?
&lt;/h2&gt;

&lt;p&gt;✔️ &lt;strong&gt;Wick‑a11y v2.2.0&lt;/strong&gt; is for &lt;strong&gt;Cypress v15+&lt;/strong&gt; projects, due to voice‑feedback dependencies on the new Cypress Runner UI.&lt;/p&gt;

&lt;p&gt;✔️ Use &lt;strong&gt;wick‑a11y v2.1.0&lt;/strong&gt; for &lt;strong&gt;Cypress v14.x&lt;/strong&gt;, which includes the latest patches and features for that line.&lt;/p&gt;

&lt;p&gt;✔️ And &lt;strong&gt;wick‑a11y v1.4.0&lt;/strong&gt; or earlier for &lt;strong&gt;Cypress v12.x &amp;amp; v13.x&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;




&lt;h1&gt;
  
  
  ACT3: RESOLUTION
&lt;/h1&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;Now you have no excuse not to incorporate accessibility analysis into your Cypress v15 projects, because &lt;strong&gt;wick‑a11y v2.2.0&lt;/strong&gt; is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;More amazing than a flaky test turning green on the first try.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cooler than John Wick in a tailored Kevlar suit tipping the Continental concierge.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;More powerful than a High Table decree.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;And kinder to screen readers than a perfectly labeled form, because that’s exactly what you’ll ship (or quite close).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Where to find:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;npm&lt;/strong&gt;: &lt;a href="https://www.npmjs.com/package/wick-a11y" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/wick-a11y&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Github&lt;/strong&gt;: &lt;a href="https://github.com/sclavijosuero/wick-a11y" rel="noopener noreferrer"&gt;https://github.com/sclavijosuero/wick-a11y&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Video Tutorial&lt;/strong&gt;: &lt;a href="https://www.youtube.com/watch?v=WpdrXU-6xzc" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=WpdrXU-6xzc&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⭐⭐⭐ Also, I want to give a special thanks to &lt;strong&gt;&lt;a href="https://www.linkedin.com/in/petermsouzajr/" rel="noopener noreferrer"&gt;Peter M Souza Jr&lt;/a&gt;&lt;/strong&gt; for his continuous contributions to keeping wick‑a11y robust and reliable.&lt;br&gt;
You rock, man! 🤘&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;strong&gt;I'd love to hear from you! Please don't forget to follow me, leave a comment, or a reaction if you found this article useful or insightful.&lt;/strong&gt;&lt;/em&gt; ❤️ 🦄 🤯 🙌 🔥&lt;/p&gt;

&lt;p&gt;You can also connect with me on my new &lt;strong&gt;YouTube channel&lt;/strong&gt;: &lt;a href="https://www.youtube.com/@SebastianClavijoSuero" rel="noopener noreferrer"&gt;https://www.youtube.com/@SebastianClavijoSuero&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you'd like to support my work, consider buying me a coffee or contributing to a training session, so I can keep learning and sharing cool stuff with all of you.&lt;br&gt;
Thank you for your support!&lt;br&gt;
&lt;a href="https://www.buymeacoffee.com/sclavijosuero" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.buymeacoffee.com%2Fbuttons%2Fv2%2Fdefault-yellow.png" alt="Buy Me A Coffee" width="545" height="153"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cypress</category>
      <category>qa</category>
      <category>a11y</category>
      <category>automation</category>
    </item>
    <item>
      <title>The Almighty PLAYWRIGHT-SCHEMA-VALIDATOR: The Unstoppable Triumvirate Playwright + AJV + Zod</title>
      <dc:creator>Sebastian Clavijo Suero</dc:creator>
      <pubDate>Mon, 04 Aug 2025 01:13:35 +0000</pubDate>
      <link>https://forem.com/sebastianclavijo/the-almighty-playwright-schema-validator-the-unstoppable-triumvirate-playwright-ajv-zod-3jno</link>
      <guid>https://forem.com/sebastianclavijo/the-almighty-playwright-schema-validator-the-unstoppable-triumvirate-playwright-ajv-zod-3jno</guid>
      <description>&lt;h1&gt;
  
  
  ACT 1: EXPOSITION
&lt;/h1&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;It's here! At last, the invincible &lt;a href="https://github.com/sclavijosuero/playwright-schema-validator" rel="noopener noreferrer"&gt;PLAYWRIGHT-SCHEMA-VALIDATOR&lt;/a&gt; has arrived to impose the "Schema Peace" on any rebellious API that dares exist on this side of the Rubicon.&lt;/p&gt;

&lt;p&gt;And it’s here wielding two powerful weapons: the classic AJV gladius and the newly forged, sharp ZOD trident.&lt;/p&gt;

&lt;p&gt;But don’t be deceived, citizen! The PLAYWRIGHT-SCHEMA-VALIDATOR remains fully backward compatible with its venerable predecessor, the &lt;a href="https://github.com/sclavijosuero/playwright-ajv-schema-validator" rel="noopener noreferrer"&gt;PLAYWRIGHT-AJV-SCHEMA-VALIDATOR&lt;/a&gt;, and allies seamlessly with the &lt;a href="https://github.com/sclavijosuero/pw-api-plugin" rel="noopener noreferrer"&gt;PW-API-PLUGIN&lt;/a&gt; to deliver an even more glorious display of schema violations, worthy of the Playwright Senate itself!&lt;/p&gt;

&lt;p&gt;Let us behold how this almighty triumvirate can make your API testing as effortless and triumphant as a Roman victory parade.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0bbadny1s1sq5kbm3ovv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0bbadny1s1sq5kbm3ovv.png" alt=" " width="800" height="483"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  ACT 2: CONFRONTATION
&lt;/h1&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;Sorry, my QA friend, I just couldn't resist! I am captivated by the Roman era, both the early Republic and the later Empire, so I had to theme this blog around that glorious epoch when so many boundaries were shattered.&lt;/p&gt;

&lt;p&gt;And that is precisely what the PLAYWRIGHT-SCHEMA-VALIDATOR plugin sets out to do: shatter any wall that dares stand between you and flawless API validation.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  New Features Available
&lt;/h2&gt;

&lt;p&gt;With this new plugin, you will benefit from new features such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Full support for the AJV validator&lt;/strong&gt; (for plain JSON schemas, Swagger documents, and OpenAPI schemas) &lt;strong&gt;as well as the ZOD validator&lt;/strong&gt; (for Zod schemas).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Consistent display of schema validation errors&lt;/strong&gt;, regardless of whether you use AJV or ZOD.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ability to configure custom styles&lt;/strong&gt; to present schema violations according to your team’s preferences (including icons and HEX colors).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Complete backward compatibility&lt;/strong&gt; and support for all existing features found in its predecessor, playwright-ajv-schema-validator.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Complete List of Features
&lt;/h2&gt;

&lt;p&gt;But if you want to know the full list of features included in the playwright-schema-validator plugin, here they are:&lt;/p&gt;

&lt;p&gt;✔️ Function &lt;strong&gt;&lt;code&gt;validateSchema()&lt;/code&gt;&lt;/strong&gt; (and alias &lt;strong&gt;&lt;code&gt;validateSchemaAjv()&lt;/code&gt;&lt;/strong&gt;) performs a JSON Schema Validation and reports errors in the responses of network requests.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;⭐ Schemas are provided as JSON objects.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;⭐ Supports Plain JSON schemas, OpenAPI 3.x schema documents and Swagger 2.0 schema documents.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;⭐ Utilizes the &lt;a href="https://github.com/sclavijosuero/core-ajv-schema-validator" rel="noopener noreferrer"&gt;&lt;em&gt;core-ajv-schema-validator&lt;/em&gt;&lt;/a&gt;, leveraging the Ajv JSON Schema Validator.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;✔️ Fuction &lt;strong&gt;&lt;code&gt;cy.validateSchemaZod()&lt;/code&gt;&lt;/strong&gt; identifies and reports Zod schema validation errors in the responses of network requests.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;⭐ Schemas are provided as Zod objects.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;⭐ Uses the &lt;a href="https://github.com/sclavijosuero/core-zod-schema-validator" rel="noopener noreferrer"&gt;&lt;em&gt;core-zod-schema-validator&lt;/em&gt;&lt;/a&gt;, leveraging the Zod Schema Validator.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;✔️ The functions process the responses provided by Playwright API requests.&lt;/p&gt;

&lt;p&gt;✔️ Provides a user-friendly view of schema errors and mismatches between the validated data and the JSON schema, clearly highlighting where each validation error occurred and the exact reason for the mismatch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;⭐ Total number of schema errors.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;⭐ Full list of schema errors as provided by &lt;strong&gt;Ajv&lt;/strong&gt; or &lt;strong&gt;Zod&lt;/strong&gt; depending on the selected function.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;⭐ A nested tree view of the validated data, clearly indicating the errors and where they occurred in an easy-to-understand format.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;✔️ Allow custom styles (icons and text colors) to match the user's preferences for distinguishing schema errors.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftj4iwuovht1cfzsq9o6k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftj4iwuovht1cfzsq9o6k.png" alt=" " width="800" height="596"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;✔️ Presents results to the user in a consistent format, regardless of whether the AJV Schema Validator or ZOD Validator is used.&lt;/p&gt;

&lt;p&gt;✔️ Environment variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;⭐ &lt;code&gt;DISABLE_SCHEMA_VALIDATION&lt;/code&gt; to disable schema validation in your tests even when function &lt;code&gt;validateSchema()&lt;/code&gt; is present.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;⭐ &lt;code&gt;LOG_API_UI&lt;/code&gt; to enable the display of API call details in &lt;strong&gt;&lt;em&gt;Playwright UI&lt;/em&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;em&gt;Trace Viewer&lt;/em&gt;&lt;/strong&gt; .&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq1r7i90p9ekgag1lip41.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq1r7i90p9ekgag1lip41.png" alt=" " width="800" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⭐ &lt;code&gt;LOG_API_REPORT&lt;/code&gt; to enable the display of API call details in &lt;strong&gt;&lt;em&gt;HTML Report&lt;/em&gt;&lt;/strong&gt; .&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8m2mq6iay29qw4xwviaz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8m2mq6iay29qw4xwviaz.png" alt=" " width="800" height="1209"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;⭐ Integrates seamlessly with the &lt;strong&gt;&lt;code&gt;pw-api-plugin&lt;/code&gt;&lt;/strong&gt; but also works independently with &lt;strong&gt;standard Playwright API requests&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fde0a7f2oa7p51ojcod3q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fde0a7f2oa7p51ojcod3q.png" alt=" " width="800" height="460"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  How to Migrate
&lt;/h2&gt;

&lt;p&gt;To upgrade your Playwright framework from &lt;strong&gt;PLAYWRIGHT-AJV-SCHEMA-VALIDATOR&lt;/strong&gt; to the new &lt;strong&gt;PLAYWRIGHT-SCHEMA-VALIDATOR&lt;/strong&gt;, you only need to:&lt;/p&gt;

&lt;p&gt;1) Uninstall the previous plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm uninstall playwright-ajv-schema-validator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2) Install the new plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; playwright-schema-validator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3) Replace previous imports in your project:&lt;/p&gt;

&lt;p&gt;Replace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;validateSchema&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;playwright-ajv-schema-validator&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;validateSchema&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;playwright-schema-validator&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;No major refactoring required, just update your imports!&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You can continue using the &lt;code&gt;validateSchema()&lt;/code&gt; function with AJV schemas as before.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For AJV validation, you now also have the option to use the new &lt;code&gt;validateSchemaAjv()&lt;/code&gt; alias.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For ZOD schema validation, the plugin provides the new &lt;code&gt;validateSchemaZod()&lt;/code&gt; function.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Some Examples
&lt;/h2&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h4&gt;
  
  
  Example 1: &lt;code&gt;validateSchemaAjv()&lt;/code&gt; using Playwright standard API requests and overriding &lt;code&gt;issuesStyles&lt;/code&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Should validate schema AJV of POST "/store/order" for Playwright standard API and custom Styles override&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;issuesStyles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;iconPropertyError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🟦&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;colorPropertyError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#5178eb&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;iconPropertyMissing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🟪&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;colorPropertyMissing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#800080&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requestBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;petId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;quantity&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;shipDate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2024-01-01T00:57:29.231Z&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;placed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;complete&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;responsePost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://petstore.swagger.io/v2/store/order`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;requestBody&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json; charset=UTF-8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;responsePost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;responseBodyPost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;responsePost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;validateSchemaAjv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="nx"&gt;responseBodyPost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;petStoreSwaggerErrors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/store/order&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;post&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="nx"&gt;issuesStyles&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;h4&gt;
  
  
  Example 2: &lt;code&gt;validateSchemaAjv()&lt;/code&gt; using &lt;code&gt;pw-api-plugin&lt;/code&gt; with &lt;code&gt;pwApi&lt;/code&gt; class
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Should validate schema of POST "/store/order" for pw-api-plugin and pwApi&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requestBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;petId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;quantity&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;shipDate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2024-01-01T00:57:29.231Z&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;placed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;complete&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;responsePost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pwApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="s2"&gt;`https://petstore.swagger.io/v2/store/order`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;requestBody&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json; charset=UTF-8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;responsePost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;responseBodyPost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;responsePost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;validateSchemaAjv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="nx"&gt;responseBodyPost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;petStoreSwaggerErrors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/store/order&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;post&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;h4&gt;
  
  
  Example 3: &lt;code&gt;validateSchemaZod()&lt;/code&gt; using &lt;code&gt;pw-api-plugin&lt;/code&gt; with &lt;code&gt;axiosApi&lt;/code&gt; class
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Should validate schema ZOD of GET "/pet/findByStatusr" for pw-api-plugin and axiosApi and overriding `issuesStyles&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;responseGet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axiosApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="s2"&gt;`https://petstore.swagger.io/v2/pet/findByStatus?status=pending`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;findByStatusReq&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;findByStatusReq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;responseFindByStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;findByStatusReq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;validateSchemaZod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="nx"&gt;responseBodyGet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;petStoreSwaggerErrors&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  PLAYWRIGHT-SCHEMA-VALIDATOR: Just One Marvel in the Joyful Empire of the Wicked Schema Validators
&lt;/h3&gt;

&lt;p&gt;If you want to perform schema validations not only in Playwright projects but also in any other Node projects, or even in Cypress, I have you covered!&lt;/p&gt;

&lt;p&gt;Over time, I have created the Wicked Schema Validators ecosystem.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fldrbwc3uuttv30k9tpx1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fldrbwc3uuttv30k9tpx1.png" alt=" " width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With this, you have all the weapons you need to conquer any API testing battlefield!&lt;/p&gt;




&lt;h1&gt;
  
  
  ACT3: RESOLUTION
&lt;/h1&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;Migrating to PLAYWRIGHT-SCHEMA-VALIDATOR is so effortless that, if you’re doing API testing in your Playwright framework, there’s really no excuse not to take advantage of this powerful plugin. 😅&lt;/p&gt;

&lt;p&gt;Simply cross the Rubicon, and the empire of seamless API testing will be yours:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/sclavijosuero/playwright-schema-validator" rel="noopener noreferrer"&gt;https://github.com/sclavijosuero/playwright-schema-validator&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;npm: &lt;a href="https://www.npmjs.com/package/playwright-schema-validator" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/playwright-schema-validator&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ayn9wy3kwx5zdieznnn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ayn9wy3kwx5zdieznnn.png" alt=" " width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;strong&gt;I'd love to hear from you! Please don't forget to follow me, leave a comment, or a reaction if you found this article useful or insightful.&lt;/strong&gt;&lt;/em&gt; ❤️ 🦄 🤯 🙌 🔥&lt;/p&gt;

&lt;p&gt;You can also connect with me on my new &lt;strong&gt;YouTube channel&lt;/strong&gt;: &lt;a href="https://www.youtube.com/@SebastianClavijoSuero" rel="noopener noreferrer"&gt;https://www.youtube.com/@SebastianClavijoSuero&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you'd like to support my work, consider buying me a coffee or contributing to a training session, so I can keep learning and sharing cool stuff with all of you.&lt;br&gt;
Thank you for your support!&lt;br&gt;
&lt;a href="https://www.buymeacoffee.com/sclavijosuero" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.buymeacoffee.com%2Fbuttons%2Fv2%2Fdefault-yellow.png" alt="Buy Me A Coffee" width="545" height="153"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>playwright</category>
      <category>qa</category>
      <category>apitesting</category>
      <category>schemavalidator</category>
    </item>
    <item>
      <title>The 'second' most abused (and misused) Cypress command ever: cy.contains()</title>
      <dc:creator>Sebastian Clavijo Suero</dc:creator>
      <pubDate>Sat, 21 Jun 2025 06:02:14 +0000</pubDate>
      <link>https://forem.com/sebastianclavijo/the-second-most-abused-and-misused-cypress-command-ever-cycontains-126j</link>
      <guid>https://forem.com/sebastianclavijo/the-second-most-abused-and-misused-cypress-command-ever-cycontains-126j</guid>
      <description>&lt;p&gt;&lt;em&gt;(Cover image from pexels.com by Levi Damasceno)&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ACT 1: EXPOSITION&lt;/li&gt;
&lt;li&gt;
ACT 2: CONFRONTATION

&lt;ul&gt;
&lt;li&gt;
What is &lt;code&gt;.contains()&lt;/code&gt;?

&lt;ul&gt;
&lt;li&gt;It has a Dual Nature&lt;/li&gt;
&lt;li&gt;It is a Cypress query&lt;/li&gt;
&lt;li&gt;Some Overlooked Options&lt;/li&gt;
&lt;li&gt;Selector Support&lt;/li&gt;
&lt;li&gt;Contains with &lt;code&gt;input[type="submit"]&lt;/code&gt; Element&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

12+1 Misuses of &lt;code&gt;.contains()&lt;/code&gt; – some might even call it abuse

&lt;ul&gt;
&lt;li&gt;1. Unnecessary Use of &lt;code&gt;.should('exist')&lt;/code&gt; Assertion&lt;/li&gt;
&lt;li&gt;2. Misinterpreting Partial Matching&lt;/li&gt;
&lt;li&gt;3. Ignoring Whitespace Issues&lt;/li&gt;
&lt;li&gt;4. Ignoring Case-sensitivity Issues&lt;/li&gt;
&lt;li&gt;5. Assuming &lt;code&gt;.contains()&lt;/code&gt; Returns All Matching Elements&lt;/li&gt;
&lt;li&gt;6. Using it for Too Broad Element Searches (VERY IMPORTANT!)&lt;/li&gt;
&lt;li&gt;7. Relying on &lt;code&gt;.contains()&lt;/code&gt; for Only Visible Elements&lt;/li&gt;
&lt;li&gt;8. Relying on &lt;code&gt;cy.contains&lt;/code&gt; Instead of Declarative Selectors&lt;/li&gt;
&lt;li&gt;9. Using &lt;code&gt;.contains()&lt;/code&gt; Against Non-Readable or Elements&lt;/li&gt;
&lt;li&gt;10. Misuse of &lt;code&gt;.contains()&lt;/code&gt; in Assertions for Non-Text Elements&lt;/li&gt;
&lt;li&gt;11. Misunderstanding &lt;code&gt;.contains()&lt;/code&gt; with Dynamic Content&lt;/li&gt;
&lt;li&gt;12. Misuse of &lt;code&gt;.contains()&lt;/code&gt; Accessing a Shadow DOM Element&lt;/li&gt;
&lt;li&gt;12+1. Chaining Incorrectly Multiple &lt;code&gt;.contains()&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Diferences: &lt;code&gt;.contains()&lt;/code&gt; vs &lt;code&gt;.should('contains.text')&lt;/code&gt; vs &lt;code&gt;.should('have.text')&lt;/code&gt;
&lt;/li&gt;

&lt;li&gt;Preference Order When Looking for Elements&lt;/li&gt;

&lt;li&gt;New: &lt;code&gt;.findOne()&lt;/code&gt; in Gleb Bahmutov's cypress-map Plugin&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;li&gt;ACT3: RESOLUTION&lt;/li&gt;

&lt;/ul&gt;




&lt;h1&gt;
  
  
  ACT 1: EXPOSITION
&lt;/h1&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;About a year ago, I wrote an article about how &lt;a href="https://dev.to/sebastianclavijo/the-most-abused-cypress-command-ever-cywaittime-15e0"&gt;&lt;code&gt;cy.wait(TIME)&lt;/code&gt; is the most abused Cypress command ever&lt;/a&gt;, and it quickly sparked an interesting conversation among my fellow QAs.&lt;/p&gt;

&lt;p&gt;When I asked which other Cypress command was excessively used but equally misunderstood or misused, the response was quite unanimous: &lt;code&gt;cy.contains()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;While I still firmly believe &lt;code&gt;cy.wait(TIME)&lt;/code&gt; is the reigning champion of infamous commands in Cypress, I must admit that &lt;code&gt;cy.contains()&lt;/code&gt; earns a solid silver medal.&lt;/p&gt;

&lt;p&gt;Do not get me wrong, &lt;code&gt;cy.contains()&lt;/code&gt; is one of my favorite and most powerful commands in the Cypress arsenal. But much like &lt;em&gt;John Wick&lt;/em&gt;, it’s a double edged weapon, capable of incredible precision, yet risky if not handled correctly. This misuse frequently results in failing or flaky tests, making its misuse both widespread and often tricky to catch.&lt;/p&gt;

&lt;p&gt;So yes, &lt;code&gt;cy.contains()&lt;/code&gt; (amazing but often poorly executed), deserves, in my opinion, its spot as runner-up in the "most abused/misused Cypress commands" debate!&lt;/p&gt;




&lt;h1&gt;
  
  
  ACT 2: CONFRONTATION
&lt;/h1&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  What is &lt;code&gt;.contains()&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;According to the &lt;a href="https://docs.cypress.io/api/commands/contains" rel="noopener noreferrer"&gt;official Cypress documentation&lt;/a&gt;, it is used to &lt;em&gt;get the DOM element containing the text. DOM elements can contain more than the desired text and still match&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In other words, it yields &lt;strong&gt;one DOM element&lt;/strong&gt; that contains the passed &lt;strong&gt;substring&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  It has a Dual Nature
&lt;/h3&gt;

&lt;p&gt;It has a dual nature because it can start a new Cypress chain (searching the entire document for the content):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Start a chain (search entire document)&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;`
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or use an existing chain (querying inside the DOM tree of the specified yielded element):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Use an existing chain (search restricted within element with id 'list')&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#list&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  It is a Cypress query
&lt;/h3&gt;

&lt;p&gt;You must know that &lt;code&gt;.contains()&lt;/code&gt; is a &lt;strong&gt;Cypress query&lt;/strong&gt; that retries until all the chained assertions pass and the element exists in the DOM or until the timeout expires according to the &lt;code&gt;defaultCommandTimeout&lt;/code&gt; in &lt;code&gt;cypress.config.js&lt;/code&gt;. But this can be overridden with the &lt;code&gt;timeout&lt;/code&gt; option.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Timeout override in millisecs&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="s2"&gt;`
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because &lt;code&gt;.contains()&lt;/code&gt; is a Cypress query, it not only retrieves the element matching the text but also performs an implicit assertion that the element must exist.&lt;/p&gt;

&lt;h3&gt;
  
  
  Some Overlooked Options
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;.contains()&lt;/code&gt; supports the &lt;code&gt;log&lt;/code&gt; and &lt;code&gt;timeout&lt;/code&gt; options, like many other Cypress commands and queries, but there are a couple of very important options that are often overlooked:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;matchCase&lt;/code&gt;&lt;/strong&gt;: To check case sensitivity. By default, it is &lt;code&gt;true&lt;/code&gt;, meaning the match is case-sensitive.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// It will also find "My TEXT"&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;matchCase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="s2"&gt;` 
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;includeShadowDom&lt;/code&gt;&lt;/strong&gt;: To include in the match elements within the shadow DOM. With this option you can directly target elements within the Shadow DOM without using the &lt;code&gt;.shadow()&lt;/code&gt; command explicitly. By default, it uses the &lt;code&gt;includeShadowDom&lt;/code&gt; option in the &lt;code&gt;cypress.config.js&lt;/code&gt;, or &lt;code&gt;false&lt;/code&gt; if not specified.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Includes elements inside Shadow DOM.&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;includeShadowDom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="s2"&gt;` 
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note: A shadow DOM element is a hidden subtree attached to a regular DOM element, isolating its HTML and CSS from the main document to prevent conflicts and enable reusable, self-contained web components.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I would like you to try to remember these two options &lt;code&gt;matchCase&lt;/code&gt; and &lt;code&gt;includeShadowDom&lt;/code&gt;, as they’re going to come up again later.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Selector Support
&lt;/h3&gt;

&lt;p&gt;An important thing to know about &lt;code&gt;.contains()&lt;/code&gt; is that you can provide a &lt;strong&gt;selector&lt;/strong&gt; to narrow and accelerate the search. When a selector is provided, it will return the DOM element that matches the selector, and either itself or one of its children matches the specified text.&lt;/p&gt;

&lt;p&gt;For example, for this HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;Enter Password&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Enter your password"&lt;/span&gt;
         &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we execute a &lt;code&gt;.contains()&lt;/code&gt; on the document using the text "Password":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Obtains the &amp;lt;label&amp;gt; element that contains "Password" as text&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively, if we execute a &lt;code&gt;.contains()&lt;/code&gt; using the HTML tag &lt;code&gt;form&lt;/code&gt; as the selector and the text "Password":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Obtains the &amp;lt;form&amp;gt; element that contains "Password" in its DOM tree&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;form&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;⚠️ Notice that, in the last case, although the element with the text "Password" is the &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt;, &lt;code&gt;.contains()&lt;/code&gt; will return the &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; element because it is the provided selector, and it contains a child with the text "Password" in its DOM tree.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Contains with &lt;code&gt;input[type="submit"]&lt;/code&gt; Element
&lt;/h3&gt;

&lt;p&gt;There is an interesting case with &lt;code&gt;.contains()&lt;/code&gt; where it can find an &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; element with the attribute &lt;code&gt;type="submit"&lt;/code&gt; and the attribute &lt;code&gt;value&lt;/code&gt; contains the provided text.&lt;/p&gt;

&lt;p&gt;For example, with the following HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"main"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;form&amp;gt;&lt;/span&gt;
    ...
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Send"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will execute successfully:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// It will find the &amp;lt;input&amp;gt; element of type submit with text "Send"&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Send&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;This is because &lt;code&gt;input[type='submit']&lt;/code&gt; is an element that behaves like a &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;, with its text in the &lt;code&gt;value&lt;/code&gt; attribute. Therefore, &lt;code&gt;.contains(&lt;/code&gt;) also includes this specific type of element in the search.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;Ok, now that we understand the theory, using &lt;code&gt;.contains()&lt;/code&gt; in our test shouldn’t be too hard, right? 😎&lt;/p&gt;

&lt;p&gt;Well, most likely not.&lt;/p&gt;

&lt;p&gt;But what if I told you that this mighty command is, unfortunately, &lt;strong&gt;often misused&lt;/strong&gt; in Cypress tests, making them &lt;strong&gt;flakily unpredictable&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;Don’t believe me? 🩳🔥&lt;br&gt;
Alright, let me explain!&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h2&gt;
  
  
  12+1 Misuses of &lt;code&gt;.contains()&lt;/code&gt; – some might even call it abuse
&lt;/h2&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h3&gt;
  
  
  1. Unnecessary Use of &lt;code&gt;.should('exist')&lt;/code&gt; Assertion
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Misuse&lt;/strong&gt;&lt;br&gt;
Unnecessarily chaining the assertion &lt;code&gt;.should('exist')&lt;/code&gt; after &lt;code&gt;.contains()&lt;/code&gt; to ensure the element is present in the DOM.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Chaining `.should('exist')` after `.contains()` is unnecessary&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;exist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Better Practice&lt;/strong&gt;&lt;br&gt;
Do not include &lt;code&gt;.should('exist')&lt;/code&gt;, as &lt;code&gt;.contains()&lt;/code&gt; is a Cypress query (like &lt;code&gt;.get()&lt;/code&gt;) and automatically waits for the element to exist and be in an actionable state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Directly use `.contains()`&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  2. Misinterpreting Partial Matching
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Misuse&lt;/strong&gt;&lt;br&gt;
Users may not realize &lt;code&gt;cy.contains()&lt;/code&gt; performs partial text matching by default, leading to unintended behavior.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Matches both "Add to Cart" and "Address".&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Add&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Better Practice&lt;/strong&gt;&lt;br&gt;
If you want an exact match, you can use a regular expression with special characters: start-of-string &lt;code&gt;^&lt;/code&gt; and end-of-string &lt;code&gt;$&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Ensures it matches the whole word 'Add'&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^Add$/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to find an element that contains an exact text match, and part of the text comes from a dynamic variable, you can use &lt;code&gt;new RegExp()&lt;/code&gt; to handle the dynamic value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Exact match for "John Wick"&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;who&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Wick&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`^John &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;who&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;$`&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  3. Ignoring Whitespace Issues
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Misuse&lt;/strong&gt;&lt;br&gt;
Passing text that doesn't account for formatting, such as extra spaces and newlines.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Fails due to extra spaces after the word "Cart"&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Add to Cart   &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Better Practice&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Trim or format the text properly.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Avoid trailing/leading spaces&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; Add to Cart  &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Or better yet, be precise in matching.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Precise matching&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Add to Cart&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  4. Ignoring Case-sensitivity Issues
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Misuse&lt;/strong&gt;&lt;br&gt;
Passing text that doesn't account for casing issues. By default &lt;code&gt;.contains()&lt;/code&gt; is &lt;strong&gt;&lt;em&gt;case-sensitive&lt;/em&gt;&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Only finds an element with the exact text "add to cart" in all lowercase&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;add to cart&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Better Practice&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you want to find an element with the text "add to cart" regardless of its letter casing, you can use &lt;code&gt;matchCase&lt;/code&gt; option.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Enables case-insensitive matching&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;add to cart&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;matchCase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You could also write a regular expression to achieve the same goal of case-insensitive matching:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Also enables case-insensitive matching&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/add to cart/i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  5. Assuming &lt;code&gt;.contains()&lt;/code&gt; Returns All Matching Elements
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Misuse&lt;/strong&gt;&lt;br&gt;
Beginners often assume that &lt;code&gt;.contains()&lt;/code&gt; retrieves all the elements that match the specified text, whereas it actually only selects the first matching element in the DOM.&lt;/p&gt;

&lt;p&gt;For example, if your DOM contains multiple matching elements with the word "Enter":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;Enter Username&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;Enter Password&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then this command will find only the first &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; element matching the text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Will find and return only the first &amp;lt;div&amp;gt; that contains "Enter",&lt;/span&gt;
&lt;span class="c1"&gt;// and that would be &amp;lt;label&amp;gt;Enter Username&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Enter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Better Practice&lt;/strong&gt;&lt;br&gt;
To get all elements that contains the substring you can use &lt;code&gt;cy.get()&lt;/code&gt; with &lt;code&gt;:contains()&lt;/code&gt; jquery selector extension.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Retrieves all elements in the DOM that contain the text "Enter"&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:contains("Enter")&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  6. Using it for Too Broad Element Searches (VERY IMPORTANT!)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Misuse&lt;/strong&gt;&lt;br&gt;
Relying on &lt;code&gt;cy.contains()&lt;/code&gt; without narrowing the context can cause the command to match unintended elements.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Consider any element including text "Password" on the DOM&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If there are multiple elements with the text "Password" on the page, this &lt;strong&gt;&lt;em&gt;can lead&lt;/em&gt;&lt;/strong&gt; to selecting the wrong element.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Better Practice&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's consider the following HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;Enter Password&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Enter your password"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Approach A&lt;/em&gt;&lt;/strong&gt;: Use a parent element or container to scope the search.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Scoped to &amp;lt;form&amp;gt; or children elements within the &amp;lt;form&amp;gt; that match the text&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;form&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Yields the &amp;lt;label&amp;gt; element&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;cy.get('form')&lt;/code&gt; selects all &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; elements in the DOM.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Within these &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; elements, &lt;code&gt;cy.contains('Password')&lt;/code&gt; finds a child element matching the text "Password" (in this case, the &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; element).&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;This approach is useful for constraining the scope of searchable elements before filtering by text.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Approach B&lt;/em&gt;&lt;/strong&gt;: Leverage &lt;code&gt;.contains()&lt;/code&gt; with a selector.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Scope only to &amp;lt;form&amp;gt; elements that match the provided text itself or&lt;/span&gt;
&lt;span class="c1"&gt;// within one of their children&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;form&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// Yields the &amp;lt;form&amp;gt; element&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this other case:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Finds the first &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; element anywhere in the whole DOM that contains the text "Password" within its DOM tree.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;This is very useful for a quick search if you are looking for any &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; in the DOM that contains an element matching the text.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  7. Relying on &lt;code&gt;.contains()&lt;/code&gt; for Only Visible Elements
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Misuse&lt;/strong&gt;&lt;br&gt;
Assuming that &lt;code&gt;.contains()&lt;/code&gt; will only locate visible elements while ignoring hidden ones.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The element containing 'Hidden Text' will be found even if the text is hidden&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hidden Text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Beginners may mistakenly believe that &lt;code&gt;.contains()&lt;/code&gt; will locate only elements that are visible. However, it will return the first element in the DOM that contains the specified text, regardless of its visibility.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Better Practice&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To ensure you're working only with visible elements, you can use the &lt;strong&gt;&lt;code&gt;:visible&lt;/code&gt; &lt;em&gt;pseudo-selector&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Directly as part of a CSS selector:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example 1.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// It will return a visible &amp;lt;div&amp;gt; element that contains the text "Username"&lt;/span&gt;
&lt;span class="c1"&gt;// or a child element within a visible &amp;lt;div&amp;gt; that contains the text "Username"&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div:visible&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Username&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example 2.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// It will return a &amp;lt;div&amp;gt; element that contains the text "Username"&lt;/span&gt;
&lt;span class="c1"&gt;// or a &amp;lt;div&amp;gt; element whose child contains the text "Username"&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div:visible&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Username&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;⚠️ Important: Note the nuance! Both might look similar, but they are slightly different and could yield completely different elements or even pass or fail the implicit assertion depending on the DOM tree under the &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; element.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Used in a &lt;code&gt;.filter()&lt;/code&gt; command:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// It will return a visible &amp;lt;div&amp;gt; element that contains the text "Username"&lt;/span&gt;
&lt;span class="c1"&gt;// or a child element within a visible &amp;lt;div&amp;gt; that contains the text "Username"&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:visible&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  8. Relying on &lt;code&gt;cy.contains&lt;/code&gt; Instead of Declarative Selectors
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Misuse&lt;/strong&gt;&lt;br&gt;
Overusing &lt;code&gt;.contains()&lt;/code&gt; instead of targeting elements by data attributes or selectors leads to brittle tests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// It breaks if the text changes to something like "Remove"&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Delete&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Better Practice&lt;/strong&gt;&lt;br&gt;
Utilize data attributes or stable semantic selectors.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Robust and still readable (clearly we are targeting the 'delete' button)&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-cy="delete-btn"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  9. Using &lt;code&gt;.contains()&lt;/code&gt; Against Non-Readable or Elements
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Misuse&lt;/strong&gt;&lt;br&gt;
Attempting to use &lt;code&gt;.contains()&lt;/code&gt; with non-readable elements, such as &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If your DOM includes an &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; element like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-control"&lt;/span&gt; 
       &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Enter your username"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the user enters the text "John Wick" into the input field:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhzhgrzb59sphkum7odsk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhzhgrzb59sphkum7odsk.png" alt=" " width="349" height="78"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;.contains()&lt;/code&gt; command searching for "John Wick" in the &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; element will not find it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;John Wick&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// This will fail&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Better Practice&lt;/strong&gt;&lt;br&gt;
Use &lt;code&gt;cy.get()&lt;/code&gt; for non-readable elements like &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;have.value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;John Wick&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// This will pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  10. Misuse of &lt;code&gt;.contains()&lt;/code&gt; in Assertions for Non-Text Elements
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Misuse&lt;/strong&gt;&lt;br&gt;
Using &lt;code&gt;.contains()&lt;/code&gt; to locate structural attributes (like &lt;code&gt;value&lt;/code&gt;, &lt;code&gt;alt&lt;/code&gt; or &lt;code&gt;placeholder&lt;/code&gt;) instead of visible text.&lt;/p&gt;

&lt;p&gt;If your DOM includes an &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; element like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"submit-btn"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Submit Button"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Submit&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The following will not work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This will fail because "Submit Button" is an alt attribute, not a visible text&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Submit Button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Better Practice&lt;/strong&gt;&lt;br&gt;
Use &lt;code&gt;cy.get()&lt;/code&gt; for non-standard-text elements, like &lt;code&gt;&amp;lt;alt&amp;gt;&lt;/code&gt; attribute.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;have.attr&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Submit Button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  11. Misunderstanding &lt;code&gt;.contains()&lt;/code&gt; with Dynamic Content
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Misuse&lt;/strong&gt;&lt;br&gt;
Failing to account for dynamically loaded content, causing intermittent errors.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This might timeout if the assertion is made prematurely&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Loading...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;not.exist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Better Practice&lt;/strong&gt;&lt;br&gt;
Chain &lt;code&gt;.contains()&lt;/code&gt; with &lt;code&gt;cy.wait()&lt;/code&gt; or &lt;code&gt;cy.intercept()&lt;/code&gt; to handle async loading.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;intercept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/items&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;getItems&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@getItems&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Loaded Content&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;exist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  12. Misuse of &lt;code&gt;.contains()&lt;/code&gt; Accessing a Shadow DOM Element
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Misuse&lt;/strong&gt;&lt;br&gt;
If you attempt to access a Shadow DOM element while testing a standard DOM page without explicitly indicating to Cypress to account for shadow elements, it will not locate them.&lt;/p&gt;

&lt;p&gt;A Shadow DOM element is part of a hidden DOM tree attached to a standard DOM element (known as the shadow host). This feature allows the encapsulation of styles and scripts, preventing them from influencing or being influenced by the rest of the document.&lt;/p&gt;

&lt;p&gt;For example, consider this HTML with a Shadow DOM component that includes a button:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;...
  &lt;span class="nt"&gt;&amp;lt;my-shadow-component&amp;gt;&lt;/span&gt;
    #shadow-root
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"my-button"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Click Me&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/my-shadow-component&amp;gt;&lt;/span&gt;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we try to locate the  with text "Click Me" using .contains():&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This will not work because the button is inside a Shadow DOM&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Click Me&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Better Practice&lt;/strong&gt;&lt;br&gt;
Use the &lt;code&gt;includeShadowDom&lt;/code&gt; option set to &lt;code&gt;true&lt;/code&gt; within &lt;code&gt;.contains()&lt;/code&gt; to automatically traverse Shadow DOM boundaries.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This will locate and interact with the &amp;lt;button&amp;gt; element inside the Shadow DOM&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Click Me&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;includeShadowDom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to include Shadow DOM elements in all searches across your project, you can configure it globally in the &lt;code&gt;cypress.config.js&lt;/code&gt; file by setting the &lt;code&gt;includeShadowDom&lt;/code&gt; option to &lt;code&gt;true&lt;/code&gt;. This way, you don’t need to include it in every &lt;code&gt;.contains()&lt;/code&gt; call.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cypress&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="c1"&gt;// Other configuration options...&lt;/span&gt;
  &lt;span class="na"&gt;includeShadowDom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// Additional options...&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively, you can use the .shadow() command to traverse shadow DOM boundaries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This will locate and interact with the &amp;lt;button&amp;gt; element inside the Shadow DOM&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-shadow-component&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shadow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Click Me&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  12+1. Chaining Incorrectly Multiple &lt;code&gt;.contains()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Misuse&lt;/strong&gt;&lt;br&gt;
Sometimes, users do not realize that when performing a &lt;code&gt;.contains()&lt;/code&gt; query, the yielded element may have changed. If you chain a second &lt;code&gt;.contains()&lt;/code&gt;, the subsequent query will be applied to the element returned by the first &lt;code&gt;.contains()&lt;/code&gt; and not to the original search context.&lt;/p&gt;

&lt;p&gt;If we have this html:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;...
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"#container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;User&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Enter your username"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;Password&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Enter your password"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The chained contains() will fail the test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The second contains will fail&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#container&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;User&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why is that?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The first &lt;code&gt;.contains('User')&lt;/code&gt; will locate the &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; element with the text "User".&lt;/li&gt;
&lt;li&gt;The second &lt;code&gt;.contains('Password')&lt;/code&gt; will be applied to the element returned by the first &lt;code&gt;.contains()&lt;/code&gt;, which is a &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; element, and not the original element yielded by &lt;code&gt;cy.get('#container')&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;As a result, the second &lt;code&gt;.contains()&lt;/code&gt; query will fail.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Better Practice&lt;/strong&gt;&lt;br&gt;
Avoid chaining &lt;code&gt;.contains()&lt;/code&gt; unless you are absolutely sure that the subsequent &lt;code&gt;.contains()&lt;/code&gt; query will operate within the DOM tree of the element yielded by the first &lt;code&gt;.contains(&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This test will pass&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#container&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;container&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@container&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;User&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@container&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Diferences: &lt;code&gt;.contains()&lt;/code&gt; vs &lt;code&gt;.should('contains.text')&lt;/code&gt; vs &lt;code&gt;.should('have.text')&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;Something I have noticed during my time working with Cypress is that quite a number of people, especially newer users, confuse the differences between &lt;code&gt;.contains()&lt;/code&gt;, &lt;code&gt;.should('contains')&lt;/code&gt;, and &lt;code&gt;.should('have.text')&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;While they may seem quite similar at first glance, each has its own specific purpose and behavior that can significantly impact your test cases.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;.contains('a_text')&lt;/code&gt;&lt;/strong&gt;: Cypress query used to locate elements containing specific text within them. It searches the entire or part of the DOM (and optionally shadow DOM) to find elements with either partial or exact text. It’s primarily a selector.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Finds any element that contains the exact text "Submit"&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Submit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;.should('contains.text', 'a_text')&lt;/code&gt;&lt;/strong&gt;: Assertion that checks if an element’s text includes a specific substring. It is not used to locate elements, but rather to validate that a particular element has text that contains the expected value. Other alias: &lt;code&gt;contain.text&lt;/code&gt;, &lt;code&gt;includes.text&lt;/code&gt;, and &lt;code&gt;include.text&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Asserts the element with class .greeting contains "Welcome" within its text&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.greeting&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;contains.text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Welcome&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;.should('have.text', 'a_text')&lt;/code&gt;&lt;/strong&gt;: Stricter assertion used to validate that an element’s text exactly matches the expected value.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Asserts the text of the element with class .greeting is exactly "Welcome User"&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.greeting&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;have.text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Welcome User&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4kfgio5wyrtioqjedhlf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4kfgio5wyrtioqjedhlf.png" alt=" " width="756" height="203"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Preference Order When Looking for Elements
&lt;/h2&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;Cypress gives top priority to a selector when it is explicitly provided in the &lt;code&gt;.contains()&lt;/code&gt; call.&lt;/p&gt;

&lt;p&gt;If no selector is specified, Cypress defaults to selecting elements higher in the DOM tree when they are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;input[type='submit']&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means that if the element containing the text is a &lt;span&gt; inside a  element, &lt;code&gt;.contains()&lt;/code&gt; will yield the &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; element instead of the &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt;.&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;For example, in the following HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;...
&lt;span class="nt"&gt;&amp;lt;form&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;User&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"user"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&lt;/span&gt;
&lt;span class="err"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we search for the element that contains the text "User":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Yields the &amp;lt;label&amp;gt;, not the &amp;lt;span&amp;gt;&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;User&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  New: &lt;code&gt;.findOne()&lt;/code&gt; in Gleb Bahmutov's cypress-map Plugin
&lt;/h2&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;One week prior to when this article was written, &lt;a href="https://www.linkedin.com/in/bahmutov/" rel="noopener noreferrer"&gt;Gleb Bahmutov&lt;/a&gt; introduced a new Cypress query &lt;code&gt;.findOne()&lt;/code&gt; to his &lt;a href="https://github.com/bahmutov/cypress-map" rel="noopener noreferrer"&gt;cypress-map&lt;/a&gt; plugin.&lt;/p&gt;

&lt;p&gt;This new query finds one DOM element that &lt;strong&gt;exactly matches&lt;/strong&gt; the provided text.&lt;/p&gt;

&lt;p&gt;For the following HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;...
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"tools"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;Mallet&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;Power Hammer&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;Hammer&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;Sledgehammer&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;.findOne()&lt;/code&gt; command for the text "Hammer":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// It will yield &amp;lt;label&amp;gt;Hammer&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#tools&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hammer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This would be equivalent to using a regular expression for an exact match in a &lt;code&gt;.contains()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// It will yield &amp;lt;label&amp;gt;Hammer&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#tools&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^Hammer$/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The advantage of this command &lt;code&gt;.findOne()&lt;/code&gt; is that it makes it much easier to find an element when the text is dynamic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Power&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// It will yield &amp;lt;label&amp;gt;Power Hammer&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#tools&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; Hammer`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And no need to use the cumbersome RegExp:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Power&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="c1"&gt;// It will yield &amp;lt;label&amp;gt;Power Hammer&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#tools&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`^&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; Hammer$`&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can find a video about how to use &lt;code&gt;.findOne()&lt;/code&gt; at this &lt;a href="https://www.youtube.com/watch?v=uc0W4UtZd2A" rel="noopener noreferrer"&gt;link&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;




&lt;h1&gt;
  
  
  ACT3: RESOLUTION
&lt;/h1&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;Well... Do you believe me now when I said at the beginning of this article that people &lt;em&gt;abuse&lt;/em&gt; (or &lt;em&gt;misuse&lt;/em&gt;) &lt;code&gt;.contains()&lt;/code&gt; in many different ways?&lt;/p&gt;

&lt;p&gt;Let me tell you, there are plenty of other ways to mess with this powerful Cypress query, but I stopped at 12+1 since this article is already quite lengthy. Besides, I really like the &lt;strong&gt;mystical number 13&lt;/strong&gt;, and that's because I'm not superstitious (that might bring bad luck)! 😄&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;strong&gt;I'd love to hear from you! Please don't forget to follow me, leave a comment, or a reaction if you found this article useful or insightful.&lt;/strong&gt;&lt;/em&gt; ❤️ 🦄 🤯 🙌 🔥&lt;/p&gt;

&lt;p&gt;You can also connect with me on my new &lt;strong&gt;YouTube channel&lt;/strong&gt;: &lt;a href="https://www.youtube.com/@SebastianClavijoSuero" rel="noopener noreferrer"&gt;https://www.youtube.com/@SebastianClavijoSuero&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you'd like to support my work, consider buying me a coffee or contributing to a training session, so I can keep learning and sharing cool stuff with all of you.&lt;br&gt;
Thank you for your support!&lt;br&gt;
&lt;a href="https://www.buymeacoffee.com/sclavijosuero" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.buymeacoffee.com%2Fbuttons%2Fv2%2Fdefault-yellow.png" alt="Buy Me A Coffee" width="545" height="153"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cypress</category>
      <category>qa</category>
      <category>automation</category>
      <category>testing</category>
    </item>
    <item>
      <title>Meet the New CYPRESS-SCHEMA-VALIDATOR: When ZOD Joined Forces with AJV for the Ultimate Validation Duo!</title>
      <dc:creator>Sebastian Clavijo Suero</dc:creator>
      <pubDate>Mon, 09 Jun 2025 05:07:51 +0000</pubDate>
      <link>https://forem.com/sebastianclavijo/meet-the-new-cypress-schema-validator-when-zod-joined-forces-with-ajv-for-the-ultimate-validation-2o73</link>
      <guid>https://forem.com/sebastianclavijo/meet-the-new-cypress-schema-validator-when-zod-joined-forces-with-ajv-for-the-ultimate-validation-2o73</guid>
      <description>&lt;h1&gt;
  
  
  ACT 1: EXPOSITION
&lt;/h1&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;I am thrilled to introduce the new &lt;strong&gt;&lt;a href="https://github.com/sclavijosuero/cypress-schema-validator" rel="noopener noreferrer"&gt;CYPRESS-SCHEMA-VALIDATOR&lt;/a&gt;&lt;/strong&gt; plugin, the ultimate successor to &lt;strong&gt;&lt;a href="https://github.com/sclavijosuero/cypress-ajv-schema-validator" rel="noopener noreferrer"&gt;CYPRESS-AJV-SCHEMA-VALIDATOR&lt;/a&gt;&lt;/strong&gt;! Combining all the power of AJV schema validation from its predecessor and now also featuring ZOD Schema Validation, and new features. 🔥 🔥 🔥 &lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;CYPRESS-SCHEMA-VALIDATOR is fully backward-compatible with the previous version&lt;/strong&gt;, and continues to integrate smoothly with leading Cypress API plugins like &lt;code&gt;@bahmutov/cy-api&lt;/code&gt; and &lt;code&gt;cypress-plugin-api&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This new version represents a significant leap forward in flexibility, usability, and debugging efficiency.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgqf9p69yglhptkrqbqjf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgqf9p69yglhptkrqbqjf.png" alt=" " width="800" height="636"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  ACT 2: CONFRONTATION
&lt;/h1&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  🤔 What’s New?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Support for Both AJV and ZOD Validators&lt;/strong&gt;: Choose between AJV and ZOD for schema validation, offering versatility for diverse workflows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs4654xgon3jd2wrzh99k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs4654xgon3jd2wrzh99k.png" alt=" " width="800" height="281"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Terminal Display of Schema Errors&lt;/strong&gt;: Schema errors are now shown directly in the terminal during &lt;code&gt;run&lt;/code&gt; mode, simplifying debugging.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz4zhna1im5wrhvcpzvuh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz4zhna1im5wrhvcpzvuh.png" alt=" " width="800" height="483"&gt;&lt;/a&gt;&lt;br&gt;
 &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Customizable Styles&lt;/strong&gt;: Add a personalized touch by using custom icons and HEX colors to better distinguish validation issues.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgj63lwn5nghznt9dls9v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgj63lwn5nghznt9dls9v.png" alt=" " width="800" height="418"&gt;&lt;/a&gt;&lt;br&gt;
 &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Consistent Error Format&lt;/strong&gt;: Enjoy unified error formatting, regardless of the validator selected (AJV or ZOD), for seamless and clear reporting during testing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ppfsbwga7tfgx2pb7re.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ppfsbwga7tfgx2pb7re.png" alt=" " width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h2&gt;
  
  
  ⏮️ Fully Backward Compatible?
&lt;/h2&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;AFFIRMATIVE! 🤟🤟🤟&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h2&gt;
  
  
  🛸 How Do I Migrate?
&lt;/h2&gt;

&lt;p&gt;Upgrading your Cypress framework from &lt;strong&gt;CYPRESS-AJV-SCHEMA-VALIDATOR&lt;/strong&gt; to the new &lt;strong&gt;CYPRESS-SCHEMA-VALIDATOR&lt;/strong&gt; is as simple as this:&lt;/p&gt;

&lt;p&gt;1) Uninstall the previous plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm uninstall cypress-ajv-schema-validator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2) Install the new plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; cypress-schema-validator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3) Replace previous imports in your project:&lt;/p&gt;

&lt;p&gt;Replace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cypress-ajv-schema-validator&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cypress-schema-validator&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  ✔️ What About the existing &lt;code&gt;.validateSchema()&lt;/code&gt; Command In My Code?
&lt;/h2&gt;

&lt;p&gt;No worries, &lt;strong&gt;you don’t need to refactor your test cases beyond the imports&lt;/strong&gt;!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;.validateSchema()&lt;/code&gt; command still works with AJV schemas.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A new alias, &lt;code&gt;.validateSchemaAjv()&lt;/code&gt;, is also available for AJV validation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Additionally, the plugin introduces &lt;code&gt;.validateSchemaZod()&lt;/code&gt; for ZOD schema validation.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it, &lt;strong&gt;NO DRAMA-LAMA!&lt;/strong&gt; 🦙🦙🦙&lt;/p&gt;

&lt;p&gt;If you're looking for drama, check out my article series: &lt;a href="https://dev.to/sebastianclavijo/series/30152"&gt;The Test Drama: Cypress and Playwright, Who Steals the Show?&lt;/a&gt; 😁&lt;/p&gt;




&lt;h1&gt;
  
  
  ACT3: RESOLUTION
&lt;/h1&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;Don't wait! Upgrade your Cypress framework with the new CYPRESS-SCHEMA-VALIDATOR and enjoy unparalleled flexibility for schema validations using two of the most powerful validators available today: AJV and ZOD (plus many other perks).&lt;/p&gt;

&lt;p&gt;You can find it in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;npm: &lt;a href="https://www.npmjs.com/package/cypress-schema-validator" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/cypress-schema-validator&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/sclavijosuero/cypress-schema-validator" rel="noopener noreferrer"&gt;https://github.com/sclavijosuero/cypress-schema-validator&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;&lt;strong&gt;I'd love to hear from you! Please don't forget to follow me, leave a comment, or a reaction if you found this article useful or insightful.&lt;/strong&gt;&lt;/em&gt; ❤️ 🦄 🤯 🙌 🔥&lt;/p&gt;

&lt;p&gt;You can also connect with me on my new &lt;strong&gt;YouTube channel&lt;/strong&gt;: &lt;a href="https://www.youtube.com/@SebastianClavijoSuero" rel="noopener noreferrer"&gt;https://www.youtube.com/@SebastianClavijoSuero&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you'd like to support my work, consider buying me a coffee or contributing to a training session, so I can keep learning and sharing cool stuff with all of you.&lt;br&gt;
Thank you for your support!&lt;br&gt;
&lt;a href="https://www.buymeacoffee.com/sclavijosuero" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.buymeacoffee.com%2Fbuttons%2Fv2%2Fdefault-yellow.png" alt="Buy Me A Coffee" width="545" height="153"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cypress</category>
      <category>qa</category>
      <category>apitesting</category>
      <category>schemavalidator</category>
    </item>
    <item>
      <title>Stand Out in the QA Crowd: Just Do You, but 'wick' Style!</title>
      <dc:creator>Sebastian Clavijo Suero</dc:creator>
      <pubDate>Mon, 12 May 2025 04:15:30 +0000</pubDate>
      <link>https://forem.com/sebastianclavijo/stand-out-in-the-qa-crowd-just-do-you-but-wick-style-368i</link>
      <guid>https://forem.com/sebastianclavijo/stand-out-in-the-qa-crowd-just-do-you-but-wick-style-368i</guid>
      <description>&lt;p&gt;&lt;em&gt;(Cover image from pexels.com by Photos by ERRN)&lt;/em&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  ACT 1: Why am I writing this article?
&lt;/h1&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;Over the past year, I have been approached numerous times by people from various continents who aspire to become QA professionals. Many of them wanted to know what they could do to become great QA Engineers.&lt;/p&gt;

&lt;p&gt;In essence, they were asking how they could &lt;strong&gt;Stand Out in the QA Crowd&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;But how could I tell these individuals what they should do? Frankly, I wasn’t entirely sure, as I’m still carving out my own path in this competitive, ever-evolving, yet fascinating QA world. These are people who are placing their trust in you and your judgment to guide their professional careers. That’s a huge responsibility.&lt;/p&gt;

&lt;p&gt;So, I shared with them the same principles I have strived to uphold throughout my personal and professional life. I believe these values are universally applicable and can genuinely help you shine in today’s highly competitive market, regardless of the profession or role you pursue. And that’s what this article is all about.&lt;/p&gt;

&lt;p&gt;However, to effectively apply these principles, long time ago I realized I needed to embrace something that could be captured in one simple yet powerful phrase:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Just Do You!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This seemingly simple yet often challenging approach is what will give you the courage to uphold these principles and truly make you stand out in the crowd.&lt;/p&gt;

&lt;p&gt;To do justice to this mindset, I will share with you, my friend, the raw, unfiltered, straightforward—and sometimes even blunt—version of my thoughts, to get it real!&lt;/p&gt;

&lt;p&gt;So, if you’re not particularly open to hearing thoughts that might push you out of your comfort zone, this might be the perfect moment to pause and redirect your focus to something else. 🙂&lt;/p&gt;




&lt;h1&gt;
  
  
  ACT 2:  Let’s Get Real—No Sugar Coating
&lt;/h1&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Just Do You
&lt;/h2&gt;

&lt;p&gt;OK, let me be completely honest to be fair...&lt;/p&gt;

&lt;p&gt;Most of the time, this approach has served me tremendously well, helping me achieve great success, overcome significant challenges, and earn the respect of many peers.&lt;/p&gt;

&lt;p&gt;However, there have been instances where it didn’t work as effectively as I had hoped. Sometimes it was because I didn’t implement it in the most effective way, and in many other cases, I genuinely believe no approach would have worked—whether due to external factors beyond my control or certain personalities where nothing could have made a difference.&lt;/p&gt;

&lt;p&gt;So, &lt;strong&gt;why should I change to please others?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Exactly, just do you! But that doesn’t mean you have a free pass to do whatever you want or to shirk your responsibilities.&lt;/p&gt;

&lt;p&gt;What it truly means is to &lt;strong&gt;gift the world the best version of you&lt;/strong&gt;. Your core values must be strong and unwavering if you aim to succeed in anything.&lt;/p&gt;

&lt;p&gt;We all know the expression "&lt;em&gt;Fake it Until You Make It&lt;/em&gt;". It has unfortunately become very popular, especially among people navigating today’s market but lacking solid technical or interpersonal skills. Too often, it is used as an excuse to avoid putting in the extra effort required beforehand to truly fulfill the demands of any position.&lt;/p&gt;

&lt;p&gt;But let me tell you what I think about that phrase, which is becoming a way of life for many... &lt;strong&gt;It's BS&lt;/strong&gt;! Pure, unfiltered, and odorous BS! 😏&lt;/p&gt;

&lt;p&gt;Sorry if I am too graphic, but yes, it is BS, no matter how many flies are trying to sell it to you, or even eat it themselves... Ewww! As we say in my home country, it is just pure '&lt;strong&gt;caca de vaca&lt;/strong&gt;' 🐄💩.&lt;/p&gt;

&lt;p&gt;So instead of faking it, don’t wait for "some day" to make it, &lt;strong&gt;just do it&lt;/strong&gt;! No tomorrow, not in one week... but NOW!&lt;/p&gt;

&lt;p&gt;Yes, it might sound cliché —like a well-known sports brand slogan— but it is a mindset you must embrace and commit to fully. Believe me, pretending won’t take you far. People are sharp and perceptive, and most can spot a fake from miles away.&lt;/p&gt;

&lt;p&gt;It might not be easy, I know. But I truly believe most people are capable of doing what they put their mind to. They just need to realize their own potential and &lt;em&gt;learn how to overcome their fear&lt;/em&gt; to give it their all. &lt;strong&gt;If you want to succeed, you cannot hold back&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;But still... YOU NEED TO BE YOU. If you’re quirky, don’t be afraid to embrace it. If your mind works differently or you approach problems in unconventional ways, that’s not a flaw, &lt;strong&gt;it is a strength&lt;/strong&gt;. It makes you unique. Unique people achieve unique things, often in ways that others might label as unorthodox or even weird, but that’s perfectly OK. &lt;strong&gt;Just do &lt;em&gt;(the better side of)&lt;/em&gt; you&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxc437cz8fijstwk3cbjk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxc437cz8fijstwk3cbjk.png" alt=" " width="600" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You don’t have to be quirky if that’s not who you are. But if you are, embrace it. What truly matters to most companies—especially those worth working for—isn’t necessarily how you do your work, but that you deliver results and ensure high quality. These companies do want to trust you, but you must prove yourself worthy of that trust. Often, they’re not overly concerned with originality or unconventional methods. However, those traits can work in your favor, helping you stand out and be remembered, turning your quirkiness into a valuable asset rather than a liability.&lt;/p&gt;

&lt;p&gt;You’ll encounter companies or individuals who may not embrace your uniqueness, and that’s perfectly fine. Chances are, you don’t align with their rigid perspective either, and that’s OK too. Move on! As my mother often says: "You didn’t grow up there, and they didn’t shape who you are". So again, why change for them?&lt;/p&gt;

&lt;p&gt;Actually, in some cases, your individuality might give you a head start in standing out from the crowd, but let’s be real—it won’t be enough on its own. You still need to deliver on your responsibilities.&lt;/p&gt;

&lt;p&gt;And for that, there is one thing you absolutely have to embrace with every fiber of your being: &lt;strong&gt;You need to Truly Care!&lt;/strong&gt;&lt;br&gt;
 &lt;/p&gt;

&lt;h2&gt;
  
  
  Truly Care
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;best professionals genuinely care about the work they do&lt;/strong&gt; or the projects they contribute to, &lt;strong&gt;and they show it&lt;/strong&gt;. They don’t fake it! They take full ownership, treating their work as more than just a way to fulfill a job description or earn a paycheck.  They possess an innate drive to feel proud at the end of the day about what they have accomplished.&lt;/p&gt;

&lt;p&gt;You don’t? Then maybe it’s time to reconsider your career path and seek a role where your lack of care will not impact the lives of so many around you.&lt;/p&gt;

&lt;p&gt;QA is a role of immense responsibility. We are the guardians of user experience, ensuring our product's integrity and preserving our company’s reputation!&lt;/p&gt;

&lt;p&gt;Let me tell you a story about something that happened to me many years ago.&lt;/p&gt;

&lt;p&gt;It was early March in Sevilla, my beautiful hometown. Some of you might find it familiar… Perhaps because of &lt;em&gt;Game of Thrones&lt;/em&gt;? 😉&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbv34yk3px656f0c0zsu2.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbv34yk3px656f0c0zsu2.jpg" alt=" " width="685" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I had just joined a new startup company in Spain less than a week earlier. Their offices were located in the garage of the founder’s house in Madrid.&lt;/p&gt;

&lt;p&gt;At the same time, I was preparing for a big life change—I was about to get married that summer, though my fiancée was still in the US. Believe me, switching from a well established company to a brand-new startup operating out of a garage, especially when you’re about to get married, might seem like a bold move. But I knew it was the right choice for the future of my new family.&lt;/p&gt;

&lt;p&gt;On my fifth day, while working from home until they set up an office in Sevilla, I got a call from the Founder-CEO-Director-PR-HR of my new company, let's call her M. Well, when a company operates out of a garage, one person often has to wear many hats! 😄&lt;/p&gt;

&lt;p&gt;I still remember it like it was just yesterday. She said:&lt;br&gt;
 &lt;/p&gt;

&lt;p&gt;"Sebastian—&lt;em&gt;told me, M&lt;/em&gt;—, I need you to come to Madrid for three months to work at the offices of a new client. Well… they’re not exactly our client, but I want them to be (&lt;em&gt;it was one of the largest telecom companies in the country back then&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;But… how can I put this... —&lt;em&gt;surprised me, M&lt;/em&gt;— Everything’s a mess! We’re not working directly for the client but for a consulting company they hired. This company already has three so-called expert consultants on-site, but they have no idea how to solve the client’s problem. So, they’re subcontracting us to get the job done. And by 'us,' I mean you.&lt;/p&gt;

&lt;p&gt;But I should also tell you that on-site—&lt;em&gt;continued, M&lt;/em&gt;—, there are a couple of official consultants for the product the client is using. However, they’ll only be there for one more week, so you’ll have, at most, three days to overlap with them.&lt;/p&gt;

&lt;p&gt;They told me they’re leaving because everything is in shambles—&lt;em&gt;warned me, M&lt;/em&gt;—. The code has been meddled with by too many hands, none skilled enough to preserve its integrity. On top of that, they believe there are too many external companies involved, each with conflicting interests."&lt;br&gt;
 &lt;/p&gt;

&lt;p&gt;Honestly, they weren’t wrong. Counting my company, there were now four external companies, plus the client’s own resources, working on this project. 🤦‍♂️&lt;/p&gt;

&lt;p&gt;Can you guess what words crossed my mind?&lt;/p&gt;

&lt;p&gt;Exactly! "&lt;em&gt;This is pure 'caca de vaca'&lt;/em&gt;" 🐄💩&lt;/p&gt;

&lt;p&gt;I could smell it, even from 533 kilometers away, all the way from Sevilla to Madrid! Sorry for being too graphic again! 😁&lt;/p&gt;

&lt;p&gt;I’m not going to lie to you... she had me scared. All of a sudden, I wasn’t so sure anymore if joining this startup was the best decision for me and the new family I was about to build. Man, I was really in a pickle!&lt;/p&gt;

&lt;p&gt;Three days later, there I was (&lt;em&gt;Sebastian&lt;/em&gt;) on the AVE (&lt;em&gt;the high-speed train originally built for the Universal Expo '92&lt;/em&gt;) racing toward the Wild Wild Madrid to face... I really had no idea what I was about to face. The only thing I knew for certain was that I had been hired to do a job in a multimillion-dollar project, and both my company and the client were counting on me. Uff, no joke!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwqby3z821gyeqqmfmcp6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwqby3z821gyeqqmfmcp6.png" alt=" " width="774" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After being met with a certain dose of cynicism by the consultants from the company we were contracted by (who, by the way, had already been there for over six months), I was finally introduced to the client. I could feel their apprehension and the frustration of a project that had been stagnating for nine months. Even more unsettling was their certainty that nobody who had shown up so far was capable of getting things done. They genuinely didn’t know who they could trust anymore.&lt;/p&gt;

&lt;p&gt;What could I possibly say to this person that he hadn’t already heard from the countless others who had come across the project?&lt;/p&gt;

&lt;p&gt;I sympathized with them. How could I show them that I genuinely wanted to help, that I truly &lt;strong&gt;did care&lt;/strong&gt; about them?&lt;/p&gt;

&lt;p&gt;So, I simply told them what I could honestly do given the circumstances: "OK, let me take a look at what’s there, and we’ll talk again in a week."&lt;br&gt;
 &lt;/p&gt;

&lt;h2&gt;
  
  
  The Method to Support the Principles
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;I give you a hint: Follow the the &lt;strong&gt;Bold&lt;/strong&gt; Brick Road!&lt;/em&gt; 😄&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In situations like that, there are basically two things you can do:&lt;br&gt;
You can try to keep a low profile, letting the days and weeks slip by while giving the impression that you’re busy and productive;&lt;br&gt;
Or, you can slap your palm on the table (when no one’s looking) and declare loud and clear: '&lt;strong&gt;&lt;em&gt;Here I am, baby, ready to shake things up!&lt;/em&gt;&lt;/strong&gt;'&lt;/p&gt;

&lt;p&gt;That’s the attitude that will make you stand out in the crowd. Sure, taking risks can be scary, and it’s always tempting to stay in your comfort zone—and that’s perfectly fine if that’s what you choose. But hear me out: staying comfortable is not what will make you stand out. No pain, no glory. &lt;/p&gt;

&lt;p&gt;To grow professionally, you need to step up and do grown-up things. Tasks that kids can’t handle or are not willing to take on.&lt;/p&gt;

&lt;p&gt;I strongly recommend &lt;strong&gt;setting a deadline to come up with a plan&lt;/strong&gt;, a realistic deadline, but not too far in the future. In my case, I committed to returning in a week with a clear course of action.&lt;/p&gt;

&lt;p&gt;Also, &lt;strong&gt;assess your environment&lt;/strong&gt;. What kind of ground are you stepping on? Is it solid, or is it unstable and squishy? Can you breathe the air safely, or do you need to control your breath because it’s too unpleasant?  Do you remember the &lt;em&gt;vaquita&lt;/em&gt;? 😅&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnu2z83gc71s7x5q4bo2n.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnu2z83gc71s7x5q4bo2n.webp" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the initial warnings I received and just one day in the field, it became glaringly clear: the terrain was sticky, and the air felt suffocating to breathe. The consulting company that had hired us to assist with the system design and implementation (though, truthfully, to do their job) was clearly at odds with a competitor who was also on-site, managing the data collection and entry for the client. They suspected that the ultimate, unspoken goal of the competitor was to outmaneuver them—the very company that brought us in—and claim the entire enchilada for themselves.&lt;/p&gt;

&lt;p&gt;The third external company, the official consultants for the product, was simply there to bill hours, and counting down the hours until they could get out of there!&lt;/p&gt;

&lt;p&gt;Meanwhile, the client had their own internal team, a mix of developers and data quality control staff who were brought on just nine months ago, supposedly brimming with expertise in the field.&lt;/p&gt;

&lt;p&gt;And then there was my company. In other words, me.&lt;/p&gt;

&lt;p&gt;After that, I did the only thing I could do if I truly wanted to solve the conundrum I was caught in:&lt;/p&gt;

&lt;p&gt;'&lt;em&gt;Echarle dos cojones&lt;/em&gt;' (as figuratively we say in my home country), and...&lt;/p&gt;

&lt;p&gt;Dig, and ask. Dig again, and ask again. &lt;strong&gt;Keep digging, and keep asking&lt;/strong&gt; relentlessly, even if you’re asking uncomfortable questions!&lt;/p&gt;

&lt;p&gt;It's fascinating what you can uncover once you start 'excavating', especially in a place where countless people before you have been digging and then carefully covering their tracks. It is indeed like working on the infamous &lt;em&gt;Money Pit&lt;/em&gt; on Oak Island! Back then, &lt;a href="https://www.history.com/shows/the-curse-of-oak-island" rel="noopener noreferrer"&gt;The Course of Oak Island TV show&lt;/a&gt; didn’t even exist yet, but man, it’s the first image that popped into my mind as I was writing this—so, of course, I had to include it. I just couldn’t resist (right, Todd?)! 😆&lt;/p&gt;

&lt;p&gt;The reality was that, even though I wasn’t getting straight answers to any of my questions, the information I gathered from the different stakeholders across these various companies gave me plenty of leads. They were all disconnected, but each one seemed worth exploring.&lt;/p&gt;

&lt;p&gt;I haven’t mentioned it yet, nor do I intend to go into detail about the nature of the work or the type of system I had to get running. After all, it’s just like the &lt;em&gt;MacGuffin&lt;/em&gt; in a Hitchcock movie—the backdrop to the story I’m sharing with you. What I will share is that it involved a distributed system operating across the county, where all database replicas needed to stay synchronized, ensuring no precious data was lost. And I think that’s plenty of detail for you to know. 😄&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm9dr35pbveoxolbjjmrl.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm9dr35pbveoxolbjjmrl.jpg" alt=" " width="491" height="601"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The interesting thing was that, after a week, the apprehension I might have felt initially was gradually fading away, inversely proportional to my growing knowledge of the system.&lt;/p&gt;

&lt;p&gt;Well… it makes sense, doesn’t it? Duh!&lt;/p&gt;

&lt;p&gt;But only once you have truly immersed yourself in the mess, instead of taking the easy road and keeping a low profile and letting the days and weeks quietly slip by.&lt;/p&gt;

&lt;p&gt;After a week in their offices, I approached the client as promised (the head of the department, who was a not insignificant big shot in that telecom) and presented my &lt;strong&gt;blueprint&lt;/strong&gt; outlining how I thought we could fix the broken pipes with minimal monetary expense. We just needed to tighten a few screws and replace a couple of leaking tubes. However, it was essential to bring together what remained of the 'team' (remember,  the official product consultants were already gone).&lt;/p&gt;

&lt;p&gt;So, the next step in the plan was to build &lt;strong&gt;consensus toward a common objective&lt;/strong&gt;. And for that, nothing works better than recognizing what each person was doing well and then, with carefully nuanced guidance (so they still believed it was their own idea), asking them how they could improve their part in the process.&lt;/p&gt;

&lt;p&gt;I know, this might seem like a little bit of manipulation, but as they say, &lt;em&gt;all’s fair in love and war&lt;/em&gt;. Just kidding! 😉&lt;/p&gt;

&lt;p&gt;Truth be told, when you candidly talk to people and &lt;strong&gt;genuinely listen to what they have to say&lt;/strong&gt;, you realize that they aren’t the mischievous personalities you initially thought—or were led to believe—they were.&lt;/p&gt;

&lt;p&gt;They need to see that you &lt;strong&gt;genuinely care&lt;/strong&gt;. Once they do, they might surprise you by showing they care as well. It is the same apprehension you felt when stepping into unfamiliar territory that made them defensive. It’s not about assigning blame for someone else’s stagnation, but rather fostering understanding and cultivating a shared sense of growth, all directed toward a common goal.&lt;/p&gt;

&lt;p&gt;And now comes the hard part, &lt;strong&gt;getting things done&lt;/strong&gt;! You can't expect or ask others to do something you are not willing to do yourself. You've likely heard the well-worn phrase: "Lead by example!" Beautiful sentiment, right? Very motivational! We drop it into conversations every chance we get...&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cut the BS&lt;/strong&gt; (AKA 'caca de vaca 🐄💩)! It's not enough to simply say "lead by example" or to just make it seem like you're doing it. You have to genuinely mean it, and do it for real! That’s how you learn, how you inspire others to follow you, and how you truly stand out from the crowd, whether it’s a QA crowd, a Dev crowd, or any other kind of crowd.&lt;/p&gt;




&lt;h1&gt;
  
  
  ACT 3:  Sebastian, You Are Amazing! Truly Inspirational! 🤣
&lt;/h1&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;Thank you, I really appreciate your sentiment! 😍&lt;/p&gt;

&lt;p&gt;But believe me, I wouldn’t say so lightly. Like any declaration of intentions and goodwill, we are still human, and sometimes our actions simply can’t keep up with our intentions.&lt;/p&gt;

&lt;p&gt;But that’s OK, nobody is really expecting you to operate at 110% all the time. That level of effort is unsustainable. The true key lies in giving it your all when it truly matters. And that’s something not everyone is willing to do. That is what will set you apart and put that extra mile between you and the rest of the crowd.&lt;/p&gt;

&lt;p&gt;To wrap up the story, the funny part of all this—which still makes me smile to this day—is that, after barely three weeks of working there, a stakeholder approached looking for some crucial information. To my surprise, the client's senior programmer-analyst genuinely told them: "You need to talk to Sebastian. He’s the expert and knows our system better than anyone". 😆&lt;/p&gt;

&lt;p&gt;By the way, within the next six months, the client replaced all the external companies and &lt;strong&gt;entrusted&lt;/strong&gt; my company to take charge of the entire operation—and cook the whole enchilada. 🌮 (Yes, this is a taco, but sadly, there’s no emoji for an enchilada!)&lt;/p&gt;

&lt;p&gt;I know... this ended up being a long story, maybe a little too quirky, maybe even a bit too graphic for your taste. But that’s just me, the real me: I like to share, some times too much, because I &lt;strong&gt;truly care&lt;/strong&gt;; I might be a little unorthodox, because I &lt;strong&gt;just do me&lt;/strong&gt;; And yes, I might be a little too graphic because, as we say in my home country, '&lt;em&gt;Me gustan las cosas claras, y el chocolate espeso!&lt;/em&gt;' (I like things to be clear, and my chocolate thick!)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F66vtz14n9fvqvyvrn589.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F66vtz14n9fvqvyvrn589.png" alt=" " width="335" height="223"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;strong&gt;I'd love to hear from you! Please don't forget to follow me, leave a comment, or a reaction if you found this article useful or insightful.&lt;/strong&gt;&lt;/em&gt; ❤️ 🦄 🤯 🙌 🔥&lt;/p&gt;

&lt;p&gt;You can also connect with me on my new &lt;strong&gt;YouTube channel&lt;/strong&gt;: &lt;a href="https://www.youtube.com/@SebastianClavijoSuero" rel="noopener noreferrer"&gt;https://www.youtube.com/@SebastianClavijoSuero&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you'd like to support my work, consider buying me a coffee or contributing to a training session, so I can keep learning and sharing cool stuff with all of you.&lt;br&gt;
Thank you for your support!&lt;br&gt;
&lt;a href="https://www.buymeacoffee.com/sclavijosuero" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.buymeacoffee.com%2Fbuttons%2Fv2%2Fdefault-yellow.png" alt="Buy Me A Coffee" width="545" height="153"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>qa</category>
      <category>careerdevelopment</category>
    </item>
    <item>
      <title>The Async Nature of Cypress: Don't Mess with the Timelines in Your Cypress Tests 'Dual-Verse'</title>
      <dc:creator>Sebastian Clavijo Suero</dc:creator>
      <pubDate>Wed, 30 Apr 2025 05:46:32 +0000</pubDate>
      <link>https://forem.com/sebastianclavijo/the-async-nature-of-cypress-dont-mess-with-the-timelines-in-your-cypress-tests-dual-verse-3ehh</link>
      <guid>https://forem.com/sebastianclavijo/the-async-nature-of-cypress-dont-mess-with-the-timelines-in-your-cypress-tests-dual-verse-3ehh</guid>
      <description>&lt;ul&gt;
&lt;li&gt;ACT 1: EXPOSITION&lt;/li&gt;
&lt;li&gt;
ACT 2: CONFRONTATION

&lt;ul&gt;
&lt;li&gt;The Asynchronous Nature of Cypress&lt;/li&gt;
&lt;li&gt;
The Two Timelines in Cypress Tests (AKA 'Dual-Verse')

&lt;ul&gt;
&lt;li&gt;Example Test 1&lt;/li&gt;
&lt;li&gt;Example Test 2&lt;/li&gt;
&lt;li&gt;Example Test 3&lt;/li&gt;
&lt;li&gt;Example Test 4&lt;/li&gt;
&lt;li&gt;Example Test 5&lt;/li&gt;
&lt;li&gt;Example Test 6&lt;/li&gt;
&lt;li&gt;And What About Cypress Hooks?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;li&gt;ACT3: RESOLUTION&lt;/li&gt;

&lt;/ul&gt;




&lt;h1&gt;
  
  
  ACT 1: EXPOSITION
&lt;/h1&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;A lot has been said about the asynchronous nature of the Cypress test framework. This asynchrony is often blamed as the primary cause of flakiness in Cypress tests. And, I would say… yeah, I agree. &lt;strong&gt;Asynchrony is the main reason of flakiness, but that’s because it’s not used correctly, which stems from it not being properly understood&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Why is that? I can’t pinpoint the exact reason, but this is my suspicious, and I’m really not trying to be judgmental:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The proper documentation on Cypress’s async behavior is often skipped or not fully read before people jump into creating more complex tests.&lt;br&gt;
 &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It’s very common for people to mix synchronous JavaScript code with asynchronous Cypress commands and queries in their tests, and treat them the same way. &lt;br&gt;
 &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;And, well… asynchronous behavior doesn’t come naturally to our brains. We think synchronously by default, and grasping asynchronous concepts requires extensive hands-on usage, often coupled with making a lot (and I mean a lot) of mistakes.&lt;br&gt;
 &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal of this blog is not to lecture you on general theories of asynchrony and race conditions, but to offer a clearer yet detailed explanation of how asynchrony works specifically in Cypress when running tests in a browser.&lt;/p&gt;

&lt;p&gt;I decided to tackle the subject with a different approach than how it is usually discussed and explained. This approach will be more visually oriented, as the human brain processes images and diagrams more effectively than large amounts of text, especially when dealing with complex concepts.&lt;/p&gt;

&lt;p&gt;To lay it all out, I’ve coined a term: “&lt;strong&gt;&lt;em&gt;The Two Timelines in Your Cypress Tests&lt;/em&gt;&lt;/strong&gt;” or, to add a little fun, “&lt;strong&gt;&lt;em&gt;The Cypress Dual-Verse&lt;/em&gt;&lt;/strong&gt;”! 😄&lt;/p&gt;




&lt;h1&gt;
  
  
  ACT 2: CONFRONTATION
&lt;/h1&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  The Asynchronous Nature of Cypress
&lt;/h2&gt;

&lt;p&gt;But before we explore the details (and the fun graphical part) of how the two timelines behave and coexist when running your Cypress tests, I want to take a moment to touch on and discuss some core aspects of how Cypress works in this context. If you’re looking for more specifics, I highly recommend reading the official Cypress.io documentation on &lt;a href="https://learn.cypress.io/cypress-fundamentals/understanding-the-asynchronous-nature-of-cypress" rel="noopener noreferrer"&gt;Understanding the Asynchronous nature of Cypress&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here are a few essential points to note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Cypress commands operate asynchronously and are &lt;strong&gt;executed later in a queued sequence&lt;/strong&gt; (grasping this is essential!).&lt;br&gt;
 &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cypress commands do not return their subjects directly; instead, they &lt;strong&gt;yield&lt;/strong&gt; their subjects. As a result, you cannot assign their output to a variable.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5q4xbslqmi8nf95qvs2l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5q4xbslqmi8nf95qvs2l.png" alt=" " width="366" height="78"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Cypress commands do not return JavaScript Promises; rather, they return what are commonly referred to as &lt;strong&gt;&lt;em&gt;Chainables&lt;/em&gt;&lt;/strong&gt;. Although the behavior of Chainables is somewhat similar to Promises, you cannot use &lt;code&gt;async/await&lt;/code&gt; in your Cypress tests.&lt;br&gt;
 &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Cypress commands are inherently asynchronous and yield chainable objects, which enables the next command in the chain to execute after the previous one completes.&lt;br&gt;
 &lt;/p&gt;

&lt;p&gt;Example of chained commands in Cypress:&lt;br&gt;
 &lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Visit the '/home' web page&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/home&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Wait until the &amp;lt;button&amp;gt; element is present in the DOM,&lt;/span&gt;
&lt;span class="c1"&gt;// verify it contains the text "Click me!", and then click it&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;contain&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Click me!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt; &lt;/p&gt;

&lt;p&gt;In this example, the &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; element retrieved by the &lt;code&gt;.get()&lt;/code&gt; command is yielded to the &lt;code&gt;.should()&lt;/code&gt; command, which makes an assertion over it, and in turn is yielded to the &lt;code&gt;.click()&lt;/code&gt; command.&lt;br&gt;
 &lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are two Cypress commands that help streamline the handling of asynchronous behavior:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;wrap()&lt;/code&gt;&lt;/strong&gt;: Used to wrap a JavaScript object or value so that it can be used within Cypress's chainable command structure. This is particularly useful when you want to work with non-Cypress objects, such as data variables or elements returned by a JavaScript function, within a Cypress test.&lt;br&gt;
 &lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myObject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Test Object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Wrap the object to use it in Cypress's command chain&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myObject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;its&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;equal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Test Object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;then()&lt;/code&gt;&lt;/strong&gt;: Used to execute a callback function once the previous Cypress command is resolved. It allows you to work directly with the resolved value of an asynchronous Cypress chainable command. This is especially useful for performing custom actions or additional assertions that cannot be directly achieved through Cypress commands.&lt;br&gt;
 &lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;$button&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// $button is the resolved jQuery element from cy.get('button')&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// Log the button's text&lt;/span&gt;
  &lt;span class="nx"&gt;$button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Perform a click directly with raw jQuery commands&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt; &lt;/p&gt;

&lt;p&gt;If you're accustomed to working with native Promises, the Cypress .then() behaves in a similar way. However, they are not promises; instead,  they are sequential commands added to a central Cypress queue, where they are executed asynchronously at a later time.&lt;/p&gt;

&lt;p&gt;You can nest additional Cypress commands within the &lt;code&gt;.then()&lt;/code&gt; block. Each nested command can utilize the results or actions performed by the preceding commands. Commands outside of the &lt;code&gt;.then()&lt;/code&gt; block will not execute until all the nested commands inside have completed. This is crucial for gaining a proper understanding of how the Cypress queue works.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;📑 Note: In Cypress, you cannot implement a &lt;code&gt;catch(error)&lt;/code&gt; handler for a failed command since Cypress does not provide built-in error recovery for failed commands. If a command fails, it halts the execution of all subsequent commands, leading to the failure of the entire test.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  The Two Timelines in Cypress Tests (AKA 'Dual-Verse')
&lt;/h2&gt;

&lt;p&gt;We have finally reached the fun part! 😄&lt;br&gt;
What do I mean by "The Two Timelines"?&lt;/p&gt;

&lt;p&gt;Here’s the answer:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The first is the &lt;strong&gt;JavaScript timeline&lt;/strong&gt;, executed by the browser, which runs the JavaScript code synchronously, one statement after another.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The second time line is the &lt;strong&gt;Cypress queue&lt;/strong&gt;, which follows its own pace, operating "in parallel" with the JavaScript timeline.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;🚨🚨 Spoiler alert: the JavaScript timeline does not wait for the Cypress queue until both timelines reach the end of the block in which they are defined (this could be an &lt;code&gt;it()&lt;/code&gt; test, a &lt;code&gt;.then()&lt;/code&gt; block, a &lt;code&gt;before()&lt;/code&gt;/&lt;code&gt;beforeEach()&lt;/code&gt;/&lt;code&gt;after()&lt;/code&gt;/&lt;code&gt;afterEach()&lt;/code&gt; hook, or any other block definition supported in Cypress). &lt;br&gt;
 &lt;/p&gt;

&lt;p&gt;The official recommendation from Cypress is not to mix async code with sync code (see &lt;a href="https://docs.cypress.io/app/core-concepts/introduction-to-cypress#Mixing-Async-and-Sync-code" rel="noopener noreferrer"&gt;here&lt;/a&gt;). However, in reality, there will be cases and situations where you encounter this practice—whether you’re debugging flaky tests implemented by someone else or addressing logical problems that cannot be resolved with only Cypress commands.&lt;/p&gt;

&lt;p&gt;This is why it is crucial for you to understand how Cypress's async behavior works and how these two timelines operate and interact with each other.&lt;/p&gt;

&lt;p&gt;And we are going to learn this through a series of six examples and some "&lt;em&gt;pretty&lt;/em&gt;" timeline graphs.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⚠️ DISCLAIMER: The examples we will explore are not meant to showcase the recommended best practices for these cases but are instead tailored to help me clearly illustrate how these timelines work.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h3&gt;
  
  
  Example Test 1
&lt;/h3&gt;

&lt;p&gt;Here is the test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Cypress Commands and JS Sync Code&lt;/span&gt;
&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Test 1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://jsonplaceholder.typicode.com/posts/1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;  &lt;span class="c1"&gt;// userId value is 1&lt;/span&gt;
            &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;equal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// Pass! (because id == 1)&lt;/span&gt;
            &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;  &lt;span class="c1"&gt;// id set to 2&lt;/span&gt;
            &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// Pass! (because id == 2)&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the first example, we will perform a network request and then process the response body by extracting the &lt;code&gt;userId&lt;/code&gt; field (which contains the value &lt;code&gt;1&lt;/code&gt;). Next, we will assert the obtained value, modify it, and then assert again.&lt;/p&gt;

&lt;p&gt;However, note that we have &lt;strong&gt;mixed Cypress &lt;code&gt;should&lt;/code&gt; assertions with Chai &lt;code&gt;expect&lt;/code&gt; assertions&lt;/strong&gt; while also storing the extracted values in a &lt;strong&gt;local variable&lt;/strong&gt; within the scope of the block.&lt;/p&gt;

&lt;p&gt;But in what order do the browser execute these JavaScript statements (which are sync) along with the Cypress commands (that are async)?&lt;/p&gt;

&lt;p&gt;When running a test, Cypress adds all the commands it finds to its &lt;strong&gt;queue&lt;/strong&gt; in the same order they are encountered. However, the QA Engineer does not truly know or cannot assume when a command will be taken by the Cypress runner from the queue and executed.&lt;/p&gt;

&lt;p&gt;And that is because JavaScript statements are executed lightning-fast by the browser and don’t wait around for the Cypress commands. It is like they are each living in their own parallel universe with separate timelines. 😄&lt;/p&gt;

&lt;p&gt;The only certainty is that nested &lt;strong&gt;JavaScript statements and Cypress commands&lt;/strong&gt; within another Cypress command block, are executed before any remaining &lt;strong&gt;Cypress command&lt;/strong&gt; outside that block.&lt;/p&gt;

&lt;p&gt;The two timelines come together either at the conclusion of a test or when a Cypress block finishes (like a &lt;code&gt;.then()&lt;/code&gt; block).&lt;br&gt;
 &lt;/p&gt;

&lt;p&gt;If we carefully analyze the order in which each statement and command is executed for the Test 1, it will look like a temporal flow diagram like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F73sz8gt0vpjzbs0d8ckx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F73sz8gt0vpjzbs0d8ckx.png" alt=" " width="800" height="944"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Interesting right? 🤔&lt;/p&gt;

&lt;p&gt;Notice in the temporal diagram, that the JavaScript statements are executed as soon as the &lt;code&gt;.then()&lt;/code&gt; block begins and do not wait for the Cypress commands, even if those commands are defined earlier in the test code. The &lt;strong&gt;&lt;code&gt;time??&lt;/code&gt;&lt;/strong&gt; (&lt;em&gt;time with question marks&lt;/em&gt; )in the graph represents the uncertainty around how much time will elapse between when a command is queued by Cypress and when it is actually executed. However, it is almost guaranteed that the JavaScript statements will run before the Cypress commands are executed within the block.&lt;/p&gt;

&lt;p&gt;Also, keep in mind that the value of the JavaScript variable &lt;code&gt;id&lt;/code&gt;, which is used in &lt;code&gt;.wrap()&lt;/code&gt;, is taken from its actual value at the moment &lt;code&gt;.wrap()&lt;/code&gt; is encountered during the test execution. The wrap command is then queued with that specific value (in this case, &lt;code&gt;1&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;And what happens in the Cypress Log in the test runner when we execute this test?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxd61muivsjemkhcezsu5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxd61muivsjemkhcezsu5.png" alt=" " width="800" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Cypress Log clearly shows that the &lt;code&gt;expect()&lt;/code&gt; Chai assertion (which is JavaScript code) runs before the &lt;code&gt;.wrap()&lt;/code&gt; and &lt;code&gt;.should()&lt;/code&gt; Cypress commands. And the test pass!&lt;/p&gt;

&lt;p&gt;Yeah, I wasn’t lying before—pure JavaScript code runs much faster than Cypress commands. It’s like they exist in a &lt;strong&gt;&lt;em&gt;Dual-Verse&lt;/em&gt;&lt;/strong&gt;, with time running much faster in one than the other, and they only converge in the future when the Cypress test or block ends!&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h3&gt;
  
  
  Example Test 2
&lt;/h3&gt;

&lt;p&gt;What if we create a second test similar to the first one, performing the same assertions, but using only Cypress commands?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Only Cypress Commands&lt;/span&gt;
&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Test 2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://jsonplaceholder.typicode.com/posts/1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;its&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;body.userId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eq&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Pass! (because userId == 1)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Increment id and yield value to next command&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eq&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Pass! (because id == 2)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this is what the temporal execution diagram will look like in this case:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5wci8zrg8gl47pzf3ne9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5wci8zrg8gl47pzf3ne9.png" alt=" " width="742" height="1016"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hmmm, it's quite different from Test 1, even though it performs the same type of assertions. However, this time, it only uses Cypress commands instead of mixing with Chai assertions.&lt;/p&gt;

&lt;p&gt;This difference is also noticeable when we look at the test results in the Cypress Log:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk1trgqawsmpqex2syr9k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk1trgqawsmpqex2syr9k.png" alt=" " width="800" height="254"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This time, the assertions were executed in order (first asserting &lt;code&gt;1&lt;/code&gt;, and then asserting &lt;code&gt;2&lt;/code&gt;), because Cypress executes commands in the same order they are added to the command queue. And the test also pass!&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Example Test 3
&lt;/h3&gt;

&lt;p&gt;Is the third test... a charm?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Cypress Commands (with 2 .then()) and JS Sync Code&lt;/span&gt;
&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Test 3 &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// id set to null&lt;/span&gt;

    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://jsonplaceholder.typicode.com/posts/1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;  &lt;span class="c1"&gt;// userId value is 1&lt;/span&gt;
    &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;equal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// Pass! (because id == 1)&lt;/span&gt;
        &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;  &lt;span class="c1"&gt;// id set to 2&lt;/span&gt;
        &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// Pass! (because id == 2)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This third test is a bit more complex. We are defining a local variable &lt;code&gt;id&lt;/code&gt; at the beginning of the test, which is accessible anywhere within the test, and also mixing JavaScript statements with Cypress commands.&lt;/p&gt;

&lt;p&gt;The temporal diagram will look like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyapspa7pi6ped9ycubso.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyapspa7pi6ped9ycubso.png" alt=" " width="800" height="972"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice that the variable &lt;code&gt;id&lt;/code&gt; is initialized at the very beginning of the test to &lt;code&gt;null&lt;/code&gt; and then modified twice within both &lt;code&gt;.then()&lt;/code&gt; blocks. Similar to Test 1, the &lt;code&gt;expect&lt;/code&gt; Chai assertion (that is Javascript sync code) is executed before the &lt;code&gt;.should()&lt;/code&gt; Cypress assertion (that is async). However, the test still pass.&lt;/p&gt;

&lt;p&gt;This is confirmed by the Cypress log when we run it in the Cypress runner:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbycac9ai6o8jyfpdkmef.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbycac9ai6o8jyfpdkmef.png" alt=" " width="800" height="266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Example Test 4
&lt;/h3&gt;

&lt;p&gt;Let’s spice things up a bit! What would happen if we threw an &lt;code&gt;expect&lt;/code&gt; Chai assertion at the very end of the test, outside both &lt;code&gt;.then()&lt;/code&gt; blocks?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Cypress Commands (with 2 .then()), JS Sync Code and expect() outside the last .then()&lt;/span&gt;
&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Test 4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// id set to null&lt;/span&gt;

    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://jsonplaceholder.typicode.com/posts/1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;  &lt;span class="c1"&gt;// userId value is 1&lt;/span&gt;
    &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;equal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// Pass! (because id == 1)&lt;/span&gt;
        &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;  &lt;span class="c1"&gt;// id set to 2&lt;/span&gt;
        &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// Pass! (because id == 2)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Fail!!! (because id == null)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And who wins the &lt;em&gt;Amazing Race&lt;/em&gt;?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzizb32i1n59jjwzm8mz9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzizb32i1n59jjwzm8mz9.png" alt=" " width="800" height="955"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The test fail!!!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Indeed, even though the &lt;code&gt;expect&lt;/code&gt; is placed at the end of the code, it is executed immediately after we initialize the variable &lt;code&gt;id&lt;/code&gt; to &lt;code&gt;null&lt;/code&gt;, long before it is updated within the Cypress commands, so &lt;strong&gt;the Cypress commands are not even executed at all&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;This is confirmed by the Cypress log when the test is executed:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvfh2h05we5rq4ms9n36n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvfh2h05we5rq4ms9n36n.png" alt=" " width="800" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Example Test 5
&lt;/h3&gt;

&lt;p&gt;Let me think...&lt;/p&gt;

&lt;p&gt;What if, instead of putting an &lt;code&gt;expect&lt;/code&gt; at the end of the test, I use Cypress commands to perform the assertion (using &lt;code&gt;.wrap()&lt;/code&gt; and &lt;code&gt;.should()&lt;/code&gt;)?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Cypress Commands (with 2 .then()), JS Sync Code and should() outside the last .then()&lt;/span&gt;
&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Test 5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// id set to null&lt;/span&gt;

    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://jsonplaceholder.typicode.com/posts/1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;  &lt;span class="c1"&gt;// userId value is 1&lt;/span&gt;
    &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;equal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// Pass! (because id == 1)&lt;/span&gt;
        &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;  &lt;span class="c1"&gt;// id set to 2&lt;/span&gt;
        &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// Pass! (because id == 2)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;equal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Will Fail!!! (because id == null)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Well... since Cypress commands are added to the queue in the order they are defined in the test, that final assertion should be executed at the end of the test, right?&lt;/p&gt;

&lt;p&gt;Let's see the diagram:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0uuas3vcuvtt27x37ywn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0uuas3vcuvtt27x37ywn.png" alt=" " width="738" height="1031"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that is exactly what happens! The Cypress assertion at the very end of the test is executed last, &lt;strong&gt;BUT&lt;/strong&gt; when the command &lt;code&gt;.wrap()&lt;/code&gt; is added to the Cypress queue, the variable &lt;code&gt;id&lt;/code&gt; still holds the value &lt;code&gt;null&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Remember? JavaScript statements are executed before Cypress commands, so the JavaScript timeline reaches the end of the test (with the &lt;code&gt;id&lt;/code&gt; variable still set to &lt;code&gt;null&lt;/code&gt;) before any of the earlier Cypress commands are executed. So the test fails!&lt;/p&gt;

&lt;p&gt;And here’s the proof (the Cypress Log never lies!):"&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff2itvp883qofz4l5cv46.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff2itvp883qofz4l5cv46.png" alt=" " width="800" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pesky variable! 😜&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Example Test 6
&lt;/h3&gt;

&lt;p&gt;And finally, the last test example.&lt;/p&gt;

&lt;p&gt;Why don’t we enclose the final Cypress assertion within a &lt;code&gt;cy.then()&lt;/code&gt; command?&lt;/p&gt;

&lt;p&gt;In this case, the &lt;code&gt;.then()&lt;/code&gt; command is not chained to the yielded value of another Cypress command, but is applied directly to the global object &lt;code&gt;cy&lt;/code&gt; (from the Cypress test framework).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Test 6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// id set to null&lt;/span&gt;

    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://jsonplaceholder.typicode.com/posts/1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;  &lt;span class="c1"&gt;// userId value is 1&lt;/span&gt;
    &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;equal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// Pass! (because id == 1)&lt;/span&gt;
        &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;  &lt;span class="c1"&gt;// id set to 2&lt;/span&gt;
        &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// Pass! (because id == 2)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Pass! (because id == 2)&lt;/span&gt;
        &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;equal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;//Pass! (because id == 2)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach will encapsulate those assertions within a Cypress block, allowing time for the &lt;code&gt;id&lt;/code&gt; variable to be set by the previous commands. Such a technique is a common and recommended practice in Cypress when you want to 'sync' commands with one another.&lt;/p&gt;

&lt;p&gt;We also added, as a bonus, an expect in that final &lt;code&gt;cy.then()&lt;/code&gt; block, and as expected, it is executed before the final Cypress assertion.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frdwsz205dukz8mbjnhbc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frdwsz205dukz8mbjnhbc.png" alt=" " width="650" height="1051"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Again Javascript synchronous timeline is faster!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq9uurkoqv75vnr7vots5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq9uurkoqv75vnr7vots5.png" alt=" " width="800" height="304"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  And What About Cypress Hooks?
&lt;/h3&gt;

&lt;p&gt;Actually, Cypress hooks are also blocks that wrap JavaScript and Cypress commands, so the same rules apply to them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fljrdvuxjhfed6d87vzog.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fljrdvuxjhfed6d87vzog.png" alt=" " width="306" height="483"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  ACT3: RESOLUTION
&lt;/h1&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;So we’ve reached the end of the exercise!&lt;/p&gt;

&lt;p&gt;Now you understand how Cypress's asynchronous nature really works—don’t be afraid of it. When used wisely, it can be a truly powerful tool. And also, the chained commands are quite elegant.&lt;/p&gt;

&lt;p&gt;Once again, the official Cypress recommendation is to avoid mixing JavaScript synchronous code with Cypress asynchronous commands. However, you now have the knowledge and tools to handle the &lt;strong&gt;&lt;em&gt;The Two Timelines&lt;/em&gt;&lt;/strong&gt; in your Cypress tests—for instance, when debugging a faulty or flaky test written by someone you’ve never met who knows how long ago! 😄&lt;/p&gt;

&lt;p&gt;One tool that might help you debug flaky tests caused by incorrect usage of Cypress's asynchronous nature is the &lt;a href="https://github.com/bahmutov/cypress-command-chain" rel="noopener noreferrer"&gt;cypress-command-chain&lt;/a&gt; plugin, developed by &lt;a href="https://www.linkedin.com/in/bahmutov" rel="noopener noreferrer"&gt;Gleb Bahmutov&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It is very easy to install and use. The plugin displays the Cypress queued commands in the Cypress log in the exact order they are inserted, along with the values of the arguments at that moment.&lt;/p&gt;

&lt;p&gt;If we run &lt;strong&gt;Example Test 5&lt;/strong&gt; (from our earlier discussion) with the &lt;code&gt;cypress-command-chain&lt;/code&gt; plugin enabled, this is what you would see in your Cypress log:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2gx4r56togm7fvsibnix.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2gx4r56togm7fvsibnix.png" alt=" " width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At the top of the log, the Cypress Command Queue is displayed, showing each command inserted in order along with its arguments. Below that is the regular Cypress log, which includes the results of the assertions as they are executed.&lt;/p&gt;

&lt;p&gt;You can find all the examples from this article in the GitHub repo: &lt;a href="https://github.com/sclavijosuero/cypress-async-behavior-examples" rel="noopener noreferrer"&gt;sclavijosuero/cypress-async-behavior-examples&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;strong&gt;I'd love to hear from you! Please don't forget to follow me, leave a comment, or a reaction if you found this article useful or insightful.&lt;/strong&gt;&lt;/em&gt; ❤️ 🦄 🤯 🙌 🔥&lt;/p&gt;

&lt;p&gt;You can also connect with me on my new &lt;strong&gt;YouTube channel&lt;/strong&gt;: &lt;a href="https://www.youtube.com/@SebastianClavijoSuero" rel="noopener noreferrer"&gt;https://www.youtube.com/@SebastianClavijoSuero&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you'd like to support my work, consider buying me a coffee or contributing to a training session, so I can keep learning and sharing cool stuff with all of you.&lt;br&gt;
Thank you for your support!&lt;br&gt;
&lt;a href="https://www.buymeacoffee.com/sclavijosuero" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.buymeacoffee.com%2Fbuttons%2Fv2%2Fdefault-yellow.png" alt="Buy Me A Coffee" width="545" height="153"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cypress</category>
      <category>qa</category>
      <category>automation</category>
      <category>testing</category>
    </item>
    <item>
      <title>The Test Drama: Cypress vs Playwright - Control Your Tests (Part 2): TAGS &amp; TEST FILTERS</title>
      <dc:creator>Sebastian Clavijo Suero</dc:creator>
      <pubDate>Thu, 24 Apr 2025 06:02:27 +0000</pubDate>
      <link>https://forem.com/sebastianclavijo/the-test-drama-cypress-vs-playwright-control-your-tests-part-2-tags-test-filters-3kh2</link>
      <guid>https://forem.com/sebastianclavijo/the-test-drama-cypress-vs-playwright-control-your-tests-part-2-tags-test-filters-3kh2</guid>
      <description>&lt;p&gt;&lt;strong&gt;Subtitle.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Cover image from pexels.com by Gu Ko)&lt;/em&gt;&lt;/p&gt;




&lt;ul&gt;
&lt;li&gt;ACT 1: EXPOSITION&lt;/li&gt;
&lt;li&gt;
ACT 2: CONFRONTATION

&lt;ul&gt;
&lt;li&gt;
🏷️ TAGS

&lt;ul&gt;
&lt;li&gt;
CYPRESS TAGS

&lt;ul&gt;
&lt;li&gt;Plugin @bahmutov/cy-grep (by Gleb Bahmutov)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;PLAYWRIGHT TAGS&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;li&gt;

⚗️ TEST FILTERS

&lt;ul&gt;
&lt;li&gt;
CYPRESS TEST FILTERS

&lt;ul&gt;
&lt;li&gt;Filter by Spec files&lt;/li&gt;
&lt;li&gt;Filter by Test Title (with @bahmutov/cy-grep)&lt;/li&gt;
&lt;li&gt;Filter by Tags (with @bahmutov/cy-grep)&lt;/li&gt;
&lt;li&gt;Plugin cypress-cli-select (by Dennis Bergevin)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

PLAYWRIGHT TEST FILTERS

&lt;ul&gt;
&lt;li&gt;Filter by Spec files&lt;/li&gt;
&lt;li&gt;Filter by Test Title&lt;/li&gt;
&lt;li&gt;Filter by Tags&lt;/li&gt;
&lt;li&gt;Plugin playwright-cli-select (by Dennis Bergevin)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;li&gt;ACT3: RESOLUTION&lt;/li&gt;

&lt;/ul&gt;




&lt;h1&gt;
  
  
  ACT 1: EXPOSITION
&lt;/h1&gt;

&lt;p&gt;This is the second part of the article on controlling the execution of your tests in the Cypress and Playwright test frameworks (or Playwright and Cypress, if you prefer 😉). In this second one, we will focus on two features: &lt;strong&gt;Tags&lt;/strong&gt; and &lt;strong&gt;Test Filters&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In addition to the out-of-the-box functionality provided by each framework, we will review and evaluate some interesting plugins that address certain gaps related to these features.&lt;/p&gt;

&lt;p&gt;As mentioned in the first article &lt;a href="https://dev.to/sebastianclavijo/the-test-drama-cypress-vs-playwright-control-your-tests-part-1-annotations-group-tests-4b28"&gt;The Test Drama: Cypress vs Playwright - Control Your Tests (Part 1): ANNOTATIONS &amp;amp; GROUP TESTS&lt;/a&gt;, I recommend reading this second installment in its entirety to gain a comprehensive understanding of these features in significant detail and depth.&lt;/p&gt;




&lt;h1&gt;
  
  
  ACT 2: CONFRONTATION
&lt;/h1&gt;

&lt;p&gt;Let's explore these tools in detail to see how they offer complete control over our test framework, whether you're using Cypress or Playwright.&lt;/p&gt;

&lt;h2&gt;
  
  
  🏷️ TAGS
&lt;/h2&gt;

&lt;p&gt;In test frameworks like Cypress and Playwright, tags are used for organizing and managing tests more efficiently. They help in categorizing tests based on certain criteria, such as the type of test, priority, or functionality.&lt;/p&gt;

&lt;p&gt;These are some of the common uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Test Selection&lt;/strong&gt;: Tags allow you to run specific subsets of tests based on certain criteria. For instance, you can run only "smoke" tests, "regression" tests, or tests related to a particular feature by specifying the relevant tag.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Filtering&lt;/strong&gt;: You can filter tests during execution to include or exclude certain tags. This is useful in CI/CD pipelines, where different environments or stages might require different sets of tests.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Organization&lt;/strong&gt;: Tags help in organizing tests into categories, making it easier to understand the scope and coverage of the test suite.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reporting&lt;/strong&gt;: Tags can be used to generate detailed reports that highlight specific areas of the software being tested, which can help in tracking test coverage and identifying gaps.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Overall, effectively using tags can significantly enhance the flexibility, readability, and maintainability of your testing framework. Therefore, we will learn how to use them in both Cypress and Playwright.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  CYPRESS TAGS
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Tags&lt;/strong&gt; are not natively supported by the Cypress test framework, which might be surprising but is indeed the case.&lt;/p&gt;

&lt;p&gt;To address this gap, there are two &lt;code&gt;grep&lt;/code&gt; plugins you can use in your Cypress framework:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/cypress-io/cypress/tree/develop/npm/grep" rel="noopener noreferrer"&gt;&lt;strong&gt;@cypress/grep&lt;/strong&gt;&lt;/a&gt;: This is the official plugin, featured in the &lt;a href="https://docs.cypress.io/app/plugins/plugins-list" rel="noopener noreferrer"&gt;Cypress Plugins&lt;/a&gt; list webpage.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/bahmutov/cy-grep" rel="noopener noreferrer"&gt;&lt;strong&gt;@bahmutov/cy-grep&lt;/strong&gt;&lt;/a&gt;: This plugin is developed by &lt;a href="https://www.linkedin.com/in/bahmutov/" rel="noopener noreferrer"&gt;Gleb Bahmutov&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To provide some context: some time ago, Gleb created a grep plugin named &lt;code&gt;cypress-grep&lt;/code&gt;. This plugin was submitted to Cypress.io, which renamed it to &lt;code&gt;@cypress/grep&lt;/code&gt;, making it the official version. However, to introduce improvements, Gleb forked the newly named &lt;code&gt;@cypress/grep&lt;/code&gt; plugin into a separate project called &lt;code&gt;@bahmutov/cy-grep&lt;/code&gt;. He has since maintained his new plugin in the new repository.&lt;/p&gt;

&lt;p&gt;Personally, I prefer Gleb's current version of the plugin and use it in my personal and organizational projects for several reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Gleb updates his plugin very frequently and keeps the dependencies up-to-date, since he relies on it heavily in his projects.&lt;/li&gt;
&lt;li&gt;He resolves issues quickly when reported. For example, I reported a problem with &lt;code&gt;.only&lt;/code&gt; annotations coexisting with certain configurations just a few days ago, and he fixed it in a matter of hours!&lt;/li&gt;
&lt;li&gt;Gleb is the original creator of both plugins and is widely recognized as the Cypress plugin master, a well known fact in the Cypress community.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Although the weekly downloads of the &lt;a href="https://www.npmjs.com/package/@cypress/grep" rel="noopener noreferrer"&gt;@cypress/grep&lt;/a&gt; plugin on &lt;strong&gt;npm&lt;/strong&gt; are approximately ten times greater than those of the &lt;a href="https://www.npmjs.com/package/@bahmutov/cy-grep" rel="noopener noreferrer"&gt;@bahmutov/cy-grep&lt;/a&gt; plugin, I would like to share additional information from the GitHub repositories of both plugins that supports my opinion as of the date this article was written.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;@cypress/grep&lt;/strong&gt; source code was last updated 2 years ago:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fldr1n9cu5k0z5spwaott.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fldr1n9cu5k0z5spwaott.png" alt=" " width="800" height="677"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a class="mentioned-user" href="https://dev.to/bahmutov"&gt;@bahmutov&lt;/a&gt;/cy-grep&lt;/strong&gt; source code was last updated 1 day ago:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvs35fsu7lyxenyu84mg9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvs35fsu7lyxenyu84mg9.png" alt=" " width="800" height="677"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;@cypress/grep&lt;/strong&gt; pull requests closed in the past few months (1):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr25g06my1i8iklvr20sj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr25g06my1i8iklvr20sj.png" alt=" " width="800" height="470"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a class="mentioned-user" href="https://dev.to/bahmutov"&gt;@bahmutov&lt;/a&gt;/cy-grep&lt;/strong&gt; pull requests closed in the past few months (108):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F829az7351vw8te0jkqkd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F829az7351vw8te0jkqkd.png" alt=" " width="800" height="597"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The configuration and use of both grep plugins are essentially the same. Hence, whatever we discuss in this article can be easily adapted for either one.&lt;/p&gt;

&lt;p&gt;Another very cool feature of &lt;code&gt;@bahmutov/cy-grep&lt;/code&gt;, which is not supported by the &lt;code&gt;@cypress/grep&lt;/code&gt; plugin, is that when running your tests in 'open' mode (with the browser open), you can perform grep operations directly from the browser's &lt;strong&gt;DevTools console&lt;/strong&gt; using the &lt;code&gt;Cypress.grep()&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjbspmlf69dhv7aor39jv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjbspmlf69dhv7aor39jv.png" alt=" " width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But wait! There is more...&lt;/p&gt;

&lt;p&gt;In "open" mode, you can also run just the failed tests from the DevTools console using &lt;code&gt;Cypress.grepFailed()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Alright, it's settled... we'll use the &lt;a class="mentioned-user" href="https://dev.to/bahmutov"&gt;@bahmutov&lt;/a&gt;/cy-grep plugin as we move forward in this article.&lt;/em&gt;&lt;/strong&gt; 🤘&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h4&gt;
  
  
  Plugin &lt;a class="mentioned-user" href="https://dev.to/bahmutov"&gt;@bahmutov&lt;/a&gt;/cy-grep (by Gleb Bahmutov)
&lt;/h4&gt;

&lt;p&gt;You can find this Cypress plugin in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;npm&lt;/strong&gt; &lt;a href="https://www.npmjs.com/package/@bahmutov/cy-grep" rel="noopener noreferrer"&gt;@bahmutov/cy-grep&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt; &lt;a href="https://github.com/bahmutov/cy-grep" rel="noopener noreferrer"&gt;@bahmutov/cy-grep&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can locate precise instructions on how to install the plugin in your Cypress framework in the plugin's &lt;a href="https://github.com/bahmutov/cy-grep/blob/main/README.md" rel="noopener noreferrer"&gt;README.md&lt;/a&gt; documentation.&lt;/p&gt;

&lt;p&gt;It consists of four main steps:&lt;/p&gt;

&lt;p&gt;1.Install the plugin as a devDependency in the project root using the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;-D&lt;/span&gt; @bahmutov/cy-grep
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2.Register the module in the &lt;code&gt;e2e.js&lt;/code&gt; support file by adding the following lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// cypress/support/e2e.js&lt;/span&gt;

&lt;span class="c1"&gt;// load and register the grep feature using "require" function&lt;/span&gt;
&lt;span class="c1"&gt;// https://github.com/bahmutov/cy-grep&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;registerCypressGrep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@bahmutov/cy-grep&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;registerCypressGrep&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3.Load and register the module in the &lt;code&gt;cypress.config.js&lt;/code&gt; file within &lt;code&gt;setupNodeEvents&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// cypress.config.js&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cypress&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="na"&gt;e2e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setupNodeEvents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@bahmutov/cy-grep/src/plugin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="c1"&gt;// IMPORTANT: return the config object&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;4.Configure the environment variables &lt;code&gt;grepFilterSpecs&lt;/code&gt; and &lt;code&gt;grepOmitFiltered&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; in your &lt;code&gt;cypress.config.js&lt;/code&gt; file (&lt;strong&gt;recommended&lt;/strong&gt; - we will discuss these variables later on):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// cypress.config.js&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cypress&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;grepFilterSpecs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;grepOmitFiltered&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Tags&lt;/strong&gt; can be any word representing the nature of the tests (like &lt;code&gt;smoke&lt;/code&gt;). However, it is very common (I would even say recommended) to use the character &lt;code&gt;@&lt;/code&gt; at the beginning of the tag name for clarity (like &lt;code&gt;@smoke&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Tags can be created at the group test level (&lt;code&gt;describe&lt;/code&gt; or &lt;code&gt;context&lt;/code&gt;) and the test level (&lt;code&gt;it&lt;/code&gt;) by passing an object with a &lt;code&gt;tags&lt;/code&gt; property as the function's second parameter. You can assign either a single tag (as a string) or multiple tags (as an array of strings) to the same test group or test.&lt;/p&gt;

&lt;p&gt;This example below illustrates tags at both the suite and test levels, including specifications for either single or multiple tags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// test-tags.cy.js&lt;/span&gt;

&lt;span class="c1"&gt;// The test suite 'User Registration' has two tags: '@user-reg' and '@smoke'.&lt;/span&gt;
&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;User Registration&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@user-reg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@smoke&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// The first test tagged with '@happy' to represent a happy path scenario.&lt;/span&gt;
    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should register a new user successfully&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@happy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Logic for testing successful user registration.&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// The second test tagged with '@unhappy' to represent an unhappy path scenario.&lt;/span&gt;
    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should not register with an already existing email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@unhappy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Logic for testing registration with a duplicate email.&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, you can select which test suites or tests to run using tags by passing them in the environment variable &lt;code&gt;grepTags&lt;/code&gt; when executing the Cypress run command in the CLI: &lt;code&gt;npx cypress run --env grepTags=...&lt;/code&gt; (we will will explore this in detail in the Cypress Test Filters section).&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  PLAYWRIGHT TAGS
&lt;/h3&gt;

&lt;p&gt;Tags are natively supported by the Playwright test framework. And believe me... this is very very handy!  🤲&lt;/p&gt;

&lt;p&gt;A tag in Playwright must start with the character &lt;code&gt;@&lt;/code&gt;. You set tags using the &lt;code&gt;tag&lt;/code&gt; property in the &lt;strong&gt;details&lt;/strong&gt; parameter when defining a test. If you want to specify only one tag, you can provide it as a string. If you would like to tag a test with more than one tag, you can do so by providing an array of string tags.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sample test with 1 tag&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@smoke&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sample test with 2 tags&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@smoke&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@regression&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tags also can be created at the group test level (&lt;code&gt;test.describe&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// test-tags.spec.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// The test suite 'Sample group with tag' has one tag: '@query'.&lt;/span&gt;
&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sample group with tag&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// The first test does not have any tag&lt;/span&gt;
    &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sample test one&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// The second test has 2 tags '@smoke' and '@regression'&lt;/span&gt;
    &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sample test two with 2 tags&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@smoke&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@regression&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;And that's it&lt;/strong&gt;—no more drama or anything else needed! 🙌&lt;/p&gt;

&lt;p&gt;Then you can select which tests or group tests to run in the terminal using the &lt;code&gt;--grep&lt;/code&gt; option followed by the tags. We will cover the details in the Playwright Test Filters section.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  ⚗️ TEST FILTERS
&lt;/h2&gt;

&lt;p&gt;Filtering tests by &lt;strong&gt;tags&lt;/strong&gt;, &lt;strong&gt;spec files&lt;/strong&gt; and &lt;strong&gt;test titles&lt;/strong&gt; allows precise control over which tests to execute. This feature enhances efficiency by enabling QA Engineers to target specific tests relevant to their current work, facilitating faster and more focused testing cycles.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  CYPRESS TEST FILTERS
&lt;/h3&gt;

&lt;p&gt;In Cypress, you can natively select which &lt;strong&gt;spec files&lt;/strong&gt; to execute by using the &lt;code&gt;--spec&lt;/code&gt; command when running tests from the terminal.&lt;/p&gt;

&lt;p&gt;However, Cypress does not support out-of-the-box filtering tests by &lt;strong&gt;tags&lt;/strong&gt; or test &lt;strong&gt;titles&lt;/strong&gt;. To achieve this, you'll need to use plugins provided by the Cypress community:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Like the previously mentioned &lt;a href="https://github.com/bahmutov/cy-grep" rel="noopener noreferrer"&gt;@bahmutov/cy-grep&lt;/a&gt;, developed by &lt;a href="https://www.linkedin.com/in/bahmutov/" rel="noopener noreferrer"&gt;Gleb Bahmutov&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Or the newly released &lt;a href="https://github.com/dennisbergevin/cypress-cli-select" rel="noopener noreferrer"&gt;cypress-cli-select&lt;/a&gt;, developed by &lt;a href="https://www.linkedin.com/in/dennis-bergevin/" rel="noopener noreferrer"&gt;Dennis Bergevin&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h4&gt;
  
  
  Filter by Spec files
&lt;/h4&gt;

&lt;p&gt;Cypress provides built-in support for filtering tests by specifying spec files through the &lt;strong&gt;&lt;code&gt;--spec&lt;/code&gt;&lt;/strong&gt; parameter in the command line.&lt;/p&gt;

&lt;p&gt;You can execute specific spec files by specifying their names, or run all tests in a folder that match a glob pattern. It is highly recommended to use double quotes when specifying the files to run, and the path can be either absolute or relative to the current working directory.&lt;/p&gt;

&lt;p&gt;To run a specific test file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npx cypress run &lt;span class="nt"&gt;--spec&lt;/span&gt; &lt;span class="s2"&gt;"cypress/e2e/my-spec.cy.js"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can specify multiple test files separated by a &lt;em&gt;comma&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npx cypress run &lt;span class="nt"&gt;--spec&lt;/span&gt; &lt;span class="s2"&gt;"cypress/e2e/examples/actions.cy.js,cypress/e2e/examples/files.cy.js"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To run all tests in a folder that match a glob pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npx cypress run &lt;span class="nt"&gt;--spec&lt;/span&gt; &lt;span class="s2"&gt;"cypress/e2e/login/**/*"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can combine all of this with the &lt;code&gt;--project&lt;/code&gt; parameter, but to be honest, I find it more confusing, so I won’t spend time on it here. If you’d like to see an example of this parameter, refer to the Cypress official &lt;a href="https://docs.cypress.io/app/references/command-line#cypress-run-spec-lt-spec-gt" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h4&gt;
  
  
  Filter by Test Title (with &lt;a class="mentioned-user" href="https://dev.to/bahmutov"&gt;@bahmutov&lt;/a&gt;/cy-grep)
&lt;/h4&gt;

&lt;p&gt;Cypress does not support filtering by test title out-of-the-box. To accomplish this, you would need to use the &lt;code&gt;cy-grep&lt;/code&gt; plugin.&lt;/p&gt;

&lt;p&gt;To filter tests by title, use the &lt;strong&gt;&lt;code&gt;--env grep=...&lt;/code&gt;&lt;/strong&gt; option in the terminal. If the title string you want to filter includes spaces, make sure to enclose it in quotes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run all tests with "footer" in their title&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;npx cypress run &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nb"&gt;grep&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;footer
&lt;span class="c"&gt;# Run all tests with "footer links" in their title&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;npx cypress run &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nb"&gt;grep&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"footer links"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Title filters are also applied to test suite blocks (&lt;code&gt;describe&lt;/code&gt; and &lt;code&gt;context&lt;/code&gt;). If a suite matches the filter, all tests within that block will be executed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run any tests in the blocks including "footer"&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;npx cypress run &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nb"&gt;grep&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;footer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test footer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;this will run&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;this will also run&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;em&gt;Or Substring Matching&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can provide multiple title substrings to match by separating them with a &lt;code&gt;;&lt;/code&gt; character. Each substring will be trimmed automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run all tests with "footer links" or "header links" in their title&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;npx cypress run &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nb"&gt;grep&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"footer links; header links"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;em&gt;Invert Filter&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can invert a filter by placing the &lt;code&gt;-&lt;/code&gt; character in front of the string to match:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run all tests WITHOUT "footer links" in their title&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;npx cypress run &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nb"&gt;grep&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"-footer links"&lt;/span&gt;
&lt;span class="c"&gt;# Run tests with "footer", but without "links" in the titles&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;npx cypress run &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nb"&gt;grep&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"footer; -links"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In "open" mode, you can also run test by title using the command &lt;code&gt;Cypress.grep()&lt;/code&gt; directly in the browser's DevTools console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Filter tests by title substring&lt;/span&gt;
&lt;span class="nx"&gt;Cypress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;grep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;header links&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn44x17r2bvx76p33xvaq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn44x17r2bvx76p33xvaq.png" alt=" " width="787" height="262"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Additionally, you can run the filtered tests multiple times:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Run filtered tests 10 times&lt;/span&gt;
&lt;span class="nx"&gt;Cypress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;grep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;header links&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;h4&gt;
  
  
  Filter by Tags (with &lt;a class="mentioned-user" href="https://dev.to/bahmutov"&gt;@bahmutov&lt;/a&gt;/cy-grep)
&lt;/h4&gt;

&lt;p&gt;As previously mentioned, Cypress does not natively support filtering tests by tags, however this can be achieved using the &lt;code&gt;cy-grep&lt;/code&gt; plugin.&lt;/p&gt;

&lt;p&gt;You can choose which tests to run or exclude by using tags with the &lt;strong&gt;&lt;code&gt;--env grepTags=...&lt;/code&gt;&lt;/strong&gt; option. By including &lt;em&gt;commas&lt;/em&gt; in the &lt;code&gt;grepTags&lt;/code&gt; environment variable, you can separate multiple tags.&lt;/p&gt;

&lt;p&gt;If a specific tag is not found in the specs, then you will get a warning in the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npx cypress run &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nv"&gt;grepTags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;@wrong-tag
cy-grep: could not find the tag &lt;span class="s2"&gt;"@wrong-tag"&lt;/span&gt; &lt;span class="k"&gt;in &lt;/span&gt;any of the specs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;em&gt;And Tags&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
Use the character &lt;code&gt;+&lt;/code&gt; to indicate that both tags must be present in a suite or test for it to run. You can specify the tag string with or without quotes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run suites and tests that have both tags @smoke and @regression&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;npx cypress run &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nv"&gt;grepTags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"@smoke+@regression"&lt;/span&gt;

&lt;span class="c"&gt;# This one is equivalent&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;npx cypress run &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nv"&gt;grepTags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;@smoke+@regression
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;em&gt;Or Tags&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
When you want to run suites or tests that match one tag or another, separate them with a &lt;em&gt;space&lt;/em&gt; character. In this case, you must always provide the tag string within quotes because of the spaces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run suites and tests that have the tag @visual or the tag @regression&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;npx cypress run &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nv"&gt;grepTags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"@visual @regression"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;em&gt;Inverted Tags&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
You can skip running certain suites and tests that include a specific tag by using the &lt;em&gt;invert&lt;/em&gt; option, which involves prefixing the tag with the character &lt;code&gt;-&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Do not run any suites or tests that have the tag @quarantine&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;npx cypress run &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nv"&gt;grepTags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;-@quarantine

&lt;span class="c"&gt;# Run suites and tests with the tag @smoke but without the tag @quarantine&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;npx cypress run &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nv"&gt;grepTags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;@smoke+-@quarantine

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;em&gt;Not Tags&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
You can exclude tests with a specific tag from running, even if they have a tag that should run, by using the "not" option. Simply prefix the tag with &lt;code&gt;--&lt;/code&gt; to skip it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run suites or tests with the tag @visual or @regression but exclude those with the tag @quarantine&lt;/span&gt;
&lt;span class="c"&gt;# (note that @quarantine is prefixed with -- due to spaces)&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;npx cypress run &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nv"&gt;grepTags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"@visual @regression --@quarantine"&lt;/span&gt;

&lt;span class="c"&gt;# This command is equivalent to the previous one&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;npx cypress run &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nv"&gt;grepTags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'@visual+-@quarantine @regression+-@quarantine'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;em&gt;Grep untagged tests&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To run tests without any tags that are also within suites without tags, set the environment variable &lt;code&gt;grepUntagged&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; when run in the CLI command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npx cypress run &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nv"&gt;grepUntagged&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;em&gt;grepFilterSpecs and grepOmitFiltered&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Previously, we recommended setting up these two environment variables when installing the &lt;code&gt;@bahmutov/cy-grep&lt;/code&gt; plugin. Now, let's explore their intended use.&lt;/p&gt;

&lt;p&gt;By default, when using the &lt;code&gt;@bahmutov/cy-grep&lt;/code&gt; plugin, all specifications are run, and all internal filters (such as title-based filters) are applied. This process can result in unnecessary time being spent.&lt;/p&gt;

&lt;p&gt;To pre-filter specs, you can set the environment variable &lt;strong&gt;&lt;code&gt;grepFilterSpecs&lt;/code&gt;&lt;/strong&gt; to &lt;code&gt;true&lt;/code&gt;. Note that &lt;code&gt;grepFilterSpecs&lt;/code&gt; works only with positive "greps" and is not compatible with inverted ones.&lt;/p&gt;

&lt;p&gt;Additionally, the plugin defaults to marking all filtered tests as &lt;strong&gt;pending&lt;/strong&gt; by using &lt;code&gt;it.skip()&lt;/code&gt;. To completely omit these tests from the output, set the environment variable &lt;strong&gt;&lt;code&gt;grepOmitFiltered&lt;/code&gt;&lt;/strong&gt; to &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If these environment variables were not set as configuration options during plugin installation, you can specify their values for the run directly in the CLI command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Filter all spec files, and run the suites and tests with the tag "@smoke"&lt;/span&gt;
&lt;span class="c"&gt;# and do not display the filtered tests in the terminal&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;npx cypress run &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nv"&gt;grepTags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;@smoke,grepFilterSpecs&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;,grepOmitFiltered&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's examine what the terminal will display when it encounters the spec file &lt;code&gt;test-groups.cy.js&lt;/code&gt; that finds no suite or test matching the specified tags &lt;code&gt;@user-reg+@smoke&lt;/code&gt;, under various configurations of &lt;code&gt;grepFilterSpecs&lt;/code&gt; and &lt;code&gt;grepOmitFiltered&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;With the command &lt;code&gt;npx cypress run --env grepTags=@user-reg+@smoke,grepFilterSpecs=false,grepOmitFiltered=false&lt;/code&gt;, all specifications are executed, filters (such as filters by title) are applied, and any filtered tests are marked as skipped:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ig442622viq773nacv2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ig442622viq773nacv2.png" alt=" " width="722" height="580"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;With the command &lt;code&gt;npx cypress run --env grepTags=@user-reg+@smoke,grepFilterSpecs=true,grepOmitFiltered=false&lt;/code&gt;, the output may look the same; however, specifications are NOT executed (significantly reducing runtime), filters are NOT applied (further saving time), and filtered tests are marked as skipped.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F48xj36twj1qz8y2ndyj4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F48xj36twj1qz8y2ndyj4.png" alt=" " width="713" height="579"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;With the command &lt;code&gt;npx cypress run --env grepTags=@user-reg+@smoke,grepFilterSpecs=true,grepOmitFiltered=true&lt;/code&gt;, specifications are NOT executed, filters are NOT applied, and filtered tests are omitted from the output:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkbe754izyiqwvg7oibxi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkbe754izyiqwvg7oibxi.png" alt=" " width="726" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With this plugin, you can also run tests by tags using the &lt;code&gt;Cypress.grep()&lt;/code&gt; command in the browser's DevTools console when running Cypress in 'open' mode.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Run filtered tests by tags (with tag @smoke or @regression)&lt;/span&gt;
&lt;span class="nx"&gt;Cypress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;grep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@smoke @regression&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6bhr03h2sg25i8ya6jeh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6bhr03h2sg25i8ya6jeh.png" alt=" " width="787" height="261"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And also run by test title, tags and multiple times:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Run tests with title containing "footer" and tag @regression 10 times&lt;/span&gt;
&lt;span class="nx"&gt;Cypress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;grep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;footer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@regression&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;NOTE:&lt;/strong&gt; Cypress supports a &lt;code&gt;--tag&lt;/code&gt; parameter when running tests in the terminal or CI pipeline. However, this &lt;code&gt;--tag&lt;/code&gt; parameter is not used to filter tests by specific tags. Instead, it is used to add &lt;strong&gt;tags to the recorded run&lt;/strong&gt;, making it easier to identify them when displayed in Cypress Cloud.&lt;/p&gt;


&lt;pre class="highlight shell"&gt;&lt;code&gt;cypress run &lt;span class="nt"&gt;--record&lt;/span&gt; &lt;span class="nt"&gt;--tag&lt;/span&gt; &lt;span class="s2"&gt;"regression,nightly"&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fonjq6bwsuespp0ieyhqj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fonjq6bwsuespp0ieyhqj.png" alt=" " width="557" height="85"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h4&gt;
  
  
  Plugin cypress-cli-select (by Dennis Bergevin)
&lt;/h4&gt;

&lt;p&gt;Recently, &lt;a href="https://www.linkedin.com/in/dennis-bergevin/" rel="noopener noreferrer"&gt;Dennis Bergevin&lt;/a&gt; released a plugin called &lt;a href="https://github.com/dennisbergevin/cypress-cli-select" rel="noopener noreferrer"&gt;cypress-cli-select&lt;/a&gt;, which allows you to &lt;strong&gt;interactively select&lt;/strong&gt; and run specific tests or groups of tests directly from your terminal. It uses the &lt;code&gt;@bahmutov/cy-grep&lt;/code&gt; plugin as its core engine.&lt;/p&gt;

&lt;p&gt;To install the plugin, add &lt;code&gt;cypress-cli-select&lt;/code&gt; and &lt;code&gt;@bahmutov/cy-grep&lt;/code&gt; as devDependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-dev&lt;/span&gt; cypress-cli-select

&lt;span class="nv"&gt;$ &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-dev&lt;/span&gt; @bahmutov/cy-grep
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of manually typing out spec file paths or using complex command line arguments, &lt;code&gt;cypress-cli-select&lt;/code&gt; visually presents a list of your test files, suites, individual test titles, or tags, enabling you to select which ones to execute.&lt;/p&gt;

&lt;p&gt;Just type in the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npx cypress-cli-select run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And "select" you go!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fufup89smeppux34bx399.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fufup89smeppux34bx399.png" alt=" " width="800" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using the &lt;code&gt;--choose-spec-pattern&lt;/code&gt; option, you can run selected specs in a specific order. However, in this case, you will not have the option to run test titles or tags.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npx cypress-cli-select run &lt;span class="nt"&gt;--choose-spec-pattern&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a very cool and useful plugin, especially when you are still developing and debugging your tests. &lt;strong&gt;Give it a try — it’s worth it!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can learn everything about this plugin in its documentation. Additionally, there’s an &lt;a href="https://www.youtube.com/watch?v=lnehIEzJhlI" rel="noopener noreferrer"&gt;intro video&lt;/a&gt; by Gleb that explains how to use it.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  PLAYWRIGHT TEST FILTERS
&lt;/h3&gt;

&lt;p&gt;Playwright offers robust native support for filtering tests, allowing you to target specific &lt;strong&gt;spec files&lt;/strong&gt;, utilize &lt;strong&gt;tags&lt;/strong&gt;, or focus on test &lt;strong&gt;titles&lt;/strong&gt;, giving you greater flexibility and precision in test execution.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h4&gt;
  
  
  Filter by Spec files
&lt;/h4&gt;

&lt;p&gt;You can run specific test files in the CLI by simply providing their file names. There's no need to include the full path, as Playwright will search all the directories within the &lt;code&gt;tests&lt;/code&gt; folder that match the specified name.&lt;/p&gt;

&lt;p&gt;Additionally, you can execute all test files within specific directories by passing the names of these directories (no need to pass the full path). If the directories contain nested subdirectories, Playwright will recursively include and execute all the tests within them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run two specific test files provided&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;npx playwright &lt;span class="nb"&gt;test &lt;/span&gt;login.spec.ts logout.spec.ts

&lt;span class="c"&gt;# Run all tests within the two specified folders&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;npx playwright &lt;span class="nb"&gt;test &lt;/span&gt;tests/contacts/ tests/profile/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to run tests that contain specific keywords in the file name, simply pass those keywords to the CLI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Runs tests that contain the keywords 'login' or 'logout'&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;npx playwright &lt;span class="nb"&gt;test &lt;/span&gt;login &lt;span class="nb"&gt;logout&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;h4&gt;
  
  
  Filter by Test Title
&lt;/h4&gt;

&lt;p&gt;To execute a test with a specific title, simply use the &lt;code&gt;-g&lt;/code&gt; flag followed by the substring, enclosed in quotes, that must match the test title.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Runs all the tests that contain the string "validate contacts" in their title&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;npx playwright &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="s2"&gt;"validate contacts"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! Quick, easy, and hassle free! 🍰&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h4&gt;
  
  
  Filter by Tags
&lt;/h4&gt;

&lt;p&gt;You can run specific test suites (&lt;code&gt;describe&lt;/code&gt;) or tests using the &lt;code&gt;--grep&lt;/code&gt; option followed by the tags. Make sure the tags are enclosed in quotes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Runs tests with the tag "@query"&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;npx playwright &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--grep&lt;/span&gt; &lt;span class="s2"&gt;"@query"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a specific tag is not found in the specs, then you will get a warning in the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npx playwright &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--grep&lt;/span&gt; &lt;span class="s2"&gt;"@wrong-tag"&lt;/span&gt;
Error: No tests found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;em&gt;And Tags&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To run suites and tests that include both tags (&lt;strong&gt;AND&lt;/strong&gt; condition), you will need to use &lt;em&gt;regex lookaheads&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Regex lookaheads&lt;/em&gt; are a type of &lt;strong&gt;zero-width assertion&lt;/strong&gt; that allow you to check whether a specific pattern exists (positive lookahead) or does not exist (negative lookahead) ahead in the input string without consuming characters. Lookaheads do not form part of the final match; instead, they assert a condition that must or must not be true.&lt;/p&gt;

&lt;p&gt;Types of Lookaheads:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Positive Lookahead: &lt;code&gt;(?=...)&lt;/code&gt;&lt;br&gt;
Ensures that the specified condition is present ahead in the string.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Example: &lt;code&gt;a(?=b)&lt;/code&gt; finds an &lt;code&gt;a&lt;/code&gt; only if it is immediately followed by &lt;code&gt;b&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Match: &lt;code&gt;"ab"&lt;/code&gt;, No match: &lt;code&gt;"ac"&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Negative Lookahead: &lt;code&gt;(?!...)&lt;/code&gt;&lt;br&gt;
Ensures that the specified condition is not present ahead in the string.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Example: &lt;code&gt;a(?!b)&lt;/code&gt; finds an &lt;code&gt;a&lt;/code&gt; only if it is not followed by &lt;code&gt;b&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Match: &lt;code&gt;"ac"&lt;/code&gt;, No match: &lt;code&gt;"ab"&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Runs tests with the tag "@api" and tag "@sanity"&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;npx playwright &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--grep&lt;/span&gt; &lt;span class="s2"&gt;"(?=.*@api)(?=.*@sanity)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flewar2ljtsu3cdb7fqkw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flewar2ljtsu3cdb7fqkw.png" alt=" " width="800" height="121"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Well... if you ask me, I’d have to say that creating a simple AND condition is considerably more complex compared to the &lt;code&gt;cy-grep&lt;/code&gt; plugin we discussed for Cypress. 😒&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Or Tags&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you want to run suites and tests that match one tag *&lt;em&gt;OR&lt;/em&gt; another, separate the tags using the &lt;code&gt;|&lt;/code&gt; character.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Runs tests with the tag "@regression" or tag "@sanity"&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;npx playwright &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--grep&lt;/span&gt; &lt;span class="s2"&gt;"@regression|@sanity"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh1fb1dw50deluit0x52u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh1fb1dw50deluit0x52u.png" alt=" " width="800" height="119"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Inverted Tags&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can skip running specific suites and tests that include a particular tag by using the &lt;code&gt;--grep-invert&lt;/code&gt; option.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Skip the tests that includes the tag "@sanity"&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;npx playwright &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--grep-invert&lt;/span&gt; &lt;span class="s2"&gt;"@sanity"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkc6gtsnv0qairdnv7cmu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkc6gtsnv0qairdnv7cmu.png" alt=" " width="800" height="137"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can also filter tests in the configuration file via &lt;a href="https://playwright.dev/docs/api/class-testconfig#test-config-grep" rel="noopener noreferrer"&gt;testConfig.grep&lt;/a&gt; and &lt;a href="https://playwright.dev/docs/api/class-testproject#test-project-grep" rel="noopener noreferrer"&gt;testProject.grep&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h4&gt;
  
  
  Plugin playwright-cli-select (by Dennis Bergevin)
&lt;/h4&gt;

&lt;p&gt;And since Dennis didn’t want Playwright to be left behind, he created a &lt;a href="https://github.com/dennisbergevin/playwright-cli-select" rel="noopener noreferrer"&gt;playwright-cli-select&lt;/a&gt; plugin (the counterpart of his &lt;code&gt;cypress-cli-select&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Because Playwright natively supports filtering tests by spec file, test title, and tags, it relies solely on the Playwright core, making its installation even faster than the Cypress version of the plugin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-dev&lt;/span&gt; playwright-cli-select
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To run the plugin, simply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npx playwright-cli-select run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you get an amazing CLI user interface to select the filters 'on the go':&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcv3rcsmc2ti14pmnsude.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcv3rcsmc2ti14pmnsude.png" alt=" " width="800" height="338"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  ACT3: RESOLUTION
&lt;/h1&gt;

&lt;p&gt;Alright, how can I put this in the clearest way possible about what everyone’s been silently thinking as they read through this article?&lt;/p&gt;

&lt;p&gt;Why &lt;strong&gt;are not&lt;/strong&gt; tags and filters by title supported in Cypress as part of its core functionality? Why must users rely on a third-party plugin developed by members of the Cypress community to handle such a basic feature?&lt;/p&gt;

&lt;p&gt;Both major grep plugins for Cypress were developed by the same individual, Gleb Bahmutov. Notably, the one featured in the official plugin directory hasn't seen updates in quite some time. It raises the question—what would the level of support for this feature be without Gleb's contributions?&lt;/p&gt;

&lt;p&gt;I have to admit, tagging and test filtering in Playwright feels far more convenient and user-friendly. Yes, I’m not a huge fan of how conditional AND filtering by test names is handled, but overall, in my opinion, everything else tips the balance heavily in favor of Playwright.&lt;/p&gt;

&lt;p&gt;Ok, I said it—someone had to, right?! 🤐&lt;/p&gt;

&lt;p&gt;One last thing, though... If you want to rerun the last failed tests, Playwright supports this natively with the &lt;code&gt;--last-failed&lt;/code&gt; flag in the CLI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npx playwright &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--last-failed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And what about Cypress? Well, what do you think? 🎲&lt;br&gt;
Bingo! You’ll need a plugin!&lt;/p&gt;

&lt;p&gt;For this, you can use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The aforementioned &lt;code&gt;@bahmutov/cy-grep&lt;/code&gt; plugin by Gleb Bahmutov and invoke the command &lt;code&gt;Cypress.grepFailed()&lt;/code&gt; from the DevTools Console.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fox2frcsr0eb266w3dhoo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fox2frcsr0eb266w3dhoo.png" alt=" " width="800" height="399"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Or the &lt;a href="https://github.com/dennisbergevin/cypress-plugin-last-failed" rel="noopener noreferrer"&gt;cypress-plugin-last-failed&lt;/a&gt; created by Dennis Bergevin, which works in both run mode and open mode. It also supports CI/CD pipelines.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqdr5yk3e3ifpn1gk4rdx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqdr5yk3e3ifpn1gk4rdx.png" alt=" " width="562" height="331"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want to experiment with all the examples discussed in this article, you can clone the repository &lt;a href="https://github.com/sclavijosuero/cypress-vs-playwright-frameworks" rel="noopener noreferrer"&gt;sclavijosuero/cypress-vs-playwright-frameworks&lt;/a&gt;. 🧪 🧫&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;strong&gt;I'd love to hear from you! Please don't forget to follow me, leave a comment, or a reaction if you found this article useful or insightful.&lt;/strong&gt;&lt;/em&gt; ❤️ 🦄 🤯 🙌 🔥&lt;/p&gt;

&lt;p&gt;You can also connect with me on my new &lt;strong&gt;YouTube channel&lt;/strong&gt;: &lt;a href="https://www.youtube.com/@SebastianClavijoSuero" rel="noopener noreferrer"&gt;https://www.youtube.com/@SebastianClavijoSuero&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you'd like to support my work, consider buying me a coffee or contributing to a training session, so I can keep learning and sharing cool stuff with all of you.&lt;br&gt;
Thank you for your support!&lt;br&gt;
&lt;a href="https://www.buymeacoffee.com/sclavijosuero" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.buymeacoffee.com%2Fbuttons%2Fv2%2Fdefault-yellow.png" alt="Buy Me A Coffee" width="545" height="153"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cypress</category>
      <category>qa</category>
      <category>playwright</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
