<?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>DEV Community: Sebastian Clavijo Suero</title>
    <description>The latest articles on DEV Community by Sebastian Clavijo Suero (@sebastianclavijo).</description>
    <link>https://dev.to/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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1453622%2F5e79b768-40f4-411c-be62-94b82d72af43.jpg</url>
      <title>DEV Community: Sebastian Clavijo Suero</title>
      <link>https://dev.to/sebastianclavijo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sebastianclavijo"/>
    <language>en</language>
    <item>
      <title>Podcast: QA Through the Lens of a Former PM Turned QA Engineer</title>
      <dc:creator>Sebastian Clavijo Suero</dc:creator>
      <pubDate>Sat, 17 Oct 2026 18:38:27 +0000</pubDate>
      <link>https://dev.to/sebastianclavijo/podcast-qa-through-the-lens-of-a-former-pm-turned-qa-engineer-5762</link>
      <guid>https://dev.to/sebastianclavijo/podcast-qa-through-the-lens-of-a-former-pm-turned-qa-engineer-5762</guid>
      <description>&lt;p&gt;I have been immersed in a lot of software stuff, some very good, some not that good. I was a software engineer for many years, but one of the roles where I learned more about product development was as a &lt;strong&gt;Product Manager&lt;/strong&gt; after 5 years in that role.&lt;/p&gt;

&lt;p&gt;I always was a &lt;strong&gt;quality advocate at heart&lt;/strong&gt;, even during my developer phase, but I realized that I wanted to &lt;strong&gt;spend the rest of my career as a QA engineer&lt;/strong&gt;, and building testing tools as an SDET that were not only useful but easy to use.&lt;/p&gt;

&lt;p&gt;I invite you to watch the first 2 episodes of &lt;a href="https://www.linkedin.com/in/ivdavidov/" rel="noopener noreferrer"&gt;Ivan Davidov&lt;/a&gt;'s podcast where we both discuss QA Through the Lens of a Former PM Turned QA Engineer (truly yours!) 🙂&lt;/p&gt;

&lt;p&gt;Episode 1: &lt;a href="https://www.youtube.com/watch?v=Re0iRhLZ4s8" rel="noopener noreferrer"&gt;&lt;strong&gt;Why Sprints Really Break, From a PM Turned QA (ft. Sebastian Clavijo)&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Episode 2 (cont.): &lt;a href="https://www.youtube.com/watch?v=NcFHcyO1TkM" rel="noopener noreferrer"&gt;&lt;strong&gt;Fake It Till You Make It Is BS: A Staff QA's Career Rules&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I hope you find it insightful and enjoy watching it.&lt;br&gt;
PS: We had a lot of fun recording it! 😄&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;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>testing</category>
      <category>careerdevelopment</category>
      <category>podcast</category>
    </item>
    <item>
      <title>Stop Guessing Where Your Accessibility Issues Are: Unleash the WICK-AG-Lens Chrome Extension to Target Them on the Spot 🎯</title>
      <dc:creator>Sebastian Clavijo Suero</dc:creator>
      <pubDate>Tue, 06 Oct 2026 03:02:04 +0000</pubDate>
      <link>https://dev.to/sebastianclavijo/stop-guessing-where-your-accessibility-issues-are-unleash-the-wick-ag-lens-chrome-extension-to-1n8m</link>
      <guid>https://dev.to/sebastianclavijo/stop-guessing-where-your-accessibility-issues-are-unleash-the-wick-ag-lens-chrome-extension-to-1n8m</guid>
      <description>&lt;p&gt;&lt;strong&gt;Table of Contents&lt;/strong&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;
Features Overview

&lt;ul&gt;
&lt;li&gt;1. Show, Don't Just Tell (Visual Highlighting)&lt;/li&gt;
&lt;li&gt;2. The Separation of Concerns: Violations vs. Informative&lt;/li&gt;
&lt;li&gt;3. Strict WCAG Criteria Filtering&lt;/li&gt;
&lt;li&gt;4. Immediate Context &amp;amp; Interactive Panels&lt;/li&gt;
&lt;li&gt;5. Precision Element Locators&lt;/li&gt;
&lt;li&gt;6. Intuitive, Organized UI &amp;amp; Built-In Documentation&lt;/li&gt;
&lt;li&gt;7. Built for the Modern Web (Shadow DOM &amp;amp; Iframes)&lt;/li&gt;
&lt;li&gt;8. Zero-Friction Ticketing (One-Click Reporting)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
Audit Library and Ruleset

&lt;ul&gt;
&lt;li&gt;Tab 1: Violations and Warnings&lt;/li&gt;
&lt;li&gt;Tab 2: Informative and Structure&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Limitations and Technical Bounds&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ACT 3: RESOLUTION&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&amp;nbsp;&lt;br&gt;
Let's be brutally honest. For many teams, accessibility testing feels like eating vegetables. We know it matters. We want to do the right thing. But it almost always gets pushed to the very end of the meal (or sprint, either way). It becomes a chore, an afterthought, or worse, a massive QA bottleneck right before release.&lt;/p&gt;

&lt;p&gt;Why? The feedback loop is broken.&lt;/p&gt;

&lt;p&gt;Developers write code, push it, and maybe later a CI pipeline throws an error. Or a QA engineer logs a ticket with a vague description like, &lt;em&gt;"Screen reader does not read this button."&lt;/em&gt; Then begins the tedious dance of inspecting the DOM, hunting for the exact element, and trying to remember the specific WCAG rule that was violated. The friction is incredibly high.&lt;/p&gt;

&lt;p&gt;A while back, I introduced a Cypress plugin called &lt;strong&gt;wick-a11y&lt;/strong&gt;, read the release article &lt;strong&gt;&lt;a href="https://dev.to/sebastianclavijo/wick-a11y-cypress-plugin-your-unstoppable-ally-for-smashing-accessibility-barriers-cool-as-john-wick-280a"&gt;here&lt;/a&gt;&lt;/strong&gt;. Think of it as your John Wick style ally for smashing accessibility barriers inside your E2E tests. It is great for guarding your pipeline. But what about before you even push your code? What if you could see accessibility issues as clearly as a glaring UI bug while you are actively developing?&lt;/p&gt;

&lt;p&gt;Enter &lt;strong&gt;WICK-AG-Lens: Accessibility Inspector&lt;/strong&gt; extension.&lt;br&gt;
&amp;nbsp;&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc3g41i6u7t4htf013wrb.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc3g41i6u7t4htf013wrb.png" alt=" " width="800" height="343"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;br&gt;
It is a brand new free and &lt;strong&gt;open-source&lt;/strong&gt; &lt;em&gt;Chrome browser extension&lt;/em&gt; designed to eliminate the friction between writing code and testing for accessibility. No more digging through console logs. No more guessing. Just immediate visual feedback directly within your page layout.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9b1iqsshjno1w8q5tftu.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9b1iqsshjno1w8q5tftu.png" alt=" " width="800" height="442"&gt;&lt;/a&gt;&lt;br&gt;
&amp;nbsp;&lt;/p&gt;




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

&lt;p&gt;&amp;nbsp;&lt;br&gt;
Building an accessibility inspector is not just about throwing rules at a page. It is about how that information is presented to developers, QA testers, and accessibility professionals so they can actually use it. WICK-AG-Lens was engineered around a few core philosophies to drastically reduce your cognitive load.&lt;/p&gt;

&lt;h2&gt;
  
  
  Features Overview
&lt;/h2&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Show, Don't Just Tell (Visual Highlighting)
&lt;/h3&gt;

&lt;p&gt;Context is everything. Instead of printing a sterile list of errors in a DevTools console, WICK-AG-Lens draws highly visible bounding boxes and color-coded badges directly over the target elements on your page. Seeing a &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; without a label visually highlighted right where it sits in the layout immediately tells you how to fix it. Reading a console error requires mental translation.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fadtpts628f5arjsw9l3d.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fadtpts628f5arjsw9l3d.png" alt=" " width="652" height="623"&gt;&lt;/a&gt;&lt;br&gt;
&amp;nbsp;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The Separation of Concerns: Violations vs. Informative
&lt;/h3&gt;

&lt;p&gt;A major source of fatigue in accessibility tooling is noise. To combat this, I divided the 40 independent audits into two distinct tabs: actionable violations and structural information checks.&lt;/p&gt;

&lt;p&gt;Automated tools cannot understand intent. A script can easily verify that a focus path exists and check the order of &lt;em&gt;tabindex&lt;/em&gt; attributes. But it takes a human to look at that highlighted path on the screen and realize the focus jumps wildly from the header, down to the footer, and back up to the main navigation. A machine sees valid code. A human sees a terrible user experience. By separating these tabs, developers can ruthlessly hunt real bugs in the Violations tab, while accessibility professionals and QA can explore the architecture in the Informative tab without the noise.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fofafy5yk7bz2fflo7mu7.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fofafy5yk7bz2fflo7mu7.png" alt=" " width="654" height="784"&gt;&lt;/a&gt;&lt;br&gt;
&amp;nbsp;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Strict WCAG Criteria Filtering
&lt;/h3&gt;

&lt;p&gt;Not every project targets the same compliance standard. WICK-AG-Lens includes a dynamic filter bar allowing you to specifically toggle target &lt;strong&gt;WCAG Versions (2.0, 2.1, 2.2)&lt;/strong&gt; and &lt;strong&gt;Conformance Levels (A, AA, AAA)&lt;/strong&gt;. The UI and execution engine will instantly adapt, hiding irrelevant audits or specific legend criteria (like differing AA vs. AAA contrast thresholds) so you only see exactly what you are testing for.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxyefvi7cne0vj37fvrdl.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxyefvi7cne0vj37fvrdl.png" alt=" " width="800" height="245"&gt;&lt;/a&gt;&lt;br&gt;
&amp;nbsp;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Immediate Context &amp;amp; Interactive Panels
&lt;/h3&gt;

&lt;p&gt;Clicking on any badge on the page opens an interactive panel. This panel tells you exactly what the tool highlighted, why it is important for users with disabilities, and provides the exact CSS selector or logic applied to identify 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9nbb4bwedw9624zy7ksx.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9nbb4bwedw9624zy7ksx.png" alt=" " width="800" height="742"&gt;&lt;/a&gt;&lt;br&gt;
&amp;nbsp;&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Precision Element Locators
&lt;/h3&gt;

&lt;p&gt;There is also a "&lt;strong&gt;View Elements&lt;/strong&gt;" button in the DevTools panel. This opens a modal containing the list of all DOM elements identified by the accessibility audit. From there, you just click the crosshair icon next to an element and the extension instantly scrolls the affected element into view, marking it with a highly visible pulsating ring.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffc3j55mmzsjy5l308tie.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffc3j55mmzsjy5l308tie.png" alt=" " width="800" height="442"&gt;&lt;/a&gt;&lt;br&gt;
&amp;nbsp;&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Intuitive, Organized UI &amp;amp; Built-In Documentation
&lt;/h3&gt;

&lt;p&gt;The extension is designed for maximum configuration in a simple way. The panel utilizes expandable/collapsible category groups to keep your workspace clean and focused. It is fully keyboard-friendly. Plus, if you ever need a refresher on a rule, a complete documentation modal is embedded directly inside the extension, so no need to leave the browser or search a wiki to understand the technical bounds.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F052ws3wzvvjqfdyv88k2.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F052ws3wzvvjqfdyv88k2.png" alt=" " width="670" height="910"&gt;&lt;/a&gt;&lt;br&gt;
&amp;nbsp;&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Built for the Modern Web (Shadow DOM &amp;amp; Iframes)
&lt;/h3&gt;

&lt;p&gt;The modern web is a messy place. The scanner utilizes custom deep bottom-up traversal logic to pierce standard open web components to evaluate encapsulated markup. It also fully traverses the internal DOM of same-origin iframes, so you aren't left blind when inspecting complex widgets.&lt;br&gt;
&amp;nbsp;&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Zero-Friction Ticketing (One-Click Reporting)
&lt;/h3&gt;

&lt;p&gt;QA engineers and testers spend half their lives writing bug tickets. WICK-AG-Lens includes a one-click reporting feature that instantly copies formatted text reports of all active audits and their findings directly to your clipboard. You just click the button, paste the clean text into Jira or GitHub, and move on.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F21naj384zimf4gbqhvf6.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F21naj384zimf4gbqhvf6.png" alt=" " width="660" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz5jdp8vitjpssij2iv7c.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz5jdp8vitjpssij2iv7c.png" alt=" " width="800" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  Audit Library and Ruleset
&lt;/h2&gt;

&lt;p&gt;WICK-AG-Lens performs 40 independent audits. Here is exactly what it is looking for under the hood:&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h3&gt;
  
  
  Tab 1: Violations and Warnings
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Keyboard Navigation&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Unfocusable Clickables:&lt;/strong&gt; Flags generic elements with &lt;code&gt;onclick&lt;/code&gt; handlers lacking &lt;code&gt;tabindex&lt;/code&gt; and &lt;code&gt;role&lt;/code&gt;. Keyboard users cannot tab to or activate these elements.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Severity:&lt;/em&gt; Critical&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/keyboard.html" rel="noopener noreferrer"&gt;2.1.1 Keyboard&lt;/a&gt; (2.0 A)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;"Unfocusable Clickable"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Disabled Focus Outlines:&lt;/strong&gt; Parses page CSS via heuristic evaluation to find focusable elements where &lt;code&gt;outline: none&lt;/code&gt; is applied on focus without a fallback. Sighted keyboard users rely entirely on focus rings.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Severity:&lt;/em&gt; Critical&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/focus-visible.html" rel="noopener noreferrer"&gt;2.4.7 Focus Visible&lt;/a&gt; (2.0 AA)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;"Disabled Focus Ring (Heuristic)"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Tabindex Violations:&lt;/strong&gt; Flags elements with &lt;code&gt;tabindex &amp;gt; 0&lt;/code&gt;, overriding natural DOM focus order and creating unpredictable navigation paths.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Severity:&lt;/em&gt; Serious&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/focus-order.html" rel="noopener noreferrer"&gt;2.4.3 Focus Order&lt;/a&gt; (2.0 A)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;tabindex="{val}"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Accesskey Attributes:&lt;/strong&gt; Flags explicit &lt;code&gt;accesskey&lt;/code&gt; usage, which often conflicts with native browser or screen reader shortcuts.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Severity:&lt;/em&gt; Moderate&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/character-key-shortcuts.html" rel="noopener noreferrer"&gt;2.1.4 Character Key Shortcuts&lt;/a&gt; (2.1 A)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;accesskey="{val}"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Images &amp;amp; Media&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Missing Alt Text:&lt;/strong&gt; Flags &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; elements completely lacking an &lt;code&gt;alt&lt;/code&gt; attribute. Screen readers will read the raw filename if missing.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Severity:&lt;/em&gt; Critical&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/non-text-content.html" rel="noopener noreferrer"&gt;1.1.1 Non-text Content&lt;/a&gt; (2.0 A)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;"Missing alt"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Uncaptioned Video:&lt;/strong&gt; Flags &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; elements lacking nested &lt;code&gt;&amp;lt;track&amp;gt;&lt;/code&gt; elements for captions.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Severity:&lt;/em&gt; Critical&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/captions-prerecorded.html" rel="noopener noreferrer"&gt;1.2.2 Captions (Prerecorded)&lt;/a&gt; (2.0 A)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;"&amp;lt;video&amp;gt; (No Captions)"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Redundant Alt Text:&lt;/strong&gt; Flags &lt;code&gt;alt&lt;/code&gt; attributes containing words like "image" or "photo". Screen readers automatically announce images, making prefixes redundant.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Severity:&lt;/em&gt; Minor&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/non-text-content.html" rel="noopener noreferrer"&gt;1.1.1 Non-text Content&lt;/a&gt; (2.0 A)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;Redundant alt: "{text}"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Forms &amp;amp; Controls&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Unlabeled Form Inputs:&lt;/strong&gt; Flags form controls missing &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; wrappers, &lt;code&gt;aria-label&lt;/code&gt;, or &lt;code&gt;for&lt;/code&gt; attributes.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Severity:&lt;/em&gt; Critical&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/labels-or-instructions.html" rel="noopener noreferrer"&gt;3.3.2 Labels or Instructions&lt;/a&gt; (2.0 A)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;"Unlabeled Input"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Empty Buttons:&lt;/strong&gt; Flags buttons with no text, aria-label, or child image alt. Screen readers will only announce 'Button'.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Severity:&lt;/em&gt; Critical&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html" rel="noopener noreferrer"&gt;4.1.2 Name, Role, Value&lt;/a&gt; (2.0 A)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;"Empty Button"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Placeholder as Label:&lt;/strong&gt; Flags inputs relying solely on placeholders for their accessible name.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Severity:&lt;/em&gt; Serious&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html" rel="noopener noreferrer"&gt;4.1.2 Name, Role, Value&lt;/a&gt; (2.0 A)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;"Placeholder as Label"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Links &amp;amp; Navigation&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Empty Links:&lt;/strong&gt; Flags &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; tags lacking readable text or accessible names.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Severity:&lt;/em&gt; Critical&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/link-purpose-in-context.html" rel="noopener noreferrer"&gt;2.4.4 Link Purpose (In Context)&lt;/a&gt; (2.0 A)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;"Empty Link"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Suspicious Link Targets:&lt;/strong&gt; Flags anchor tags acting as buttons (&lt;code&gt;href="#"&lt;/code&gt; or &lt;code&gt;javascript:&lt;/code&gt;) without a button role, which breaks spacebar operability.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Severity:&lt;/em&gt; Critical / Serious&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html" rel="noopener noreferrer"&gt;4.1.2 Name, Role, Value&lt;/a&gt; (2.0 A), &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/keyboard.html" rel="noopener noreferrer"&gt;2.1.1 Keyboard&lt;/a&gt; (2.0 A)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;"Fake Button (JS)"&lt;/code&gt; or &lt;code&gt;"Fake Button (#)"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Generic Link Text:&lt;/strong&gt; Flags ambiguous link text like "Click Here" or "Read More" using exact word boundaries.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Severity:&lt;/em&gt; Moderate&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/link-purpose-in-context.html" rel="noopener noreferrer"&gt;2.4.4 Link Purpose (In Context)&lt;/a&gt; (2.0 A)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;Generic link: "{text}"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Unwarned New Window Links:&lt;/strong&gt; Flags &lt;code&gt;target="_blank"&lt;/code&gt; links lacking text or ARIA warnings, which unexpectedly disorients users.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Severity:&lt;/em&gt; Moderate&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/change-on-request.html" rel="noopener noreferrer"&gt;3.2.5 Change on Request&lt;/a&gt; (2.0 AAA)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;'target="_blank" (No Warning)'&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;ARIA &amp;amp; Semantics&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Focusable in Aria-Hidden:&lt;/strong&gt; Flags elements that receive keyboard focus but are hidden from Assistive Technologies via &lt;code&gt;aria-hidden="true"&lt;/code&gt; on the element or an ancestor, creating a "ghost focus" trap.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Severity:&lt;/em&gt; Critical&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html" rel="noopener noreferrer"&gt;4.1.2 Name, Role, Value&lt;/a&gt; (2.0 A)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;"aria-hidden (Focusable)"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Invalid ARIA State:&lt;/strong&gt; Flags elements marked with &lt;code&gt;aria-invalid="true"&lt;/code&gt; indicating validation errors.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Severity:&lt;/em&gt; Serious&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/error-identification.html" rel="noopener noreferrer"&gt;3.3.1 Error Identification&lt;/a&gt; (2.0 A)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;'aria-invalid="true"'&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Prohibited Author Names:&lt;/strong&gt; Flags &lt;code&gt;presentation&lt;/code&gt; or &lt;code&gt;none&lt;/code&gt; roles that improperly contain aria labels, breaking semantics.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Severity:&lt;/em&gt; Serious&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html" rel="noopener noreferrer"&gt;4.1.2 Name, Role, Value&lt;/a&gt; (2.0 A)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;Prohibited name on role="{role}"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Structure and Document&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Auto-playing Media:&lt;/strong&gt; Flags audio/video with &lt;code&gt;autoplay&lt;/code&gt; enabled, which conflicts with screen reader announcements.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Severity:&lt;/em&gt; Serious&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/audio-control.html" rel="noopener noreferrer"&gt;1.4.2 Audio Control&lt;/a&gt; (2.0 A)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;"Autoplay Media"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Heading Hierarchy Errors:&lt;/strong&gt; Flags skipped sequential levels or fake structural classes to maintain logical document outlines.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Severity:&lt;/em&gt; Moderate&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html" rel="noopener noreferrer"&gt;1.3.1 Info and Relationships&lt;/a&gt; (2.0 A)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;"{TAG} (Skipped)"&lt;/code&gt; or &lt;code&gt;"Fake Heading"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Visual &amp;amp; Contrast&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Text Color Contrast:&lt;/strong&gt; Calculates the effective contrast ratio between visible text and its backing container, dynamically factoring in font-size and weight for large-text thresholds.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Severity:&lt;/em&gt; Critical&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html" rel="noopener noreferrer"&gt;1.4.3 Contrast (Minimum)&lt;/a&gt; (2.0 AA), &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/contrast-enhanced.html" rel="noopener noreferrer"&gt;1.4.6 Contrast (Enhanced)&lt;/a&gt; (2.0 AAA)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;AA Fail ({ratio}:1)&lt;/code&gt;, &lt;code&gt;AAA Fail ({ratio}:1)&lt;/code&gt;, or &lt;code&gt;Manual Check (Bg)&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h3&gt;
  
  
  Tab 2: Informative and Structure
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Keyboard Navigation&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Tabindex (Comparison):&lt;/strong&gt; Highlights all tabindex usages simultaneously to provide a visual map of the programmatic focus strategy.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/focus-order.html" rel="noopener noreferrer"&gt;2.4.3 Focus Order&lt;/a&gt; (2.0 A)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;tabindex="{val}"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Display Focus Order:&lt;/strong&gt; Traces and numbers the natural sequential path of focusable elements, dynamically flagging elements hidden from AT.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/focus-order.html" rel="noopener noreferrer"&gt;2.4.3 Focus Order&lt;/a&gt; (2.0 A)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;#{counter}: &amp;lt;{tag}&amp;gt;&lt;/code&gt; or &lt;code&gt;#{counter}: &amp;lt;{tag}&amp;gt; (Hidden from AT)&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Images &amp;amp; Media&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Display Image Alternatives:&lt;/strong&gt; Extracts and visually prints the alt text for all &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; elements.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/non-text-content.html" rel="noopener noreferrer"&gt;1.1.1 Non-text Content&lt;/a&gt; (2.0 A)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;alt="{val}"&lt;/code&gt;, &lt;code&gt;alt=""&lt;/code&gt;, or &lt;code&gt;"Missing alt"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Captioned Video:&lt;/strong&gt; Highlights video elements that correctly implement &lt;code&gt;&amp;lt;track&amp;gt;&lt;/code&gt; captions.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/captions-prerecorded.html" rel="noopener noreferrer"&gt;1.2.2 Captions (Prerecorded)&lt;/a&gt; (2.0 A)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;"&amp;lt;video&amp;gt; (Captioned)"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Iframe Context &amp;amp; Titles:&lt;/strong&gt; Extracts explicitly defined titles from embedded iframes.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html" rel="noopener noreferrer"&gt;4.1.2 Name, Role, Value&lt;/a&gt; (2.0 A)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;title="{title}"&lt;/code&gt;, &lt;code&gt;"Hidden Iframe"&lt;/code&gt;, or &lt;code&gt;"Missing Title"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Forms &amp;amp; Controls&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Form Field Descriptions:&lt;/strong&gt; Identifies supplemental instructional text linked to inputs via &lt;code&gt;aria-describedby&lt;/code&gt; or &lt;code&gt;title&lt;/code&gt;.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/labels-or-instructions.html" rel="noopener noreferrer"&gt;3.3.2 Labels or Instructions&lt;/a&gt; (2.0 A)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;"aria-describedby"&lt;/code&gt; or &lt;code&gt;"title"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Fieldsets &amp;amp; Captions:&lt;/strong&gt; Highlights visual grouping containers and their titles.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html" rel="noopener noreferrer"&gt;1.3.1 Info and Relationships&lt;/a&gt; (2.0 A)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;&amp;lt;{tag}&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Touch Target Sizes:&lt;/strong&gt; Computes the exact width and height pixel boundaries of interactive elements to catch undersized targets.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/target-size-minimum.html" rel="noopener noreferrer"&gt;2.5.8 Target Size (Minimum)&lt;/a&gt; (2.2 AA), &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/target-size.html" rel="noopener noreferrer"&gt;2.5.5 Target Size (Enhanced)&lt;/a&gt; (2.1 AAA)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;{width}x{height}px&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Links &amp;amp; Navigation&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Warned New Window Links:&lt;/strong&gt; Highlights &lt;code&gt;target="_blank"&lt;/code&gt; links that correctly include accessible warnings.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/change-on-request.html" rel="noopener noreferrer"&gt;3.2.5 Change on Request&lt;/a&gt; (2.0 AAA)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;'target="_blank" (Warned)'&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;ARIA &amp;amp; Semantics&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;ARIA Roles &amp;amp; Attributes:&lt;/strong&gt; Maps all elements utilizing an explicit role definition.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html" rel="noopener noreferrer"&gt;4.1.2 Name, Role, Value&lt;/a&gt; (2.0 A)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;role="{val}"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;ARIA Live Regions:&lt;/strong&gt; Maps dynamic &lt;code&gt;aria-live&lt;/code&gt; injection containers.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/status-messages.html" rel="noopener noreferrer"&gt;4.1.3 Status Messages&lt;/a&gt; (2.1 AA)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;aria-live="{val}"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Required Fields:&lt;/strong&gt; Maps mandatory inputs enforced via HTML5 &lt;code&gt;required&lt;/code&gt; or &lt;code&gt;aria-required&lt;/code&gt;.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/error-identification.html" rel="noopener noreferrer"&gt;3.3.1 Error Identification&lt;/a&gt; (2.0 A)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;"required"&lt;/code&gt; or &lt;code&gt;'aria-required="true"'&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Aria-Expanded State:&lt;/strong&gt; Maps expandable widgets, color-coded by true or false state.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html" rel="noopener noreferrer"&gt;4.1.2 Name, Role, Value&lt;/a&gt; (2.0 A)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;aria-expanded="{val}"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Aria-Controls:&lt;/strong&gt; Highlights elements that dictate the visibility of remote DOM elements.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html" rel="noopener noreferrer"&gt;4.1.2 Name, Role, Value&lt;/a&gt; (2.0 A)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;"aria-controls"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Aria-Owns:&lt;/strong&gt; Highlights elements establishing parent/child relationships outside the DOM tree.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html" rel="noopener noreferrer"&gt;4.1.2 Name, Role, Value&lt;/a&gt; (2.0 A)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;"aria-owns"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Structure &amp;amp; Document&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Valid Heading Order:&lt;/strong&gt; Maps the standard, sequential H1 to H6 structure.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html" rel="noopener noreferrer"&gt;1.3.1 Info and Relationships&lt;/a&gt; (2.0 A)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;&amp;lt;{tag}&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Landmark Regions:&lt;/strong&gt; Maps native HTML5 and ARIA structural containers.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/bypass-blocks.html" rel="noopener noreferrer"&gt;2.4.1 Bypass Blocks&lt;/a&gt; (2.0 A)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;&amp;lt;{role or tag}&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Table &amp;amp; Grid Structure:&lt;/strong&gt; Highlights native tables, captions, headers, and custom div-based grids.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html" rel="noopener noreferrer"&gt;1.3.1 Info and Relationships&lt;/a&gt; (2.0 A)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;&amp;lt;{tag}&amp;gt;&lt;/code&gt; or &lt;code&gt;role="{role}"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Lists and List Items:&lt;/strong&gt; Maps unordered, ordered, and glossary lists and children.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html" rel="noopener noreferrer"&gt;1.3.1 Info and Relationships&lt;/a&gt; (2.0 A)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;&amp;lt;{tag}&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Language Definitions:&lt;/strong&gt; Maps the root document language declaration alongside inline phonetic shifts.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;WCAG Alignment:&lt;/em&gt; &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/language-of-page.html" rel="noopener noreferrer"&gt;3.1.1 Language of Page&lt;/a&gt; (2.0 A), &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/language-of-parts.html" rel="noopener noreferrer"&gt;3.1.2 Language of Parts&lt;/a&gt; (2.0 AA)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Badge Output:&lt;/em&gt; &lt;code&gt;"Missing Root Lang"&lt;/code&gt;, &lt;code&gt;Root lang="{val}"&lt;/code&gt;, or &lt;code&gt;lang="{val}"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations and Technical Bounds
&lt;/h2&gt;

&lt;p&gt;Before you dive in, it is important to understand the technical boundaries of what an automated browser extension can and cannot do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Visibility &amp;amp; Occlusion:&lt;/strong&gt; It strictly ignores hidden elements (&lt;code&gt;display: none&lt;/code&gt;, &lt;code&gt;inert&lt;/code&gt;, &lt;code&gt;hidden&lt;/code&gt;, etc.) to perfectly match the native accessibility tree, but it cannot calculate 3D visual occlusion (e.g., an element covered by a high z-index modal).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Shadow DOM &amp;amp; Iframes:&lt;/strong&gt; It successfully pierces &lt;em&gt;open&lt;/em&gt; Web Components and &lt;em&gt;same-origin&lt;/em&gt; iframes. Closed shadow roots and cross-origin iframes (like embedded third-party videos) remain inaccessible due to strict browser security policies.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Contrast Heuristics:&lt;/strong&gt; Contrast is calculated via computed DOM styles. While it dynamically computes thresholds based on font size and weight, elements layered over complex images or CSS gradients are automatically flagged for 'Manual Check'.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Filter Isolation:&lt;/strong&gt; WCAG version and conformance filters are strictly isolated. Selecting '2.2' does not automatically include '2.1' or '2.0'. You must explicitly check all versions and levels you wish to evaluate for your current analysis.
&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  ACT 3: RESOLUTION
&lt;/h1&gt;

&lt;p&gt;&amp;nbsp;&lt;br&gt;
Accessibility is not just a checkbox to tick off before a release. It is about making the web work for everyone. But that doesn't mean the process of building accessible software has to be painful.&lt;/p&gt;

&lt;p&gt;With &lt;strong&gt;&lt;a href="https://github.com/sclavijosuero/wick-a11y" rel="noopener noreferrer"&gt;wick-a11y&lt;/a&gt;&lt;/strong&gt; guarding your Cypress pipelines and WICK-AG-Lens riding shotgun in your browser, you have a pretty solid duo to keep the noise down and the quality up. The goal is to catch these barriers early, fix them fast, and get back to building cool things.&lt;/p&gt;

&lt;p&gt;Don't wait around. Install the extension, pull up your favorite notoriously inaccessible website, and watch the badges light up like a hit list. No bloated audits, just sharp, uncompromising accessibility inspection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://chromewebstore.google.com/detail/accessibility-inspector/fjbgedagallcokpkfdlmpafcffblmlhd" rel="noopener noreferrer"&gt;Install WICK-AG-Lens from the Chrome Web Store&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The entire blueprint is open-source on GitHub at &lt;strong&gt;&lt;a href="https://github.com/sclavijosuero/wick-ag-lens" rel="noopener noreferrer"&gt;sclavijosuero/wick-ag-lens&lt;/a&gt;&lt;/strong&gt;. If it gets the job done, pay the bounty with a star on the repository. Otherwise, I might be forced to call my friend Johnny to set things straight. 😎&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, and I get my boost to keep learning and sharing cool stuff with all of you.&lt;/p&gt;

&lt;p&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>a11y</category>
      <category>frontend</category>
      <category>testing</category>
      <category>tools</category>
    </item>
    <item>
      <title>The Cypress i18n Mistake: Testing Words Instead of Meaning - i18next is your partner</title>
      <dc:creator>Sebastian Clavijo Suero</dc:creator>
      <pubDate>Sun, 10 May 2026 20:42:28 +0000</pubDate>
      <link>https://dev.to/sebastianclavijo/the-cypress-i18n-mistake-testing-words-instead-of-meaning-i18next-is-your-partner-k8n</link>
      <guid>https://dev.to/sebastianclavijo/the-cypress-i18n-mistake-testing-words-instead-of-meaning-i18next-is-your-partner-k8n</guid>
      <description>&lt;h3&gt;
  
  
  A Better Way to Test Multilanguage Apps in Cypress
&lt;/h3&gt;

&lt;p&gt;Table of contents&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;The Problem With Copy-Pasting Translations&lt;/li&gt;
&lt;li&gt;What if Cypress Could Be Polyglot?&lt;/li&gt;
&lt;li&gt;Let Me Introduce You to i18next, If You Haven’t Met It Yet&lt;/li&gt;
&lt;li&gt;Where Should the Translations Live?&lt;/li&gt;
&lt;li&gt;Creating a Small &lt;code&gt;cy.init()&lt;/code&gt; and &lt;code&gt;cy.t()&lt;/code&gt; Commands&lt;/li&gt;
&lt;li&gt;What About Fallbacks?&lt;/li&gt;
&lt;li&gt;Running the Same Test Across Multiple Languages&lt;/li&gt;
&lt;li&gt;A Small Example With a Language Switcher: &lt;code&gt;cy.changeLanguage()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Should We Use Translated Text to Click Buttons?&lt;/li&gt;
&lt;li&gt;When Exact Text Actually Matters&lt;/li&gt;
&lt;li&gt;A Healthier Mental Model&lt;/li&gt;
&lt;li&gt;Interpolation: When Translations Need Dynamic Values&lt;/li&gt;
&lt;li&gt;Namespaces: When One Translation File Is Not Enough&lt;/li&gt;
&lt;li&gt;i18next Can Do More Than Just Interpolation&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;ACT 3: RESOLUTION&lt;/li&gt;

&lt;/ul&gt;




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

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;There is a very common situation that appears when testing applications that support multiple languages.&lt;/p&gt;

&lt;p&gt;At first, everything looks innocent.&lt;br&gt;
You have an application in English, so you 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;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;Login&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;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;Welcome back&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;Easy enough, right?&lt;/p&gt;

&lt;p&gt;Then, one day, the Product team says: “&lt;strong&gt;We are going international!&lt;/strong&gt;”&lt;br&gt;
And suddenly your application also supports &lt;em&gt;Spanish&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;So now you write:&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;Iniciar sesión&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;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;Bienvenido de nuevo&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;Easy enough!&lt;/p&gt;

&lt;p&gt;Then &lt;em&gt;French&lt;/em&gt; arrives.&lt;br&gt;
Then &lt;em&gt;Portuguese&lt;/em&gt;.&lt;br&gt;
Then &lt;em&gt;German&lt;/em&gt;.&lt;br&gt;
And how about &lt;em&gt;Mandarin&lt;/em&gt;?&lt;/p&gt;

&lt;p&gt;After that someone from UX changes "Welcome back" to "Good to see you again". &lt;/p&gt;

&lt;p&gt;And of course, the Spanish translator decides that "Bienvenido de nuevo" should actually be "Qué bueno verte de nuevo".&lt;/p&gt;

&lt;p&gt;So your Cypress test fails, and then you ask yourself the existential automation question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is my test failing because the application is broken, or because somebody "improved" the copy?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And that, my QA friend, is where the fun begins.&lt;/p&gt;

&lt;p&gt;There are some articles about internationalization in Cypress, but surprisingly, not that many.&lt;/p&gt;

&lt;p&gt;And as a multicultural and multilingual person, I have always been interested in how, a well-designed multilingual website, can connect with people across languages, cultures, and contexts, and in doing so, create a much bigger impact.&lt;/p&gt;


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

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The problem is not testing multilingual applications. Obviously, we absolutely should test them.&lt;/p&gt;

&lt;p&gt;The problem (and big mistake) starts when we copy-paste translated text directly into our Cypress tests and pretend that this is a stable strategy.&lt;/p&gt;

&lt;p&gt;This could be a typical test for creating a new project while also validating the messages shown to the 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="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;Nuevo proyecto&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;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;Nombre del proyecto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Mi proyecto 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;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;Crear proyecto&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;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;Proyecto creado correctamente&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks simple!&lt;br&gt;
But what are we really testing?&lt;/p&gt;

&lt;p&gt;Are we testing that the login flow works?&lt;br&gt;
Are we testing that the _Spanish _translation exists?&lt;br&gt;
Are we testing that the exact copy has not changed?&lt;br&gt;
Are we testing that the translator did not open the thesaurus with too much confidence?&lt;/p&gt;

&lt;p&gt;Maybe all of the above?&lt;br&gt;
That is the problem.&lt;/p&gt;

&lt;p&gt;Do not get me wrong.&lt;/p&gt;

&lt;p&gt;Using &lt;code&gt;cy.contains()&lt;/code&gt; with visible text is not evil at all. Actually, I like it a lot when it is used with intention. It makes tests readable, and in many cases, it reflects how users experience the application.&lt;/p&gt;

&lt;p&gt;But in a multilingual app, hardcoded translated strings can quickly become a maintenance nightmare.&lt;/p&gt;

&lt;p&gt;The test is no longer expressing the meaning of the user journey. It is expressing one particular version of the translated words at one particular moment in time.&lt;/p&gt;

&lt;p&gt;In other words: &lt;strong&gt;We are testing words instead of meaning.&lt;/strong&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  The Problem With Copy-Pasting Translations
&lt;/h3&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;A very common Cypress approach for testing the login user journey would 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="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;/login?lng=es&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;Iniciar sesión&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;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;Bienvenido de nuevo&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;This works.&lt;br&gt;
Until it does not! 🤔&lt;/p&gt;

&lt;p&gt;The moment the &lt;em&gt;Spanish&lt;/em&gt; translation changes, the test breaks, even if the application is working perfectly.&lt;/p&gt;

&lt;p&gt;Of course, sometimes this is exactly what we want. If the purpose of the test is to validate a very specific legal disclaimer, marketing text, warning message, or compliance-related copy, then yes, the exact words matter.&lt;/p&gt;

&lt;p&gt;But for most application flows, the exact words are not the real behavior.&lt;br&gt;
The behavior looks something like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The app loads in Spanish.&lt;/li&gt;
&lt;li&gt;The login button is translated.&lt;/li&gt;
&lt;li&gt;The user can log in.&lt;/li&gt;
&lt;li&gt;The welcome message is shown in the selected language.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the meaning. The translated string is only the visible representation of that meaning.&lt;/p&gt;

&lt;p&gt;So the real question is: &lt;strong&gt;How can Cypress verify the meaning without copy-pasting every translated word?&lt;/strong&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  What if Cypress Could Be Polyglot?
&lt;/h3&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Imagine this.&lt;/p&gt;

&lt;p&gt;Instead of telling Cypress to find one exact &lt;em&gt;Spanish&lt;/em&gt; sentence, we ask Cypress to find whatever &lt;code&gt;welcome&lt;/code&gt; means in &lt;em&gt;Spanish&lt;/em&gt;, and then we assert that the application shows it.&lt;/p&gt;

&lt;p&gt;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;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auth.welcome&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="s1"&gt;es&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;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="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;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A command like &lt;code&gt;cy.t('auth.welcome', { lng: 'es' })&lt;/code&gt; would get the &lt;em&gt;Spanish&lt;/em&gt; translation for the auth welcome message, and then we can use that value in the assertion.&lt;/p&gt;

&lt;p&gt;Now the test is not responsible for knowing the final translated sentence. The test only knows the meaning, and that, after all, is what is actually relevant for the test.&lt;/p&gt;

&lt;p&gt;The key:&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;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;welcome&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;represents the meaning.&lt;/p&gt;

&lt;p&gt;The 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="nx"&gt;es&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;represents the locale we want.&lt;/p&gt;

&lt;p&gt;And the translation system takes care of the rest. Definitely that would be a much better contract.&lt;/p&gt;

&lt;p&gt;The test would no longer be saying find "Bienvenido de nuevo", but find whatever &lt;code&gt;auth.welcome&lt;/code&gt; means in Spanish.&lt;/p&gt;




&lt;h3&gt;
  
  
  Let Me Introduce You to i18next, If You Haven’t Met It Yet
&lt;/h3&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;If your application already uses &lt;a href="https://www.i18next.com/" rel="noopener noreferrer"&gt;&lt;code&gt;i18next&lt;/code&gt;&lt;/a&gt;, then you probably already have the concepts we need.&lt;/p&gt;

&lt;p&gt;But, at a very basic level, &lt;code&gt;i18next&lt;/code&gt; gives us two important things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A way to initialize the translation system.&lt;/li&gt;
&lt;li&gt;A way to resolve a translation key into actual text.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The initialization happens with the &lt;code&gt;i18next.init()&lt;/code&gt; method. This method receives a &lt;strong&gt;configuration object&lt;/strong&gt; where we define things like the current language, fallback language, and translation resources:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;i18next.init(options, callback) // returns a Promise&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;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;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;es&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Current language&lt;/span&gt;
  &lt;span class="na"&gt;fallbackLng&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&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Fallback language to use if message key not found&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="na"&gt;en&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// English translations&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="c1"&gt;// "translation" is the default namespace (we will see namespaces later)&lt;/span&gt;
        &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;login&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Message auth.login in 'en'&lt;/span&gt;
          &lt;span class="na"&gt;welcome&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 back&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;// Message auth.welcome in 'en'&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="na"&gt;es&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// Spanish translations&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="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;login&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Iniciar sesión&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Message auth.login in 'en'&lt;/span&gt;
          &lt;span class="na"&gt;welcome&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bienvenido de nuevo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;// Message auth.welcome in 'en'&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The callback function can be used for example to inform in the console if something went wrong 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;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;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;fallbackLng&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&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="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;t&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;err&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;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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Something went wrong loading&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// -&amp;gt; same as i18next.t&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we can use the &lt;code&gt;i18next.t()&lt;/code&gt; method to resolve a translation key into the corresponding text. In simple terms, the method &lt;code&gt;t()&lt;/code&gt; receives a &lt;strong&gt;message key&lt;/strong&gt; and an optional &lt;strong&gt;configuration object&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;i18next.t(key, options)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For example:&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;i18next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auth.welcome&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="s1"&gt;es&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;So the idea is very simple: &lt;strong&gt;Let Cypress use the same translation mechanism that the application might already use&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Where Should the Translations Live?
&lt;/h3&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Writing all translations directly inside &lt;code&gt;i18next.init()&lt;/code&gt; could be very ugly, specially if we have a very large number of messages.&lt;/p&gt;

&lt;p&gt;Something like the &lt;code&gt;.init&lt;/code&gt; example we used above may be fine for a tiny demo, but in a real application, it makes much more sense for translations to live in separate files.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;src/locales/en.json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;English&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;file&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"auth"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"login"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Login"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"welcome"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Welcome back"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;src/locales/es.json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Spanish&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;file&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"auth"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"login"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Iniciar sesión"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"welcome"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bienvenido de nuevo"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is cleaner for a few reasons:&lt;/p&gt;

&lt;p&gt;First, maintenance: Translations change often. Keeping them in JSON files makes them easier to update without touching test logic.&lt;/p&gt;

&lt;p&gt;Second, abstraction: The test does not need to know every word in every language. It only needs to know the key that represents the meaning.&lt;/p&gt;

&lt;p&gt;Third, it works like a dictionary. To adding a third, fourth, or fifth language becomes much easier, and your tests can still work like a charm.&lt;/p&gt;

&lt;p&gt;You are basically saying "For this language, this key means this sentence." And that is exactly what we want.&lt;/p&gt;

&lt;p&gt;A translation file is our dictionary.&lt;br&gt;
Cypress should ask the dictionary.&lt;br&gt;
&lt;strong&gt;Cypress should NOT become the dictionary.&lt;/strong&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  Creating a Small cy.init() and cy.t() Commands
&lt;/h3&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Now let’s make this easier to use inside Cypress, of course, using custom commands!&lt;/p&gt;

&lt;p&gt;We can create two commands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;cy.initI18n()&lt;/code&gt; to initialize the translation system.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cy.t()&lt;/code&gt; to translate a key.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They 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;// cypress/support/commands.js&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;i18next&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;i18next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;// Do not forget "npm install i18next" first :)&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;enMsgs&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;../../src/locales/en.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;// English "dictionary"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;esMsgs&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;../../src/locales/es.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;// The Spanish one&lt;/span&gt;

&lt;span class="c1"&gt;// cy.init() command will receive the exact same arguments as i18next.init()&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;initI18n&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;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="nx"&gt;callback&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;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&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Use English as the default language if no language is provided&lt;/span&gt;
      &lt;span class="na"&gt;fallbackLng&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&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 fallback language will be English&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="na"&gt;en&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="nx"&gt;enMsgs&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c1"&gt;// Provide the English translations&lt;/span&gt;
        &lt;span class="na"&gt;es&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="nx"&gt;esMsgs&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c1"&gt;// And the Spanish translations&lt;/span&gt;
      &lt;span class="p"&gt;},&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="c1"&gt;// Pass along any other options supported by i18next.init()&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;callback&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="c1"&gt;// Do not show the wrap() command in the Cypress Log&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&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;t&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="nx"&gt;will&lt;/span&gt; &lt;span class="nx"&gt;receive&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;exact&lt;/span&gt; &lt;span class="nx"&gt;same&lt;/span&gt; &lt;span class="nx"&gt;arguments&lt;/span&gt; &lt;span class="k"&gt;as&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;t&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="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;t&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;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&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;// Get the translation for the provided key in the currently configured&lt;/span&gt;
  &lt;span class="c1"&gt;// language, and apply any i18next options if provided.&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;i18next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now our tests can use 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="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;Login page&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;beforeEach&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;// Initialize our polyglot Cypress system and set Spanish as current language&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;initI18n&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;es&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;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;/login?lng=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="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 login experience in Spanish&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;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auth.login&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;loginText&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;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loginText&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="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auth.welcome&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;welcomeText&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;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;welcomeText&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="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 is much better!&lt;/p&gt;

&lt;p&gt;And if the Spanish translation changes from &lt;code&gt;"welcome": "Bienvenido de nuevo"&lt;/code&gt; to &lt;code&gt;"welcome": "Qué bueno verte de nuevo"&lt;/code&gt;, then no drama.&lt;/p&gt;

&lt;p&gt;We change the message in the corresponding translation file, and &lt;strong&gt;the test itself does not need to change because the meaning did not change.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Only the wording changed, and wording is exactly what translation files are supposed to manage.&lt;/p&gt;




&lt;h3&gt;
  
  
  What About Fallbacks?
&lt;/h3&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Another nice thing about using &lt;code&gt;i18next&lt;/code&gt; is that we can also use its language &lt;strong&gt;fallback&lt;/strong&gt; behavior. A fallback is basically what happens when the translation you asked for does not exist in the selected language.&lt;/p&gt;

&lt;p&gt;For example, when we initialize our message system 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;initI18n&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;es&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Selected language is Spanish&lt;/span&gt;
  &lt;span class="na"&gt;fallbackLng&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&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;// Fallback language is English&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means that if the Spanish translation is missing, try English.&lt;/p&gt;

&lt;p&gt;This can be useful when the application itself behaves this way. And that last sentence is important:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your Cypress configuration should reflect your application behavior. If your app falls back to English, your Cypress translation helper should also fall back to English.&lt;/li&gt;
&lt;li&gt;If your app does not allow missing translations, your test should probably be stricter.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can also resolve the key using a &lt;strong&gt;specific language&lt;/strong&gt; directly from the &lt;code&gt;cy.t()&lt;/code&gt; command, regardless of the language currently initialized:&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;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auth.forgotPassword&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="s1"&gt;es&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;Or use a &lt;strong&gt;default value&lt;/strong&gt; when the key cannot be resolved:&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;// Passing as a second argument&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;t&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.key&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;This is the default value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Or within the options object&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;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auth.forgotPassword&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;defaultValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Forgot 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;Or even better, instead of hardcoding a default value in the test, you can provide an array of fallback keys as the first argument. If the first key cannot be resolved, &lt;code&gt;i18next&lt;/code&gt; will try the next one, and so 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="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auth.forgotPassword&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;common.help&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;The important part is that our custom &lt;code&gt;cy.t()&lt;/code&gt; command does not need to know every possible option.&lt;/p&gt;

&lt;p&gt;It simply passes the options to &lt;code&gt;i18next.t()&lt;/code&gt;, because it has been defined 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;t&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;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&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="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;i18next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;We can use the power of &lt;code&gt;i18next&lt;/code&gt; without reinventing it inside Cypress&lt;/strong&gt;, which is always nice.&lt;/p&gt;

&lt;p&gt;Because reinventing things inside Cypress tests is how many horror stories begin.&lt;/p&gt;




&lt;h3&gt;
  
  
  Running the Same Test Across Multiple Languages
&lt;/h3&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Once we have this setup, testing multiple languages becomes much cleaner.&lt;/p&gt;

&lt;p&gt;Check out this code:&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;// Supported languages&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;languages&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="s1"&gt;en&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;es&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;fr&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;// Iterate over all supported languages&lt;/span&gt;
&lt;span class="nx"&gt;languages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;lng&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;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Login page in &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;lng&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;beforeEach&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;initI18n&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;lng&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="s2"&gt;`/login?lng=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;lng&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;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Shows the translated login experience for language &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;lng&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="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;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auth.login&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;loginText&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;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loginText&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="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auth.welcome&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;welcomeText&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;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;welcomeText&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="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;We are not duplicating the same test with different hardcoded strings.&lt;/p&gt;

&lt;p&gt;Instead we are expressing the real intention:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For each supported language, the login page should render the expected translated content.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is cleaner.&lt;br&gt;
It is less code.&lt;br&gt;
It is more scalable.&lt;/p&gt;

&lt;p&gt;And most importantly, that is easier to maintain when your application inevitably keeps changing. Because it will.&lt;/p&gt;

&lt;p&gt;Applications always change.&lt;br&gt;
That is their favorite hobby.&lt;/p&gt;


&lt;h3&gt;
  
  
  A Small Example With a Language Switcher: cy.changeLanguage()
&lt;/h3&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;To change the current language in &lt;code&gt;i18next&lt;/code&gt; without re-initializing, we can use the &lt;code&gt;i18next.changeLanguage()&lt;/code&gt; method. This method is designed to switch languages at runtime and will automatically trigger a re-render of components if you are using bindings like &lt;code&gt;react-i18next&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;i18next.changeLanguage(lng, callback) // returns a Promise&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We can use &lt;code&gt;i18next.changeLanguage()&lt;/code&gt; through a new Cypress custom command, &lt;code&gt;cy.changeLanguage()&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;// cy.changeLanguage() command will receive the exact same arguments as i18next.changeLanguage()&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;changeLanguage&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;lng&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&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;i18next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;changeLanguage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&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="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then running the same tests across multiple languages would be 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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;languages&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="s1"&gt;en&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;es&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;fr&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nf"&gt;before&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;initI18n&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&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="c1"&gt;// Initialize i18next once, using English by default&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;languages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;lng&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;beforeEach&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;changeLanguage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lng&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="s2"&gt;`/login?lng=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;lng&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;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;``&lt;/span&gt;&lt;span class="nx"&gt;Shows&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;translated&lt;/span&gt; &lt;span class="nx"&gt;login&lt;/span&gt; &lt;span class="nx"&gt;experience&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;language&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`e`&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;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auth.login&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;loginText&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;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loginText&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="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auth.welcome&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;welcomeText&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;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;welcomeText&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="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;No need to execute the full &lt;code&gt;cy.initI18n()&lt;/code&gt; process for each language.&lt;/p&gt;

&lt;p&gt;We initialize it once in the &lt;code&gt;before()&lt;/code&gt; hook, and then we simply switch the current language in the &lt;code&gt;beforeEach()&lt;/code&gt; hook before each test.&lt;/p&gt;




&lt;h3&gt;
  
  
  Should We Use Translated Text to Click Buttons?
&lt;/h3&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Well...&lt;/p&gt;

&lt;p&gt;It depends.&lt;/p&gt;

&lt;p&gt;I know, I know. That is the classic "let me sit comfortably on the fence" answer.&lt;/p&gt;

&lt;p&gt;But hear me out.&lt;/p&gt;

&lt;p&gt;If your intention is to verify that the translated button text appears on the page, then using the translated value makes sense:&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;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auth.login&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;loginText&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;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loginText&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But if your intention is simply to interact with the login button and continue the test flow, then a stable selector like &lt;code&gt;data-cy&lt;/code&gt; or &lt;code&gt;data-testid&lt;/code&gt;, IMO, is usually a better 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="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="login-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;click&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 the distinction I personally like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use selectors to interact with the elements in the DOM.
&lt;/li&gt;
&lt;li&gt;Use translations to verify the localized user experience.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this case:&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="login-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;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;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auth.welcome&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;welcomeText&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;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;welcomeText&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the test does not depend on the button text to perform the click. But it still verifies that the expected translated welcome message appears.&lt;/p&gt;

&lt;p&gt;That is a nice &lt;strong&gt;balance&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And as we know, balance is usually where maintainable test automation lives. &lt;strong&gt;Somewhere between chaos and overengineering&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Remember: &lt;strong&gt;Do not hide bad selectors behind i18n!&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  When Exact Text Actually Matters
&lt;/h3&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Now, before someone sharpens their keyboard in the comments, let me clarify something.&lt;/p&gt;

&lt;p&gt;I never said: &lt;em&gt;never&lt;/em&gt; assert exact translated text. That would be too extreme. And extreme rules in testing usually age like milk.&lt;/p&gt;

&lt;p&gt;There are cases where exact text absolutely matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Legal disclaimers&lt;/li&gt;
&lt;li&gt;Error messages with regulatory requirements&lt;/li&gt;
&lt;li&gt;Payment warnings&lt;/li&gt;
&lt;li&gt;Accessibility instructions&lt;/li&gt;
&lt;li&gt;Medical, financial, or security-related messages&lt;/li&gt;
&lt;li&gt;Marketing copy that must be approved&lt;/li&gt;
&lt;li&gt;Any text where the exact wording is the actual requirement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In those cases, exact matching is not only valid, it is absolutely a must.&lt;br&gt;
But then make it intentional.&lt;/p&gt;

&lt;p&gt;If this is the approved legal 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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expectedLegalText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;By continuing, you agree to the Terms and Conditions.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we should explicitly verify the full 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="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="legal-disclaimer"]&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;have.text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expectedLegalText&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test is clearly saying and the exact wording matters here.&lt;/p&gt;

&lt;p&gt;And if you still want that text to come from the translation system, you can combine both ideas:&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;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;legal.termsAndConditions&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;expectedLegalText&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;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="legal-disclaimer"]&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;have.text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expectedLegalText&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;The important part is the intention.&lt;br&gt;
The problem is not text assertions.&lt;br&gt;
The problem is accidental text assertions.&lt;/p&gt;

&lt;p&gt;There is a big difference.&lt;/p&gt;


&lt;h3&gt;
  
  
  A Healthier Mental Model
&lt;/h3&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;A multilingual Cypress test should not try to prove every word in every language on every screen.&lt;/p&gt;

&lt;p&gt;That sounds heroic, but it usually becomes slow, noisy, and painful to maintain.&lt;/p&gt;

&lt;p&gt;A better multilingual test answers questions like:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Did the application load the expected language?&lt;/li&gt;
&lt;li&gt;Did the important user-facing messages come from the right translation keys?&lt;/li&gt;
&lt;li&gt;Can the user complete the main flow in that language?&lt;/li&gt;
&lt;li&gt;Does the app still behave correctly when the locale changes?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is much more valuable than copy-pasting 200 translated strings into a spec file and hoping nobody touches them.&lt;/p&gt;

&lt;p&gt;Because they will be touched. They always are!&lt;br&gt;
Again... that is their favorite hobby. 😄&lt;/p&gt;


&lt;h3&gt;
  
  
  Interpolation: When Translations Need Dynamic Values
&lt;/h3&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;So far, our translations have been static, but real applications love dynamic text.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;'Welcome, Sebastian'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Invoice 1234 was created successfully
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;i18next&lt;/code&gt;, this is usually handled with &lt;strong&gt;interpolation&lt;/strong&gt;. That means the translation has placeholders, and we provide the values later.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"auth"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"welcomeUser"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Welcome, {{name}}"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"invoice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"created"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Invoice {{invoiceId}} was created successfully"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in Cypress, we do not need to change our custom command. Since &lt;code&gt;cy.t()&lt;/code&gt; already passes the options object directly to &lt;code&gt;i18next.t()&lt;/code&gt;, any interpolation values we provide are handled by &lt;code&gt;i18next&lt;/code&gt; automatically.&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;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auth.welcomeUser&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;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;Sebastian&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;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="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;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That resolves to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Welcome, Sebastian
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you can also pass the desired language along with the interpolation values:&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;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;invoice.created&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;invoiceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1234&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;es&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;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="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;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It would resolve to whatever the Spanish translation for invoice.created is, with {{invoiceId}} replaced by 1234.&lt;/p&gt;

&lt;p&gt;It is also possible to provide positional values if your app also uses them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"checkout"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"step"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Step {{0}} of {{1}}"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&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;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;checkout.step&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="mi"&gt;0&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="mi"&gt;1&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="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="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;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That resolves to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Step 2 of 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The nice part is that our &lt;code&gt;cy.t()&lt;/code&gt; command does not need to know if the translation is static, interpolated, simple, or complex.&lt;/p&gt;

&lt;p&gt;It simply delegates to &lt;code&gt;i18next&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use the translation library for translation logic.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Use Cypress for testing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Everyone stays in their lane.&lt;/p&gt;


&lt;h3&gt;
  
  
  Namespaces: When One Translation File Is Not Enough
&lt;/h3&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;As applications grow, one giant translation file can become painful. Very painful.&lt;/p&gt;

&lt;p&gt;For that &lt;code&gt;i18next&lt;/code&gt; supports namespaces. A namespace lets you split translations by area, domain, or feature.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/locales/en/auth.json
src/locales/en/common.json
src/locales/es/auth.json
src/locales/es/common.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;English &lt;code&gt;auth.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"login"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Login"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"welcome"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Welcome back"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;English &lt;code&gt;common.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"save"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Save"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cancel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Cancel"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we can initialize our 'polyglot Cypress' 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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;enAuth&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;../../src/locales/en/auth.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;enCommon&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;../../src/locales/en/common.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;esAuth&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;../../src/locales/es/auth.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;esCommon&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;../../src/locales/es/common.json&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;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="nx"&gt;en&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="na"&gt;en&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;enAuth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// namespace auth (English)&lt;/span&gt;
      &lt;span class="na"&gt;common&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;enCommon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// namespace common (English)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; 
    &lt;span class="na"&gt;es&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;esAuth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// namespace auth (Spanish)&lt;/span&gt;
      &lt;span class="na"&gt;common&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;esCommon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// namespace common (Spanish)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// This tells which namespace to by default when not provided one in .t()&lt;/span&gt;
  &lt;span class="nl"&gt;defaultNS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;common&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 

  &lt;span class="c1"&gt;// It is also good practice to list all available namespaces&lt;/span&gt;
  &lt;span class="na"&gt;ns&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;auth&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;common&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;Here, &lt;code&gt;auth&lt;/code&gt; is the default namespace, that means:&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;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;login&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;will look in the &lt;code&gt;auth&lt;/code&gt; namespace by default.&lt;/p&gt;

&lt;p&gt;But if we want something from &lt;code&gt;common&lt;/code&gt; namespace instead, we will do 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;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;save&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;ns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;common&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;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="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;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or use the namespace prefix format:&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;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;common:save&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;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="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;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Although both approaches can work, personally, I like using the namespace prefix style:&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;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;save&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;ns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;common&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 the most important thing is consistency.&lt;/p&gt;

&lt;p&gt;Pick one style.&lt;br&gt;
And stick to the plan!&lt;/p&gt;

&lt;p&gt;Future you will be grateful... probably. 😉&lt;/p&gt;




&lt;h3&gt;
  
  
  i18next Can Do More Than Just Interpolation
&lt;/h3&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Interpolation is only one part of what &lt;code&gt;i18next&lt;/code&gt; can do. It also supports other features that can be useful in real applications, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Formatting&lt;/strong&gt;: useful for numbers, and values that need locale-specific formatting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plurals&lt;/strong&gt;: when text changes depending on quantity, like "1 item" vs "5 items".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context&lt;/strong&gt;: useful when the translation changes depending on additional context, such as tone, audience, or grammatical differences.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;And I will repeat one more time: The nice thing is that our Cypress command does not need special logic for each one, since we pass the options directly to &lt;code&gt;i18next.t()&lt;/code&gt;!&lt;/strong&gt; 😉&lt;/p&gt;

&lt;p&gt;If you want to become an &lt;code&gt;i18next&lt;/code&gt; master and explore all its options and methods (there are a lot!) check the official site: &lt;a href="https://www.i18next.com/" rel="noopener noreferrer"&gt;https://www.i18next.com/&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  ACT 3: RESOLUTION
&lt;/h2&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Testing multilingual applications is not just about running the same test in different languages.&lt;/p&gt;

&lt;p&gt;It is about deciding what your test should actually care about.&lt;/p&gt;

&lt;p&gt;If your test hardcodes every translated string, you may end up with a suite that fails every time copy changes, even when the product works perfectly.&lt;/p&gt;

&lt;p&gt;But if your test uses the same &lt;code&gt;i18next&lt;/code&gt; translation keys as the application, your assertions become closer to the real meaning of the product.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The key idea is simple: Test the meaning, not the hardcoded words.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use stable selectors for interactions.&lt;br&gt;
Use translation keys for localization assertions.&lt;br&gt;
Use exact text only when exact text is truly the requirement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use interpolation, namespaces, fallbacks, plurals, formatting, and context through &lt;code&gt;i18next&lt;/code&gt;, not through custom Cypress reinventions.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That small shift will make your multilingual Cypress tests cleaner, less repetitive, and much easier to maintain.&lt;/p&gt;

&lt;p&gt;So, the next time you are about to copy-paste a &lt;em&gt;Spanish&lt;/em&gt;, &lt;em&gt;French&lt;/em&gt;, &lt;em&gt;Portuguese&lt;/em&gt;, &lt;em&gt;German&lt;/em&gt;, or &lt;em&gt;Klingon&lt;/em&gt; translation into your Cypress spec, pause for a second and ask yourself:&lt;/p&gt;

&lt;p&gt;Am I testing the product behavior, or am I just testing today’s wording?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Because in multilingual testing, words change. Meaning should not.&lt;/strong&gt;&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;👉 My LinkedIn: &lt;a href="https://www.linkedin.com/in/sebastianclavijosuero/" rel="noopener noreferrer"&gt;linkedin.com/in/sebastianclavijosuero&lt;/a&gt;&lt;br&gt;
👉 My projects and plugin repos: &lt;a href="https://github.com/sclavijosuero" rel="noopener noreferrer"&gt;github.com/sclavijosuero&lt;/a&gt;&lt;br&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;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>testing</category>
      <category>cypress</category>
      <category>qa</category>
      <category>intl</category>
    </item>
    <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://dev.to/sebastianclavijo/wick-dom-observer-the-deterministic-cypress-plugin-for-fast-spinners-blinking-toasts-optional-4e59</link>
      <guid>https://dev.to/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;&amp;nbsp;&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;&amp;nbsp;&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;
&amp;nbsp;&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;&amp;nbsp;&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;
&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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;
&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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;
&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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;
&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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://dev.to/sebastianclavijo/wick-a11y-v301-cypress-v16-ready-upgrade-without-fear-fully-backward-compatible-77m</link>
      <guid>https://dev.to/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;&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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://dev.to/sebastianclavijo/cypresspromiseall-and-cymapchain-two-hidden-gems-for-cypress-you-wont-let-go-once-you-try-20mj</link>
      <guid>https://dev.to/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;&amp;nbsp;&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;&amp;nbsp;&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;
&amp;nbsp;&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;
&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;




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

&lt;p&gt;&amp;nbsp;&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://dev.to/sebastianclavijo/cypress-flaky-testaudit-thriving-in-the-cypress-dual-verse-for-once-l4o</link>
      <guid>https://dev.to/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;&amp;nbsp;&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;&amp;nbsp;&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;
&amp;nbsp;&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;
&amp;nbsp;&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;
&amp;nbsp;&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;
&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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://dev.to/sebastianclavijo/wick-a11y-v230-a-dazzling-new-report-wcag-22-aaa-and-more-goodies-hd0</link>
      <guid>https://dev.to/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;&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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://dev.to/sebastianclavijo/wick-a11y-v220-x-cypress-v15-parabellum-for-axe-essibility-barriers-2fam</link>
      <guid>https://dev.to/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;
&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;
  
  
  ACT 1: EXPOSITION
&lt;/h1&gt;

&lt;p&gt;&amp;nbsp;&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;&amp;nbsp;&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;
&amp;nbsp;&lt;/p&gt;


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

&lt;p&gt;&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;




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

&lt;p&gt;&amp;nbsp;&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;&amp;nbsp;&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://dev.to/sebastianclavijo/the-almighty-playwright-schema-validator-the-unstoppable-triumvirate-playwright-ajv-zod-3jno</link>
      <guid>https://dev.to/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;&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;

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

&lt;p&gt;&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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://dev.to/sebastianclavijo/the-second-most-abused-and-misused-cypress-command-ever-cycontains-126j</link>
      <guid>https://dev.to/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;&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;

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

&lt;p&gt;&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;




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

&lt;p&gt;&amp;nbsp;&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://dev.to/sebastianclavijo/meet-the-new-cypress-schema-validator-when-zod-joined-forces-with-ajv-for-the-ultimate-validation-2o73</link>
      <guid>https://dev.to/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;&amp;nbsp;&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;&amp;nbsp;&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;
&amp;nbsp;&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;
&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;
  
  
  ⏮️ Fully Backward Compatible?
&lt;/h2&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;AFFIRMATIVE! 🤟🤟🤟&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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>
  </channel>
</rss>
