<?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: S Chathuranga Jayasinghe</title>
    <description>The latest articles on DEV Community by S Chathuranga Jayasinghe (@s_chathuranga_j).</description>
    <link>https://dev.to/s_chathuranga_j</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2877326%2Fc658c545-70cf-483b-a050-05e2ba57acfe.jpeg</url>
      <title>DEV Community: S Chathuranga Jayasinghe</title>
      <link>https://dev.to/s_chathuranga_j</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/s_chathuranga_j"/>
    <language>en</language>
    <item>
      <title>Comet Browser: The Revolutionary AI-Powered Browser That Actually Does Your Work</title>
      <dc:creator>S Chathuranga Jayasinghe</dc:creator>
      <pubDate>Wed, 27 Aug 2025 21:25:22 +0000</pubDate>
      <link>https://dev.to/s_chathuranga_j/comet-browser-the-revolutionary-ai-powered-browser-that-actually-does-your-work-n6d</link>
      <guid>https://dev.to/s_chathuranga_j/comet-browser-the-revolutionary-ai-powered-browser-that-actually-does-your-work-n6d</guid>
      <description>&lt;p&gt;Remember when browsers were just glorified document viewers? You'd click links, fill forms manually, and navigate through websites one laborious step at a time. Well, those days just became ancient history with Comet Browser and trust me, once you experience what this thing can do, going back to Chrome feels like using a stone tablet.&lt;/p&gt;

&lt;p&gt;I recently put Comet through its paces with some real-world scenarios, and the results left me genuinely stunned. We're not talking about incremental improvements here; this is a fundamental reimagining of what a browser can be.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Makes Comet Different
&lt;/h2&gt;

&lt;p&gt;Comet is an AI-powered browser that acts as a personal assistant and thinking partner, featuring the Comet Assistant that can automate routine tasks, summarize emails and calendar events, manage tabs, and navigate web pages on behalf of users. But that description barely scratches the surface of what this browser actually accomplishes.&lt;/p&gt;

&lt;p&gt;Comet Browser is the first truly agentic browser, meaning you can not only talk to the built-in Assistant (powered by Perplexity), but you can have it interact with your tabs, emails, calendar, and even navigate the web for you as if an assistant took over your screen.&lt;/p&gt;

&lt;p&gt;The key word here is "agentic." What separates Comet from traditional browsers is its "agentic" architecture instead of acting only when prompted, the Comet Assistant can proactively understand page content, follow your browsing history, and offer to complete multi-step tasks across tabs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Magic in Action: Real-World Testing
&lt;/h2&gt;

&lt;p&gt;Let me paint you a picture of just how mind-blowing this technology is. I decided to test Comet with a practical e-commerce scenario that anyone who's done online shopping can relate to.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario 1: Text-Based Automation
&lt;/h3&gt;

&lt;p&gt;For my first test, I simply typed out a scenario: "Log into saucedemo.com and sort the items by price." That's it. No step-by-step instructions, no detailed navigation commands just a plain English description of what I wanted to accomplish.&lt;/p&gt;

&lt;p&gt;What happened next felt like magic. Comet Browser took over completely, navigating to the website, finding the login elements, entering the credentials, locating the sorting options, and organizing the products by price, all without any input from me. Every click, every form field, every navigation decision was handled autonomously.&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://player.vimeo.com/video/1113710177" width="710" height="399"&gt;
&lt;/iframe&gt;
&lt;br&gt;
Watch as Comet Browser transforms a simple text instruction into a fully automated browsing session, handling every interaction seamlessly from login to product sorting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario 2: Voice-Powered Web Automation
&lt;/h3&gt;

&lt;p&gt;The second test pushed the boundaries even further. Instead of typing, I used Comet's voice feature to verbally describe the same scenario. The browser didn't just understand my speech, it comprehended the intent, processed the multi-step workflow, and executed everything flawlessly.&lt;/p&gt;

&lt;p&gt;Speaking to your browser and watching it perform complex tasks feels like something straight out of a sci-fi movie, except it's happening right now on your desktop.&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://player.vimeo.com/video/1113710212" width="710" height="399"&gt;
&lt;/iframe&gt;
&lt;br&gt;
Experience the future of web browsing as voice commands translate directly into sophisticated web interactions, eliminating the need for manual clicking and typing.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: I even generated the banner image for this article using Comet Assistent. I wrote the article, then opened the Assistant on the side and asked to create a banner image for this article. It just went through the article and generated this banner image!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why This Represents a Paradigm Shift
&lt;/h2&gt;

&lt;p&gt;Launched in July 2025, Comet represents Perplexity AI's attempt to fundamentally change how users interact with the web. Rather than relying on tabs, bookmarks, or search bars, users can now simply ask questions, issue commands, or request tasks, all within a conversational interface.&lt;/p&gt;

&lt;p&gt;This isn't just a browser with AI features bolted on it's a complete reconceptualization of web interaction. Here's why it's such a massive leap forward:&lt;/p&gt;

&lt;h3&gt;
  
  
  From Manual to Conversational
&lt;/h3&gt;

&lt;p&gt;Traditional browsers require you to think like a computer: click here, type there, navigate through menus, remember URLs. Comet lets you think like a human. You describe what you want to accomplish, and it figures out how to do it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Context-Aware Intelligence
&lt;/h3&gt;

&lt;p&gt;At its core is the Comet Assistant, a contextual AI that can read and interact with whatever is on your screen. This isn't a chatbot living in a search bar. The assistant understands not just what you're asking, but the context of what you're currently doing, where you are on the web, and what logical next steps make sense.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-Step Task Automation
&lt;/h3&gt;

&lt;p&gt;Most AI assistants can answer questions or provide information. Comet actually performs work. It can handle complex, multi-step workflows that typically require dozens of manual interactions, reducing them to simple natural language requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Technical Innovation Behind the Magic
&lt;/h2&gt;

&lt;p&gt;What makes Comet possible is a convergence of several breakthrough technologies:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agentic AI Architecture&lt;/strong&gt;: Unlike traditional AI that waits for prompts, Comet's system proactively analyzes web pages, understands their structure, and can interact with any element it encounters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Natural Language Processing&lt;/strong&gt;: The browser doesn't just recognize words, it understands intent, context, and can translate conversational requests into precise web actions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Computer Vision Integration&lt;/strong&gt;: Comet can "see" web pages the way humans do, identifying buttons, forms, links, and interactive elements regardless of how they're coded or styled.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Voice Recognition and Processing&lt;/strong&gt;: The voice interface isn't just speech-to-text — it's speech-to-action, bypassing the need for any manual interaction entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implications for the Future
&lt;/h2&gt;

&lt;p&gt;We're witnessing the birth of what might be called "invisible computing" technology so intuitive that the interface disappears entirely. When you can accomplish complex web tasks through simple conversation, the traditional barriers between human intention and digital execution start to dissolve.&lt;/p&gt;

&lt;p&gt;Think about the implications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Accessibility&lt;/strong&gt;: Complex web tasks become available to users regardless of their technical proficiency&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Productivity&lt;/strong&gt;: Time spent on routine web interactions drops dramatically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User Experience&lt;/strong&gt;: The cognitive load of remembering how to navigate different websites vanishes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automation&lt;/strong&gt;: Personal web workflows can be as simple as having a conversation&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A Word of Caution!
&lt;/h2&gt;

&lt;p&gt;It's worth noting that recent security audits by Brave and Guardio have revealed vulnerabilities in the AI-powered Comet browser, reminding us that cutting-edge technology often comes with growing pains. As with any powerful tool, users should stay informed about security updates and best practices.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;After experiencing Comet Browser firsthand, I can confidently say we're looking at a genuine inflection point in web technology. This isn't just a better browser, it's a fundamentally different way of interacting with the internet.&lt;/p&gt;

&lt;p&gt;Comet is Perplexity's new AI-powered Chromium browser that automates tasks from booking reservations to managing emails, making browsing productive. But even that description undersells what you actually experience when using it.&lt;/p&gt;

&lt;p&gt;The web has always been about information and interaction. Comet makes both effortless. When you can simply speak or type what you want to accomplish and watch it happen automatically, you realize how much friction we've been accepting in our daily digital lives.&lt;/p&gt;

&lt;p&gt;We're still in the early days of agentic browsing, but the foundation is solid and the potential is staggering. If you get a chance to try Comet Browser, take it. You'll never look at web browsing the same way again.&lt;/p&gt;

&lt;p&gt;The future of the internet isn't just about faster connections or better websites, it's about browsers smart enough to be true partners in getting things done. And that future is available to try right now.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>automation</category>
    </item>
    <item>
      <title>From 1-Hour Nightmares to 7-Minute Dreams: Our Cypress Cloud Journey</title>
      <dc:creator>S Chathuranga Jayasinghe</dc:creator>
      <pubDate>Wed, 23 Jul 2025 06:00:46 +0000</pubDate>
      <link>https://dev.to/cypress/from-1-hour-nightmares-to-7-minute-dreams-our-cypress-cloud-journey-23h7</link>
      <guid>https://dev.to/cypress/from-1-hour-nightmares-to-7-minute-dreams-our-cypress-cloud-journey-23h7</guid>
      <description>&lt;p&gt;&lt;em&gt;How we transformed our testing workflow at TrackMan and why Cypress Cloud became our testing superhero&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Picture this: It’s 3 PM on a Friday, you’ve just pushed what you think is a small fix, and now you’re staring at your terminal watching Cypress tests crawl by at a snail’s pace. One test… two tests… still going… Your weekend plans are slowly evaporating as you realize you’ve got another 45 minutes to wait before you know if your code actually works.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Note: Don’t deploy on Fridays!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sound familiar? That was our reality at TrackMan not too long ago.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;The Dark Ages: When Testing Felt Like Punishment&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Let me paint you a picture of how things used to be. Our Cypress test suite had grown into this massive beast that took nearly an hour to complete. An entire hour! We were running everything sequentially, watching tests execute one by one like we were back in the dial-up internet era.&lt;/p&gt;

&lt;p&gt;The worst part? When something broke (and trust me, things broke), debugging was an absolute nightmare. You’d get a cryptic failure message, maybe a screenshot if you were lucky, and then you’d have to play detective, trying to figure out what went wrong. It was like trying to solve a murder mystery with half the clues missing.&lt;/p&gt;

&lt;p&gt;We were using Cypress Custom Commands, which seemed like a good idea at the time, but as our test suite grew, maintaining and understanding the flow became increasingly difficult. The whole experience was just… painful.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;The Lightbulb Moment: Enter Cypress Cloud&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;After months of frustration and countless hours lost to slow test runs, we finally decided enough was enough. We’d heard whispers about Cypress Cloud, but like many teams, we were hesitant to make the switch. “Another tool to learn? Another service to manage?” But honestly, we were desperate.&lt;/p&gt;

&lt;p&gt;Best decision we ever made!&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;The Transformation: From Hours to Minutes&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Here’s where things get exciting. Remember that nearly 1-hour test suite I mentioned? After moving to Cypress Cloud and implementing parallelization, it now runs in just 7 minutes.&lt;/p&gt;

&lt;p&gt;Let me repeat that: &lt;strong&gt;7 minutes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I’m not exaggerating. We went from grabbing coffee, checking emails, and sometimes even taking walks during test runs to barely having time to refill our water bottles.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;What Made the Magic Happen&lt;/strong&gt;
&lt;/h1&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Smart Test Orchestration: The Brain Behind the Operation&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Cypress Cloud’s Smart Test Orchestration is like having a really smart project manager for your tests. Instead of running tests in some random order, it analyzes your test history and intelligently distributes them across multiple machines. Tests that typically take longer get started first, while quicker tests fill in the gaps. It’s beautiful to watch in action.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Parallelization: Divide and Conquer&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;We’re currently running with parallelization enabled, and it’s a game-changer. Instead of one machine plodding through all our tests, we have multiple machines working simultaneously. Think of it like having multiple checkout lanes at a grocery store instead of making everyone wait in one long line.&lt;/p&gt;

&lt;p&gt;With around 44,000 test results per month (yes, we test a lot!), this parallel execution saves us countless hours every single month.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Page Object Model: A Much-Needed Architectural Shift&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;While we were at it, we also moved from Cypress Custom Commands to the Page Object Model (POM). This wasn’t directly related to Cypress Cloud, but it complemented our new setup perfectly.&lt;/p&gt;

&lt;p&gt;Custom Commands felt scattered and hard to maintain as our test suite grew. With POM, everything is organized, reusable, and much easier to understand. Each page has its own class with methods that represent the actions you can perform on that page. It’s clean, it’s logical, and it makes onboarding new team members so much smoother.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Test Replay: The Debugging Superhero We Never Knew We Needed&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Now, let’s talk about the feature that has truly saved our sanity: Test Replay.&lt;/p&gt;

&lt;p&gt;Remember those debugging nightmares I mentioned earlier? They’re basically extinct now. When a test fails, Test Replay captures everything. And I mean everything. Every click, every hover, every network request, every DOM change it’s all there, recorded and ready to be analyzed.&lt;/p&gt;

&lt;p&gt;You can literally watch your test execution step by step, like you’re sitting right next to the browser as it runs. You can see exactly where things went wrong, what the page looked like at that moment, and what data was flowing through your application.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Test Analytics: Your Personal Test Performance Detective&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;But Test Replay is just the tip of the iceberg. Cypress Cloud’s Test Analytics dashboard has become our command-center for test optimization. Here’s where things get really interesting.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Slowest Tests&lt;/strong&gt; view is pure gold. It shows you exactly which tests are dragging down your entire suite. We discovered that three specific tests were accounting for nearly 40% of our total execution time! Once we optimized those, we shaved off another couple of minutes from our already improved 7-minute runtime.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Failure Reasons&lt;/strong&gt; analytics helped us identify patterns we never would have caught manually. Turns out, we had several tests failing due to timing issues that only happened when running in parallel. The dashboard made it crystal clear which tests were problematic and why.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Flaky Test Detection: No More “It Works on My Machine”&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Here’s something that used to drive us absolutely crazy: flaky tests. You know, those tests that pass 90% of the time and then randomly fail, usually right before an important release.&lt;/p&gt;

&lt;p&gt;Cypress Cloud’s &lt;strong&gt;Flaky Test Detection&lt;/strong&gt; is like having a data scientist dedicated to analyzing your test stability. It tracks your test results over time and flags tests that show inconsistent behavior. We can now see exactly how flaky a test is (like “passes 85% of the time”) and prioritize which ones to fix first.&lt;/p&gt;

&lt;p&gt;The best part? It gives you insights into what might be causing the flakiness. Network timeouts? Element loading issues? Race conditions? The data helps you pinpoint the root cause instead of just crossing your fingers and hoping for the best.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Cypress Cloud AI: The Future is Here&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;And then there’s the newest addition that honestly feels like magic: &lt;strong&gt;Cypress Cloud AI&lt;/strong&gt;. When a test fails, it doesn’t just show you what happened it analyzes the failure and suggests what might have gone wrong.&lt;/p&gt;

&lt;p&gt;I’m talking about actual AI-powered suggestions like “This test appears to be failing due to a network timeout. Consider increasing the timeout or mocking this network request.” It’s like having a senior QA engineer looking over your shoulder, except this one never gets tired or takes coffee breaks.&lt;/p&gt;

&lt;p&gt;The level of detail you get across all these features is incredible. Network timings, console logs, screenshots at every step it’s like having X-ray vision for your tests, but with a smart assistant helping you interpret what you’re seeing.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;The Real-World Impact: Beyond Just Speed&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Sure, going from 1 hour to 7 minutes is impressive, but the real benefits go much deeper:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Developer Happiness&lt;/strong&gt;: Our team actually looks forward to running tests now. No more dreading that pre-deployment test run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Faster Feedback Loops&lt;/strong&gt;: We can iterate much quicker. Push a fix, wait 7 minutes, know if it works. The faster feedback makes us more confident and more productive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Better Test Coverage&lt;/strong&gt;: When tests run quickly, you’re more likely to write more of them. We’ve actually expanded our test coverage because the pain of long execution times is gone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Easier Onboarding&lt;/strong&gt;: New team members can understand our test failures quickly thanks to Test Replay. No more spending hours explaining “what probably went wrong.”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Predictable Releases&lt;/strong&gt;: With reliable, fast tests and amazing debugging tools, our releases became much more predictable and less stressful.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;The Numbers Don’t Lie&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Let me put this in perspective with some real numbers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Test execution time&lt;/strong&gt;: From ~60 minutes to 7 minutes (88% reduction)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Monthly test results&lt;/strong&gt;: 44,000+ tests handled smoothly&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Debugging time&lt;/strong&gt;: Reduced by approximately 70% thanks to Test Replay&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Developer productivity&lt;/strong&gt;: Immeasurably better (seriously, the mood in our standups improved!)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;What We Learned Along the Way&lt;/strong&gt;
&lt;/h1&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;It’s Not Just About the Tools&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;While Cypress Cloud provided the technical foundation for our improvement, we learned that success also required some process changes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Embrace parallelization thinking&lt;/strong&gt;: We had to restructure some tests that were inadvertently dependent on each other.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Invest in good test data management&lt;/strong&gt;: With parallel execution, you need to be more careful about test data isolation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Monitor and optimize continuously&lt;/strong&gt;: The analytics provided by Cypress Cloud help us continuously improve our test suite.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The ROI is Real&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Yes, Cypress Cloud is a paid service, but the time savings alone justify the cost. When you factor in developer productivity, faster release cycles, and reduced debugging time, it’s honestly a no-brainer.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Looking Forward&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Moving to Cypress Cloud wasn’t just a technical upgrade — it was a complete transformation of how we approach testing. We went from viewing tests as a necessary evil to treating them as a competitive advantage.&lt;/p&gt;

&lt;p&gt;If you’re sitting there reading this while waiting for your own slow test suite to finish, or if you’re tired of spending hours debugging mysterious test failures, I can’t recommend Cypress Cloud enough. The combination of Smart Test Orchestration, parallelization, and Test Replay creates a testing experience that’s not just faster, but genuinely enjoyable.&lt;/p&gt;

&lt;p&gt;Trust me, your experience will thank me!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Have you made the switch to Cypress Cloud, or are you still on the fence? I’d love to hear about your testing experiences in the comments below. And if you decide to give Cypress Cloud a try, let me know how it goes, I’m always excited to hear about teams escaping the slow-test nightmare!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cypresscloud</category>
      <category>testautomation</category>
      <category>qualityengineering</category>
      <category>cypress</category>
    </item>
    <item>
      <title>Stop Writing Messy Cypress Tests: This Boilerplate Plugin Saves Your Sanity</title>
      <dc:creator>S Chathuranga Jayasinghe</dc:creator>
      <pubDate>Sat, 07 Jun 2025 17:29:33 +0000</pubDate>
      <link>https://dev.to/s_chathuranga_j/stop-writing-messy-cypress-tests-this-boilerplate-plugin-saves-your-sanity-4dna</link>
      <guid>https://dev.to/s_chathuranga_j/stop-writing-messy-cypress-tests-this-boilerplate-plugin-saves-your-sanity-4dna</guid>
      <description>&lt;p&gt;You know that feeling when you open a Cypress test file and see selectors scattered everywhere like confetti? Yeah, me too. It's painful.&lt;/p&gt;

&lt;p&gt;After dealing with this mess for way too long, I created &lt;a href="https://www.npmjs.com/package/cypress-bootstrap" rel="noopener noreferrer"&gt;cypress-bootstrap&lt;/a&gt; to bring some order to the chaos. And honestly? It will change everything about how you write tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem We All Face
&lt;/h2&gt;

&lt;p&gt;Let's be real here. Most Cypress tests look like this disaster if you write as raw 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="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;Some test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;does something&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;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-test="username"]&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;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;[data-test="password"]&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;pass&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-test="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;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;.inventory_item&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="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;#react-burger-menu-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;#logout_sidebar_link&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;What happens when that username selector changes? You hunt through 50 test files to update it. Fun times, right?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Role Model: cypress-bootstrap
&lt;/h2&gt;

&lt;p&gt;This plugin brings the Page Object Model (POM) to Cypress in the cleanest way possible. No complicated setup, no fancy configurations. Just clean, organized code that actually makes sense.&lt;/p&gt;

&lt;p&gt;Here's the same test with cypress-bootstrap way:&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;LoginPage&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;../../pages/LoginPage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;InventoryPage&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../../pages/InventoryPage&lt;/span&gt;&lt;span class="dl"&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Inventory Page Test Suite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should display products in the inventory 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="nx"&gt;InventoryPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;checkPageURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;InventoryPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;InventoryPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&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;InventoryPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inventoryItems&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;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should add specific item 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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;InventoryPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inventoryItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sauce Labs Backpack&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;InventoryPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addToCartButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sauce Labs Backpack&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;InventoryPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shoppingCartButton&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;contain&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should display hamburger menu functionality&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;InventoryPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hamburgerMenuButton&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="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nx"&gt;InventoryPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sideMenu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;overlay&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;InventoryPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sideMenu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;aboutButton&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;InventoryPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sideMenu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logOutButton&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;InventoryPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sideMenu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;closeButton&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="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nx"&gt;InventoryPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sideMenu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;overlay&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="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;See the difference? You can actually read what the test is doing without deciphering cryptic selectors.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Secret Sauce: Smart Locator Design
&lt;/h2&gt;

&lt;p&gt;Here's where it gets really interesting. Let's look at how the page object is structured:&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;BasePage&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;../testbase/BasePage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InventoryPage&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BasePage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/inventory.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;title&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="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-test="title"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;hamburgerMenuButton&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="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;#react-burger-menu-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;filterButton&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="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;.select_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;filterSelector&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="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-test="product-sort-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;activeFilterOption&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="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-test="active-option"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;shoppingCartButton&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="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-test="shopping-cart-link"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;inventoryItems&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="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-test="inventory-item"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;inventoryItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&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;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-test="inventory-item-name"]&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="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;inventoryItemNameLabel&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="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-test="inventory-item-name"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;inventoryItemDescription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;itemName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&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;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-test="inventory-item-name"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;itemName&lt;/span&gt;&lt;span class="p"&gt;)&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;div[data-test="inventory-item-desc"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;inventoryItemPrice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;itemName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&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;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-test="inventory-item-name"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;itemName&lt;/span&gt;&lt;span class="p"&gt;)&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;div[data-test="inventory-item-price"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;addToCartButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;itemName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&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;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-test="inventory-item-name"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;itemName&lt;/span&gt;&lt;span class="p"&gt;)&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;div[data-test*="add-to-cart-sauce-labs-"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;sideMenu&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;overlay&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="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;.bm-menu&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;closeButton&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="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;#react-burger-cross-btn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;allItemsButton&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="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-test="inventory-sidebar-link"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;aboutButton&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="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-test="about-sidebar-link"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;logOutButton&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="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-test="logout-sidebar-link"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;resetAppStateButton&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="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-test="reset-sidebar-link"]&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;InventoryPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice something special here? Every locator is defined as an arrow function. This isn't just a style choice — it's actually crucial for how Cypress works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Arrow Functions Are Your Best Friend
&lt;/h2&gt;

&lt;p&gt;Here's the thing about Cypress: it's all about that command queue. When you define locators as arrow functions like &lt;code&gt;title = () =&amp;gt; cy.get('[data-test="title"]')&lt;/code&gt;, you're not executing the command immediately. You're creating a function that returns a Cypress command when called.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lazy evaluation&lt;/strong&gt;: The element lookup happens when you call the function, not when you define it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fresh queries&lt;/strong&gt;: Each time you call &lt;code&gt;InventoryPage.title()&lt;/code&gt;, you get a fresh query to the DOM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No stale references&lt;/strong&gt;: You never have to worry about elements that were found earlier but changed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Trust me, I learned this the hard way. My first attempt at this plugin used direct assignments, and it was a nightmare of stale element references.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dynamic Locators: The Game Changer
&lt;/h2&gt;

&lt;p&gt;But here's where things get really cool. Look at this locator:&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;inventoryItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&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;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-test="inventory-item-name"]&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="nx"&gt;name&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 dynamic locator. Instead of hardcoding which inventory item you want or using replace statements in the test script, you can pass the name as a parameter. So in your test, you can do:&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;InventoryPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inventoryItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sauce Labs Backpack&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;InventoryPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inventoryItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sauce Labs Bike Light&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;Same locator function, different items. No more copy-pasting selectors for every single product in your inventory.&lt;/p&gt;

&lt;p&gt;And it gets even better with complex dynamic locators:&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;inventoryItemPrice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;itemName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&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;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-test="inventory-item-name"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;itemName&lt;/span&gt;&lt;span class="p"&gt;)&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;div[data-test="inventory-item-price"]&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 locator finds a specific item by name, then navigates to its price element. One function, endless possibilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Singleton Pattern Magic
&lt;/h2&gt;

&lt;p&gt;Here's where the architecture really shines. The plugin uses the Singleton design pattern, but not in the way you might expect.&lt;/p&gt;

&lt;p&gt;Instead of the traditional constructor approach, your page objects extend a base class and get exported as pre-instantiated singletons:&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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;InventoryPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// This is the magic line&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last line? That's your singleton right there.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Singleton is Perfect Here
&lt;/h3&gt;

&lt;p&gt;The Singleton pattern solves several problems that are unique to Cypress testing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Memory efficiency&lt;/strong&gt;: You get exactly one instance of each page object across your entire test suite. No matter how many times you import InventoryPage, you're always working with the same instance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Consistent state&lt;/strong&gt;: Since it's the same instance everywhere, you don't have weird issues with different instances having different behaviors.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Clean imports&lt;/strong&gt;: You just import and use. No &lt;code&gt;new InventoryPage()&lt;/code&gt; scattered throughout your tests.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Shared functionality&lt;/strong&gt;: All your page objects can extend BasePage and inherit common methods while maintaining their singleton nature.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Organizing Complex UI Components
&lt;/h2&gt;

&lt;p&gt;The framework really shines when dealing with complex UI components. You can organize them as nested objects:&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;sideMenu&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;overlay&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="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;.bm-menu&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;closeButton&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="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;#react-burger-cross-btn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;allItemsButton&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="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-test="inventory-sidebar-link"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;aboutButton&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="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-test="about-sidebar-link"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;logOutButton&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="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-test="logout-sidebar-link"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;resetAppStateButton&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="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-test="reset-sidebar-link"]&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;Now your tests can access complex UI elements in a logical way: &lt;code&gt;InventoryPage.sideMenu.logOutButton()&lt;/code&gt; or &lt;code&gt;InventoryPage.sideMenu.closeButton()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This organization makes your tests incredibly readable. Anyone on your team can look at the test and immediately understand what's happening.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Readability Game-Changer
&lt;/h2&gt;

&lt;p&gt;Let's talk about readability because this is where the framework really pays off. Your tests become self-documenting. Look at this 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="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should add specific item 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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;InventoryPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inventoryItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sauce Labs Backpack&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;InventoryPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addToCartButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sauce Labs Backpack&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;InventoryPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shoppingCartButton&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;contain&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You don't need comments. The code tells the story: find the backpack, click its add to cart button, verify the cart shows 1 item.&lt;/p&gt;

&lt;p&gt;Compare that to selector soup, and there's no contest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Benefits You'll Actually Notice
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Maintenance becomes painless&lt;/strong&gt;: When a selector changes, you update it in one place. Done.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dynamic testing gets easy&lt;/strong&gt;: Want to test 20 different products? Use the same dynamic locator with different parameters.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;New team members onboard faster&lt;/strong&gt;: They can read and understand your tests without playing "guess the selector."&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tests become reusable&lt;/strong&gt;: Common actions and element interactions get written once and used everywhere.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Debugging gets easier&lt;/strong&gt;: When a test fails, you know exactly which page object method to check.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Code reviews become productive&lt;/strong&gt;: Reviewers focus on test logic instead of deciphering what selectors do.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started Takes 2 Minutes
&lt;/h2&gt;

&lt;p&gt;First, initiate a new Node.js project if you haven't already:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then install the package:&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;cypress-bootstrap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the magic part — run the setup script and let it do all the heavy lifting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Option 1: Run the setup script directly&lt;/span&gt;
npx cypress-bootstrap-setup

&lt;span class="c"&gt;# Option 2: Use the npm script&lt;/span&gt;
npm run setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! The setup script automatically creates your entire folder structure, copies all configuration files, and installs dependencies. You'll get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;├── cypress/
│   ├── pages/           # Your page objects go here
│   ├── tests/ui/        # Your test files
│   ├── testbase/        # Base classes
│   ├── testdata/        # Test data JSON files
│   ├── support/         # Cypress support files
│   └── reports/         # Test reports
├── cypress.config.ts    # Main Cypress config
├── .prettierrc          # Code formatting rules
└── package.json         # Updated with all the scripts you need
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The framework even sets up Prettier and Husky for automatic code formatting on commits. No more arguing about semicolons with your team!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: This will include some execution-ready template test cases when you install and setup. Give it a run, study them and follow the style ❤&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;I was tired of messy, unmaintainable tests. I came up with this framework and I saw many people writing messy Cypress tests. So I though I should share the way I work with Cypress ❤&lt;/p&gt;

&lt;p&gt;The Singleton pattern combined with smart locator design and the Page Object Model creates a testing framework that's actually enjoyable to work with. Your tests become readable, maintainable, and reliable. Dynamic locators mean you can test complex scenarios without writing repetitive code.&lt;/p&gt;

&lt;p&gt;Your team can onboard faster and contribute more effectively. And you spend less time hunting down broken selectors and more time building features.&lt;/p&gt;

&lt;p&gt;Try it in your next project. I think you'll be surprised at how much cleaner your test suite becomes. And trust me, your future self will thank you when you need to test that entire product catalog without writing 50 different locators.&lt;/p&gt;

&lt;p&gt;Highly appreciate your thoughts about this in the comment section below..&lt;/p&gt;

</description>
      <category>cypress</category>
      <category>testautomation</category>
      <category>bestpractices</category>
      <category>pageobjectmodel</category>
    </item>
    <item>
      <title>Stop Your Users From Cringing: How to Add Grammar and Spell-Check to Your Cypress Tests</title>
      <dc:creator>S Chathuranga Jayasinghe</dc:creator>
      <pubDate>Sat, 31 May 2025 16:09:48 +0000</pubDate>
      <link>https://dev.to/s_chathuranga_j/stop-your-users-from-cringing-how-to-add-grammar-and-spell-check-to-your-cypress-tests-5815</link>
      <guid>https://dev.to/s_chathuranga_j/stop-your-users-from-cringing-how-to-add-grammar-and-spell-check-to-your-cypress-tests-5815</guid>
      <description>&lt;p&gt;You know that feeling when you're browsing a website and suddenly hit a glaring typo? It's like nails on a chalkboard, right? Your users feel the same way. But here's the thing – as developers, we're so focused on making sure our buttons click and our forms submit that we sometimes forget about the words themselves.&lt;/p&gt;

&lt;p&gt;I learned this the hard way when a client called me up, slightly panicked, because their brand new marketing page had "recieve" instead of "receive" plastered across the hero section. It had been live for three days. &lt;em&gt;Three whole days&lt;/em&gt; of potential customers seeing that typo.&lt;/p&gt;

&lt;p&gt;That's when I discovered the beautiful marriage of Cypress and LanguageTool API – a combination that'll save you from those cringe-worthy moments and keep your content looking professional.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why LanguageTool + Cypress is a Match Made in Heaven
&lt;/h2&gt;

&lt;p&gt;LanguageTool isn't just another spell checker. It's like having that really smart friend who catches your grammar mistakes AND explains why they're wrong. It supports over 20 languages, catches context-sensitive errors, and has a generous free tier that's perfect for testing.&lt;/p&gt;

&lt;p&gt;Cypress, on the other hand, is already crawling through your pages, clicking buttons, and checking functionality. Why not have it proofread while it's there?&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up Your Grammar Police
&lt;/h2&gt;

&lt;p&gt;First things first – let's talk about the LanguageTool API. The good news? There's a free public API at &lt;a href="https://api.languagetool.org/v2/check" rel="noopener noreferrer"&gt;&lt;code&gt;https://api.languagetool.org/v2/check&lt;/code&gt;&lt;/a&gt; that doesn't require an API key for basic usage. The limits are generous for testing: 20 requests per IP per minute and up to 20KB of text per request.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing the Dependencies
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;bashnpm&lt;/span&gt; &lt;span class="nx"&gt;install&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;save&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! We'll use axios to make our API calls, but you could just as easily use fetch or your HTTP client of choice.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating Your Custom Cypress Command
&lt;/h3&gt;

&lt;p&gt;Here's where the magic happens. We're going to create a custom Cypress command that grabs text from your page and sends it to LanguageTool for analysis.&lt;/p&gt;

&lt;p&gt;Create or update your &lt;code&gt;cypress/support/commands.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;javascriptimport&lt;/span&gt; &lt;span class="nx"&gt;axios&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;axios&lt;/span&gt;&lt;span class="dl"&gt;'&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;checkGrammar&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;selector&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="nx"&gt;language&lt;/span&gt; &lt;span class="o"&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="nx"&gt;skipWords&lt;/span&gt; &lt;span class="o"&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;=&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;selector&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;$element&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;// Extract text from the element&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="nx"&gt;$element&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="nf"&gt;trim&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="o"&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="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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;No text found in element&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Make API call to LanguageTool&lt;/span&gt;
    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="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;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.languagetool.org/v2/check&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;form&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;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;text&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="na"&gt;language&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;language&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matches&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;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;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;span class="c1"&gt;// Filter out words we want to skip (like brand names)&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filteredErrors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;errors&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="nx"&gt;error&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;errorText&lt;/span&gt; &lt;span class="o"&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;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;error&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="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;skipWords&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;word&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 
            &lt;span class="nx"&gt;errorText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filteredErrors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;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;span class="c1"&gt;// Create a readable error message&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errorMessages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;filteredErrors&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;error&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;errorText&lt;/span&gt; &lt;span class="o"&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;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;error&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="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`"&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;errorText&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;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&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="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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Grammar/Spelling errors found:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="nx"&gt;errorMessages&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;msg&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;msg&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

          &lt;span class="c1"&gt;// Fail the test if errors are found&lt;/span&gt;
          &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Grammar/Spelling errors found:\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;errorMessages&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="se"&gt;\n&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;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;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Text checked: "&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;substring&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;50&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;..." - No errors found`&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;h3&gt;
  
  
  No More API Key Management!
&lt;/h3&gt;

&lt;p&gt;Good news – you don't need to sign up for anything or manage API keys for basic usage. The public LanguageTool API is free and ready to use right out of the box.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing Your First Grammar Test
&lt;/h2&gt;

&lt;p&gt;Now for the fun part – let's write a test that actually uses this new superpower:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;javascriptdescribe&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 Quality Tests&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should have error-free content on the homepage&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;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Check the main heading&lt;/span&gt;
    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;checkGrammar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;h1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Check the hero description&lt;/span&gt;
    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;checkGrammar&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-testid="hero-description"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Check all paragraph text, but skip our brand name "TechnoWiz"&lt;/span&gt;
    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;checkGrammar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;main p&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;skipWords&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;TechnoWiz&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&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;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should validate blog post content&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;/blog/latest-post&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Check the title and content&lt;/span&gt;
    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;checkGrammar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;article h1&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;checkGrammar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;article .content&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;skipWords&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;JavaScript&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;React&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;npm&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;h2&gt;
  
  
  Getting Fancy: Batch Checking Multiple Elements
&lt;/h2&gt;

&lt;p&gt;Here's a neat trick I discovered after running this on a content-heavy site. Instead of making individual API calls for each element, you can batch them together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;javascriptCypress&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;checkMultipleElements&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;selectors&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;texts&lt;/span&gt; &lt;span class="o"&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;elementMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Collect all text first&lt;/span&gt;
  &lt;span class="nx"&gt;selectors&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;selector&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="nx"&gt;selector&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;$element&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;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$element&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="nf"&gt;trim&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;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;elementMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;texts&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="nx"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;texts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&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="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Then check all at once&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;texts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&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;return&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;combinedText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;texts&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="se"&gt;\n\n&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;request&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;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.languagetool.org/v2/check&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;form&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;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;combinedText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;language&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;language&lt;/span&gt; &lt;span class="o"&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="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matches&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;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;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;span class="c1"&gt;// Map errors back to their original elements&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errorMessages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;errors&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;error&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;errorText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;combinedText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;error&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="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`"&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;errorText&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;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&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="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Content errors found:\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;errorMessages&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="se"&gt;\n&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;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;h2&gt;
  
  
  Real-World Tips and Gotchas
&lt;/h2&gt;

&lt;p&gt;After using this setup on several projects, here are some lessons I've learned:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Watch Your API Limits&lt;/strong&gt;: The free public API allows 20 requests per IP per minute and up to 20KB of text per request. That's actually quite generous for testing, but keep an eye on your usage if you're running lots of tests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Handle Dynamic Content&lt;/strong&gt;: If your content changes frequently (like user-generated content), you might want to be more selective about what you check. Focus on static content like headers, navigation, and marketing copy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Brand Names and Technical Terms&lt;/strong&gt;: Always use the &lt;code&gt;skipWords&lt;/code&gt; option for your brand names, technical jargon, and industry-specific terms that LanguageTool might flag incorrectly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Different Languages&lt;/strong&gt;: If you're building a multilingual site, you can easily switch the language parameter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;javascriptcy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;checkGrammar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.spanish-content&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;language&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;checkGrammar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.french-content&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;language&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Making It Part of Your CI/CD Pipeline
&lt;/h2&gt;

&lt;p&gt;Here's where this really shines – imagine catching content errors before they go live. Add this to your CI pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;javascript&lt;/span&gt;&lt;span class="c1"&gt;// In your cypress/e2e/content-quality.cy.js&lt;/span&gt;
&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Pre-deployment Content Check&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;criticalPages&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;/&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;/pricing&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;/about&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;/contact&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="nx"&gt;criticalPages&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;page&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`should have quality content on &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;`&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="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// Check all headings and main content&lt;/span&gt;
      &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;checkGrammar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;h1, h2, h3&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;checkGrammar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;main p, .content p&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;checkGrammar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[class*="hero"], [class*="cta"]&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;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;Look, perfect grammar won't make your app faster or more secure, but it will make your users trust you more. And trust, my friend, is everything in business.&lt;/p&gt;

&lt;p&gt;This Cypress + LanguageTool combo has saved me from more embarrassing typos than I care to admit. It's like having a really pedantic proofreader who never gets tired and works for free (well, mostly free).&lt;/p&gt;

&lt;p&gt;The best part? Once you've set it up, it just runs. Every time you deploy, every time you test, your content gets a quality check. Your marketing team will love you, your users will respect you, and you'll sleep better knowing that "recieve" will never make it to production again.&lt;/p&gt;

&lt;p&gt;So go ahead, give your content the same attention you give your code. Your future self (and your users) will thank you for it.&lt;/p&gt;

</description>
      <category>cypress</category>
      <category>grammar</category>
      <category>spellcheck</category>
      <category>testautomation</category>
    </item>
    <item>
      <title>Let’s Sync Cypress Tests with Azure Like a Pro (No More Manual Linking!)</title>
      <dc:creator>S Chathuranga Jayasinghe</dc:creator>
      <pubDate>Mon, 19 May 2025 18:26:03 +0000</pubDate>
      <link>https://dev.to/s_chathuranga_j/lets-sync-cypress-tests-with-azure-like-a-pro-no-more-manual-linking-k94</link>
      <guid>https://dev.to/s_chathuranga_j/lets-sync-cypress-tests-with-azure-like-a-pro-no-more-manual-linking-k94</guid>
      <description>&lt;p&gt;Cypress tests + Azure Test Plans + a ton of manual clicks &amp;amp; types? Nah. Let’s automate that boring part..&lt;/p&gt;

&lt;p&gt;You know this drill:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;You write some beautiful Cypress tests, your coverage is solid, you push everything to your repo and then…&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You have to manually go to Azure Test Plans, update test cases.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Mark them as &lt;strong&gt;Automated.&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Then again come back to your Cypress script and update the each test title with the test case Id.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ugh! I’ve been there. Too many times! So I decided to fix it!&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Introducing my new Cypress plugin:&lt;/em&gt; &lt;a href="https://www.npmjs.com/package/cypress-azure-sync" rel="noopener noreferrer"&gt;&lt;strong&gt;&lt;em&gt;cypress-azure-sync&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This tiny yet mighty plugin lets you &lt;strong&gt;sync your Cypress test cases with Azure Test Plans&lt;/strong&gt; like a breeze, no more tedious updates, no more forgetting to change the test status, and definitely no more grumbling at the screen.&lt;/p&gt;

&lt;p&gt;Let me walk you through what it does, how it works, and how you can get started in just a few minutes!&lt;/p&gt;




&lt;h1&gt;
  
  
  &lt;strong&gt;What does&lt;/strong&gt; &lt;code&gt;cypress-azure-sync&lt;/code&gt; do?
&lt;/h1&gt;

&lt;p&gt;Simply put, it reads your Cypress spec files, extracts the test case IDs based on a pattern you define (like &lt;code&gt;TC1234&lt;/code&gt;or &lt;code&gt;[1234]&lt;/code&gt;), and automatically updates your Azure Test Plan test cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Marks them as &lt;strong&gt;Automated&lt;/strong&gt; if they’re not.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If the Cypress &lt;code&gt;test case title&lt;/code&gt; does not have a test case id, it &lt;strong&gt;creates a new test case&lt;/strong&gt; in Azure Test Plans:&lt;br&gt;&lt;br&gt;
- In the &lt;strong&gt;defined Area Path&lt;/strong&gt;.&lt;strong&gt;-&lt;/strong&gt; With the &lt;strong&gt;test case title.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
- Adding the &lt;strong&gt;tags&lt;/strong&gt; mentioned in the test case too.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If a new test case created, retrieves the test case id and &lt;strong&gt;updates the Cypress test case title&lt;/strong&gt; with the id in the spec file.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Magic? Nah. Just good automation ❤&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;⚠️ Note: This plugin currently does&lt;/em&gt; &lt;strong&gt;&lt;em&gt;not publish test results&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;to Azure Test Plans. That feature is on the roadmap!&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  &lt;strong&gt;How does it identify and map tests?&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;The plugin uses a simple naming convention. In your Cypress test case title, include the Azure test case ID in the following format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;1234: should successfully login with valid credentials&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tagOne&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;tagTwo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// test steps&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  &lt;strong&gt;Quick Setup — How to use it in your project&lt;/strong&gt;
&lt;/h1&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Install the Plugin&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Install the plugin using a simple command: &lt;code&gt;npm install cypress-azure-sync --save-dev&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Configure the Plugin&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;As usual in Cypress, you need to configure the plugin in the &lt;code&gt;cypress.config.ts&lt;/code&gt; file as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;defineConfig&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;cypress&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;initAzureSync&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;cypress-azure-sync&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;e2e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setupNodeEvents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;initAzureSync&lt;/span&gt;&lt;span class="p"&gt;()(&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Define the Azure Configurations&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;You can define the Azure Configurations in different ways. For local executions, you can add a &lt;code&gt;.env&lt;/code&gt; file to define the configurations as below and add it to &lt;code&gt;.gitignore&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;Azure&lt;/span&gt; &lt;span class="nx"&gt;DevOps&lt;/span&gt; &lt;span class="nx"&gt;Configuration&lt;/span&gt;
&lt;span class="nx"&gt;AZURE_DEVOPS_ORG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;YOUR_ORG_NAME&lt;/span&gt;
&lt;span class="nx"&gt;AZURE_DEVOPS_PROJECT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;YOUR_PROJECT_NAME&lt;/span&gt;
&lt;span class="nx"&gt;AZURE_DEVOPS_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;YOUR_PERSONAL_ACCESS_TOKEN&lt;/span&gt;
&lt;span class="nx"&gt;AZURE_DEVOPS_API_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;7.0&lt;/span&gt;
&lt;span class="nx"&gt;AZURE_AUTOMATION_STATE_FIELD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;Custom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AutomationState&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;Test&lt;/span&gt; &lt;span class="nx"&gt;Plan&lt;/span&gt; &lt;span class="nx"&gt;Configuration&lt;/span&gt;
&lt;span class="nx"&gt;AZURE_TEST_PLAN_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;YOUR_TEST_PLAN_ID&lt;/span&gt;
&lt;span class="nx"&gt;AZURE_TEST_SUITE_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;YOUR_TEST_SUITE_ID&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;Cypress&lt;/span&gt; &lt;span class="nx"&gt;Configuration&lt;/span&gt;
&lt;span class="nx"&gt;CYPRESS_SPEC_PATTERN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;cypress&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;e2e&lt;/span&gt;&lt;span class="cm"&gt;/**/&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Defining the Test Plan Configuration is optional here, you will know why below. The other ways of configuring this can be found in the plugin page &lt;a href="https://www.npmjs.com/package/cypress-azure-sync" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Set the MetaData on Spec Files&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;If we being rational, in most cases the tests in one Spec file should go to one &lt;strong&gt;Area Path&lt;/strong&gt; in Azure Test Plans and another set of tests in another Spec should go some other Area Path. So the plugin uses metadata to map this accordingly.&lt;/p&gt;

&lt;p&gt;On each of your Spec file, define the &lt;strong&gt;Test Plan ID, Test Suite ID&lt;/strong&gt; and &lt;strong&gt;Area Path&lt;/strong&gt; as a &lt;strong&gt;JSDoc comment&lt;/strong&gt; (or as a regular comment, i prefer this).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * TestPlanId = 170880
 * TestSuiteId = 170921
 * AreaPath = 'Project XYZ\Application XYZ\Cache Service'
 */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All done! Now it’s time to see the magic happen (sorry not the magic! see the automation in action!)..&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Execute the Sync Process&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;There are two ways execute the Sync. You can decide what you prefer!&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use a Hook:
You can add this simple command in a &lt;code&gt;before()&lt;/code&gt; or &lt;code&gt;after()&lt;/code&gt; hook (I prefer in a &lt;code&gt;before()&lt;/code&gt; hook defined in the &lt;code&gt;commands.ts&lt;/code&gt; file 😉):
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;after&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;task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;syncWithAzure&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;2. Execute a NPX Command (CLI):&lt;br&gt;&lt;br&gt;
You can also run &lt;code&gt;npx cypress-azure-sync&lt;/code&gt; in your terminal/command-prompt to start the Sync.&lt;/p&gt;




&lt;h1&gt;
  
  
  &lt;strong&gt;Why I Built This Plugin (a mini behind-the-scenes)&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;So here’s the story…&lt;/p&gt;

&lt;p&gt;I was happily cruising along with Cypress and Azure DevOps pipelines, until I hit a familiar bump — one that always made me sigh:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Why is there no easy way to sync test cases &amp;amp; metadata in Azure?”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then came &lt;strong&gt;&lt;em&gt;the rock&lt;/em&gt;&lt;/strong&gt;. One of our API projects landed with &lt;strong&gt;500+ test cases&lt;/strong&gt;. Yep, you read that right. And guess what? All of them needed to be created in Azure Test Plans. Manually.&lt;/p&gt;

&lt;p&gt;Not just that — after creating them, we had to go back and update every test script to include the corresponding Azure Test Case ID. That whole thing screamed &lt;em&gt;“manual pain”&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;That’s when I knew enough was enough. I rolled up my sleeves and started building a plugin that would take care of the annoying bits: mapping, syncing, updating metadata. Basically, everything that wastes our time and energy.&lt;/p&gt;

&lt;p&gt;And thus, &lt;code&gt;cypress-azure-sync&lt;/code&gt; was born. Built from frustration, refined by passion. I hope it saves you the hours it’s already saving me! Now it’s open-source, and I hope it helps you too!&lt;/p&gt;




&lt;h1&gt;
  
  
  &lt;strong&gt;What’s Next for the Plugin?&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Here’s what I have in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Publishing test results&lt;/strong&gt; directly to Azure Test Runs (&lt;strong&gt;yep, it’s coming! Hooray!!!&lt;/strong&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Support for &lt;strong&gt;multiple test case ID formats&lt;/strong&gt; in a single test title&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Handle &lt;strong&gt;custom fields&lt;/strong&gt; in Azure test cases&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your ideas are welcome too! Just drop a comment below!&lt;/p&gt;

&lt;p&gt;So go ahead, give &lt;a href="https://www.npmjs.com/package/cypress-azure-sync" rel="noopener noreferrer"&gt;&lt;code&gt;cypress-azure-sync&lt;/code&gt;&lt;/a&gt; a try. Let magic do the heavy lifting — while you focus on writing awesome tests.&lt;/p&gt;

&lt;p&gt;Until next time, keep Testing Smart!&lt;/p&gt;

</description>
      <category>cypress</category>
      <category>azure</category>
      <category>azuretestplans</category>
      <category>azuredevops</category>
    </item>
    <item>
      <title>Why Linking your Test Scripts with the Test Management Tool is a Game-Changer!</title>
      <dc:creator>S Chathuranga Jayasinghe</dc:creator>
      <pubDate>Sun, 18 May 2025 10:53:25 +0000</pubDate>
      <link>https://dev.to/s_chathuranga_j/why-linking-your-test-scripts-with-the-test-management-tool-is-a-game-changer-5a2p</link>
      <guid>https://dev.to/s_chathuranga_j/why-linking-your-test-scripts-with-the-test-management-tool-is-a-game-changer-5a2p</guid>
      <description>&lt;p&gt;Let’s talk about something that often gets overlooked in the hustle of automation, &lt;strong&gt;syncing your automated test scripts with your test management system!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You might think, “Eh, my tests run fine. I’ve got my green ticks on the pipeline. Why bother?”&lt;/p&gt;

&lt;p&gt;Well, hold onto that thought, because today I’m going to show you why this link is not just a &lt;em&gt;nice-to-have&lt;/em&gt; but a &lt;em&gt;superpower&lt;/em&gt; for your test automation efforts!&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;What do I mean by Linking?&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;At its core, linking means there’s a direct connection between the test cases you’ve written in your test management tool (like TestRail, Azure Test Plans, XRay, etc.) and the actual automation scripts you’ve coded up in your framework (Cypress, Playwright, Selenium — you name it).&lt;/p&gt;

&lt;p&gt;This sync can be manual (you link test IDs in your scripts) or fully automated. What do I mean by automated:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Your test report magically updates the test case in the management system with results&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Execution history is maintained&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Even the “Test Automation Status” is updated&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Creates the missing test cases in the test management tool which automated in your scripts&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sounds sweet, right?&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Why Is It So Important?&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;You know that moment during a sprint review when someone asks,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Hey, are all the acceptance criteria for this feature automated?”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Or worse:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“Why did this test fail? Was it even tested in the first place?”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Without a proper link between your test management system and your scripts, you’re stuck searching through commit histories, spec files, and pipelines. Not fun!!!&lt;/p&gt;

&lt;p&gt;But with a well-established link?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You instantly know which test case maps to which automated script.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can track test coverage accurately.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You bring full traceability into the QA lifecycle.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Benefits of Having the Link&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Alright, let me hit you with the real juice — the &lt;strong&gt;benefits&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Traceability from Requirement to Automation:&lt;/strong&gt;&lt;br&gt;
You can trace back from a Jira ticket -&amp;gt; to the test case -&amp;gt; to the automated script -&amp;gt; to the pipeline result. This full-circle view is gold when debugging, reporting, or auditing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Real-Time Test Reporting:&lt;/strong&gt;&lt;br&gt;
Imagine your nightly CI/CD run finishes, and boom! your test case statuses are updated automatically in the management tool. PMs and stakeholders can view the latest status &lt;em&gt;without&lt;/em&gt; bugging you on Slack.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Better Test Planning:&lt;/strong&gt;&lt;br&gt;
You get a bird’s-eye view of what’s automated, what’s still manual, and what’s not covered at all. This makes sprint planning and regression suite grooming way easier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Smarter CI/CD Pipelines:&lt;/strong&gt;&lt;br&gt;
With test case IDs linked, you can selectively run only impacted tests based on what’s changed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Effortless Audits and Compliance:&lt;/strong&gt;&lt;br&gt;
In domains like finance or healthcare, traceability isn’t just a bonus, it’s a requirement. Having this sync helps you sleep better during audits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Team Collaboration Superpowers:&lt;/strong&gt;&lt;br&gt;
Your non-technical team members, like BAs or Product Owners can actually understand which scenarios are covered by automation without diving into code. That’s a win for everyone? ❤&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;So… How Do You Set This Up?&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Good news — you don’t have to manually update test case statuses or keep a spreadsheet of which test case is automated (ugh!). Most modern test management systems like &lt;strong&gt;TestRail&lt;/strong&gt;, &lt;strong&gt;Azure Test Plans&lt;/strong&gt;, and &lt;strong&gt;Xray&lt;/strong&gt; come with powerful APIs that let you do all this in a breeze!&lt;/p&gt;

&lt;p&gt;Let’s break it down:&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Test Rail&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;TestRail has a well-documented REST API that lets you do all the cool stuff — fetch test cases, post test results, update automation status, and more.&lt;/p&gt;

&lt;p&gt;Here’s a simple flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Include the test case ID in your test name or metadata (like [C1234]).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;After your tests run, parse the results and extract test case IDs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use the TestRail API to:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Mark test case as &lt;em&gt;automated&lt;/em&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Push the result of each test execution.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🔗 &lt;a href="https://www.gurock.com/testrail/docs/api/" rel="noopener noreferrer"&gt;TestRail API Documentation&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Azure Test Plans&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Azure DevOps (Test Plans) exposes a robust API under the TestManagement namespace. With it, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Search for test cases using queries or IDs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Update test case fields (e.g., &lt;em&gt;Automation Status&lt;/em&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create test runs and push results&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Link your automation to a test case using “Automated test name”&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s what a typical setup looks like:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Parse your test reports (JUnit or custom).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Match test case IDs in the name or tags.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Call Azure DevOps REST API to update:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;AutomationStatus to “Automated”&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AutomatedTestName to your test script name&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Push results into a test run&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🔗 &lt;a href="https://learn.microsoft.com/en-us/rest/api/azure/devops/test/?view=azure-devops-rest-7.2" rel="noopener noreferrer"&gt;Azure DevOps Test API Docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🔌 &lt;a href="https://www.gurock.com/testrail/integrations/" rel="noopener noreferrer"&gt;TestRail Plugins &amp;amp; Integrations&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Xray (Jira)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Xray works beautifully with REST APIs and even supports GraphQL. It’s great for syncing automation results directly into Jira.&lt;/p&gt;

&lt;p&gt;With Xray API, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Search and update test cases&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Submit results via the “Import Execution Results” endpoint&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Link tests to test executions automatically&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s the flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Add your test issue key (e.g., XRAY-123) in your test title or tag.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Generate a test result file (Xray supports JUnit, Cucumber, etc.).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use the API to:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Import the result&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a test execution issue&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Link the execution to your test plan&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🔗 &lt;a href="https://docs.getxray.app/display/XRAY/Import+Execution+Results+-+REST" rel="noopener noreferrer"&gt;Xray REST API Docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🧩 Xray also has official CI/CD plugins for Jenkins, GitHub Actions, and more!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Note:&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;I’m currently working on some&lt;/em&gt; &lt;strong&gt;&lt;em&gt;Cypress plugins&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;that makes this even easier, automatically parsing test scripts and updating the automation metadata in Azure Test Plans and creating Test Cases. Stay tuned if you’re into that kinda thing..&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Final Thoughts&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Test automation isn’t just about writing code and getting green check marks. It’s about creating visibility, trust, and confidence in the process.&lt;/p&gt;

&lt;p&gt;By linking your test scripts with your test management system, you’re not just making your life easier, you’re building a bridge that connects testers, developers, product owners, and stakeholders. That bridge is what enables real quality engineering!&lt;/p&gt;

&lt;p&gt;Have you already set this up in your team? Or struggling with how to start? Drop me a comment or connect with me on &lt;a href="https://www.linkedin.com/in/schathurangaj/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; — let’s chat!&lt;/p&gt;

&lt;p&gt;Until next time, Happy Testing!&lt;/p&gt;

</description>
      <category>testautomation</category>
      <category>testmanagement</category>
      <category>qualityassurance</category>
      <category>cypress</category>
    </item>
    <item>
      <title>Unlocking AI Superpowers in Test Automation: It All Starts with Your Framework</title>
      <dc:creator>S Chathuranga Jayasinghe</dc:creator>
      <pubDate>Sun, 11 May 2025 19:32:05 +0000</pubDate>
      <link>https://dev.to/s_chathuranga_j/unlocking-ai-superpowers-in-test-automation-it-all-starts-with-your-framework-4pad</link>
      <guid>https://dev.to/s_chathuranga_j/unlocking-ai-superpowers-in-test-automation-it-all-starts-with-your-framework-4pad</guid>
      <description>&lt;p&gt;Tried using AI agents to generate tests but ended up frustrated and ready to give up? Hold on — this might just change the game for you. Keep reading until the End!&lt;/p&gt;

&lt;p&gt;Let’s talk about something that’s been buzzing for a while now, &lt;strong&gt;AI in test automation&lt;/strong&gt;. Yep, it’s cool, powerful, and kind of magical when it works! But here’s the catch: most people assume AI is some all-knowing oracle that just writes perfect test scripts if you ask nicely.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Spoiler alert:&lt;/em&gt;&lt;/strong&gt; &lt;strong&gt;&lt;em&gt;It doesn’t‼️&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In my experience, the real magic happens &lt;em&gt;only&lt;/em&gt; when you pair AI with a &lt;strong&gt;&lt;em&gt;well-structured test automation framework&lt;/em&gt;&lt;/strong&gt; and a &lt;strong&gt;&lt;em&gt;proper prompt&lt;/em&gt;&lt;/strong&gt;. That’s when things start to click. So let’s unpack that a bit.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;The AI Isn’t the Sole Hero Here&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;I’ve played around with AI agents like ChatGPT, GitHub Copilot, JetBrains Junie and a few custom LLM setups to help generate tests. And I’ll be blunt: the output varies wildly depending on ‘from-where’ you prompt. Which means the environment where you are in. Which means in this case, specifically, &lt;strong&gt;your test framework&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Let me explain..&lt;/p&gt;

&lt;p&gt;If your test automation framework is a chaotic mess with test files dumped all over the place, no clear naming convention, or structure — AI has no idea what to do with that. &lt;strong&gt;It’ll guess&lt;/strong&gt;. &lt;strong&gt;And most of the time, it guesses wrong.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But when the framework follows a clear architecture, like the Page Object Model (POM), or even better, a layered design with base test classes, reusable commands, and folder separation for different test types — AI can start making sense of it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;It’s kind of like giving the AI a map. Without it, it’s lost in the woods. With it? It can run a marathon blindfolded.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Share the Blueprint: Feed the Architecture to AI&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Here’s something that works like a charm..&lt;/p&gt;

&lt;p&gt;Before asking AI to generate any tests, I give it a &lt;em&gt;basic overview of my framework architecture&lt;/em&gt;. Not a 20-page documentation dump, but something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- /tests: test specs go here
- /pages: all page objects live here
- /utils: helper functions
- We use TypeScript + Cypress
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note: Above is just a hint on how to give the information&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Then I’ll follow up with a simple example test and page object file, so the AI can get the “pattern.”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After this little orientation tour, the AI becomes surprisingly accurate.&lt;/strong&gt; It knows where to place new tests, how to reference page objects, how hooks work, and even when to reuse utility functions.&lt;/p&gt;

&lt;p&gt;It’s like onboarding a new teammate. You don’t just throw them into the deep end, you show them around first.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;The Prompt Is Your Steering Wheel&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Okay, so now your framework is neat and structured, and you’ve handed over the blueprint. What’s next?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Prompting.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This part is half art, half science. If you say “Write a login test,” you’ll probably get something generic that may or may not fit your actual setup.&lt;/p&gt;

&lt;p&gt;But if you say something like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Write a Cypress test in TypeScript to verify the login flow using the Page Object Model. Use the LoginPage class from /pages/LoginPage.ts. The test should check for successful login and redirection to the dashboard.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Boom! Now you’re talking! ❤&lt;/p&gt;

&lt;p&gt;Add test data examples, expected behaviors, or edge cases into your prompt, and AI will often generate tests that are eerily spot-on!&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Real-World Result? AI Writes, You Refine&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;When the framework is well-organized and you guide the AI properly, the tests it generates aren’t just dummy scripts. They’re often 80–90% production-ready.&lt;/p&gt;

&lt;p&gt;You still need to polish a few edges — naming, assertion tweaks, maybe a bit of test data handling — but the base is there. And that’s a massive time-saver.&lt;/p&gt;

&lt;p&gt;I’ve even had AI generate test cases for new features on the same day they were developed — saving hours of manual work and letting me focus on edge case validation and exploratory testing instead.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Make AI Work with You, Not for You!&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;So yeah, AI can be a great assistant in test automation, but only if you do your part first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Keep your test framework clean, modular, and structured&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Share your framework structure with the AI agent&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Write prompts that are clear, detailed, and contextual&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The AI isn’t your silver bullet. But with the right setup, it’s a serious productivity booster!&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Gift for the Finishers!&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;If you’ve made it this far, &lt;strong&gt;thank you!&lt;/strong&gt; As a little treat, I’ve got something for you.&lt;/p&gt;

&lt;p&gt;I recently worked on a Cypress boilerplate framework tailored for beginners and teams who want a clean, scalable starting point. It follows the Page Object Model, is structured for scalability &amp;amp; maintainability. And this will play well with AI agents when generating tests.&lt;/p&gt;

&lt;p&gt;I’ve bundled it all up and published it as an &lt;strong&gt;npm package&lt;/strong&gt; so you can kickstart your next project with ease.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Grab it here:&lt;/strong&gt; &lt;a href="https://www.npmjs.com/package/cypress-bootstrap" rel="noopener noreferrer"&gt;&lt;strong&gt;cypress-bootstrap&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Give it a spin, and let me know what you think!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>testautomation</category>
      <category>cypress</category>
      <category>promptengineering</category>
    </item>
    <item>
      <title>Why JetBrains Junie is the Best AI Agent I’ve Ever Used So Far</title>
      <dc:creator>S Chathuranga Jayasinghe</dc:creator>
      <pubDate>Sat, 03 May 2025 14:58:28 +0000</pubDate>
      <link>https://dev.to/s_chathuranga_j/why-jetbrains-junie-is-the-best-ai-agent-ive-ever-used-so-far-4nlg</link>
      <guid>https://dev.to/s_chathuranga_j/why-jetbrains-junie-is-the-best-ai-agent-ive-ever-used-so-far-4nlg</guid>
      <description>&lt;p&gt;I’ve been there using lot of different tools with AI Agents: Trae IDE Builder, Cursor, JetBrains Junie.. And this is my opinion on them :)&lt;/p&gt;

&lt;h1&gt;
  
  
  What is Junie?
&lt;/h1&gt;

&lt;p&gt;Junie isn’t just another AI assistant that suggests code snippets. Launched in January 2025 and now production-ready, Junie represents a leap from reactive code suggestions to proactive task execution. Unlike traditional tools like GitHub Copilot, which focus on autocompletion, Junie acts as a collaborative agent capable of planning, executing, and debugging multi-step workflows — saving hours of manual effort! It’s an autonomous coding agent that can take on entire tasks, from understanding your project structure to writing code, running tests, and even refactoring, all within your IDE.&lt;/p&gt;

&lt;p&gt;As a tester who’s tested numerous AI tools, I found Junie’s ability to handle tasks like generating CRUD operations, writing tests, and even self-correcting code to be unparalleled. Let’s dive into what makes Junie a game-changer.&lt;/p&gt;




&lt;h1&gt;
  
  
  Junie’s Core Features: Why It Stands Out
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Autonomous Task Execution
&lt;/h2&gt;

&lt;p&gt;Junie doesn’t just suggest code — it executes tasks end-to-end. For example, when asked to “Implement CRUD operations for a Spring Boot app with Thymeleaf UI”. Junie:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Analyzes the project structure and dependencies.&lt;/li&gt;
&lt;li&gt;Generates JPA entities, repositories, and controllers.&lt;/li&gt;
&lt;li&gt;Writes integration tests and runs them automatically.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;To do all the above, Junie first creates a descriptive plan to approach all the thing that needs to be done. It also documents &amp;amp; creates check-list of everything. Then start working on them one by one!&lt;/p&gt;

&lt;p&gt;And most importantly, writes tests and run the tests after implementation. That’s not all, if the tests fail it keeps on trying to fix the application code recursively until all the tests are green!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Context-Aware Problem Solving
&lt;/h2&gt;

&lt;p&gt;Junie leverages deep IDE integration to understand project-specific context, including coding guidelines and file structures. Developers can customize its behavior using a .junie/guidelines.md file, ensuring alignment with team standards (e.g., test framework preferences or directory conventions)&lt;/p&gt;

&lt;h2&gt;
  
  
  Human-in-the-Loop Control
&lt;/h2&gt;

&lt;p&gt;While Junie operates autonomously with the “Brave mode”, still it maintains transparency:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Propose a step-by-step plan for tasks.&lt;/li&gt;
&lt;li&gt;Require explicit approval for risky actions&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Performance Metrics
&lt;/h2&gt;

&lt;p&gt;According to the &lt;strong&gt;SWEBench Verified benchmark (500+ tasks), Junie solves 53.6% of tasks on the first attempt&lt;/strong&gt;, a promising figure for early-stage AI agents.&lt;/p&gt;




&lt;h1&gt;
  
  
  Real-World Use Cases
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Case 1: Building a Spring Boot Web App
&lt;/h2&gt;

&lt;p&gt;In a tutorial for IntelliJ IDEA, Junie:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Created a Spring Boot project with Maven and Java 21.&lt;/li&gt;
&lt;li&gt;Generated a Bookmark JPA entity and repository.&lt;/li&gt;
&lt;li&gt;Implemented Thymeleaf templates for CRUD operations.&lt;/li&gt;
&lt;li&gt;Wrote and ran integration tests — all within 30 minutes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Case 2: Self-Correcting Code Errors
&lt;/h2&gt;

&lt;p&gt;During a Django app test, Junie detected a failed compilation, identified missing dependencies, and updated requirements.txt without user intervention. This “Brave Mode” feature (optional) showcases its ability to recover from errors autonomously.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;Let me start by saying, I’m a big fan of JetBrains IDEs (seriously, who isn’t? ❤). Sure, they come with a price tag, but the developer experience is just unmatched. Now with Junie baked right into the JetBrains ecosystem, things get even better. It goes beyond basic suggestions, Junie handles autonomous tasks inside your IDE so you can spend less time on repetitive coding and more time solving the fun, complex problems.&lt;/p&gt;

&lt;p&gt;If you’re aiming to level up your productivity and smooth out your development flow, give Junie a spin, it’s absolutely worth it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Just one piece of advice&lt;/strong&gt;: as amazing as AI agents are, &lt;strong&gt;they’re still not perfect&lt;/strong&gt;. So &lt;strong&gt;always review&lt;/strong&gt; what’s generated, treat them like a junior dev with superpowers, not a replacement for your judgment.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ai</category>
      <category>softwareengineering</category>
      <category>softwaredevelopment</category>
      <category>agents</category>
    </item>
    <item>
      <title>How AI is Transforming Software Testing: From Manual to Mindful</title>
      <dc:creator>S Chathuranga Jayasinghe</dc:creator>
      <pubDate>Sun, 27 Apr 2025 06:05:14 +0000</pubDate>
      <link>https://dev.to/s_chathuranga_j/how-ai-is-transforming-software-testing-from-manual-to-mindful-1p49</link>
      <guid>https://dev.to/s_chathuranga_j/how-ai-is-transforming-software-testing-from-manual-to-mindful-1p49</guid>
      <description>&lt;p&gt;👋 It’s time to speak-up about the evolution in Software Testing..&lt;/p&gt;

&lt;p&gt;Let’s have a little chat about a revolution that’s been sneaking up on u quietly at first, but now it’s banging on the door with a megaphone. Yes, I’m talking about how Artificial Intelligence is changing the game in software testing.&lt;/p&gt;

&lt;p&gt;We’ve come a long way from the days when testing was all about clicking through UI elements manually and filling in endless Excel sheets with “Pass” and “Fail” tags. Now, with AI joining the party, we’re not just automating testing, we’re making it mindful. Let me explain what I mean.&lt;/p&gt;

&lt;h1&gt;
  
  
  From Click Monkeys to Smart Testers
&lt;/h1&gt;

&lt;p&gt;Remember the term “click monkey”? That was the (unfair) label for testers stuck in repetitive manual testing loops. No shame — we’ve all been there. Writing out hundreds of test cases in test management tools, then executing them step-by-step, release after release. It was exhausting, and frankly, soul-crushing.&lt;/p&gt;

&lt;p&gt;Then came automation tools — Selenium, Cypress, Playwright, you name it. Automation brought speed, reliability, and repeatability. But here’s the catch: it still required humans to write and maintain scripts. The minute your UI changed, your tests broke. And fixing them? Often harder than writing new ones.&lt;/p&gt;

&lt;p&gt;That’s where AI comes in.&lt;br&gt;
AI in Testing: What It Really Means&lt;/p&gt;

&lt;p&gt;When we talk about AI in testing, we’re not just talking about some fancy buzzwords slapped onto a product. Real AI in testing does three core things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Analyzes patterns&lt;/li&gt;
&lt;li&gt;Predicts failures&lt;/li&gt;
&lt;li&gt;Learns and adapts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s break this down with real-world examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Self-Healing Tests: Tests That Fix Themselves
&lt;/h2&gt;

&lt;p&gt;Let’s say you’ve got an automated UI test that clicks a button with the selector #submit-btn. Now imagine your dev team updates the app and that button becomes #submit-now. Boom! your test fails. Traditionally, you’d have to dig into logs, inspect the app, update the selector, and re-run the test.&lt;/p&gt;

&lt;p&gt;But with AI-powered self-healing, the script can detect that #submit-now is most likely the updated version of #submit-btn, thanks to context clues like button text, location, or surrounding elements. It adapts and continues execution — without human intervention. Some frameworks (like Testim, Mabl, Functionize, or even plugins for Selenium and Cypress) already do this.&lt;/p&gt;

&lt;p&gt;That’s not just automation. That’s automation with intuition. Or at least the AI version of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Predictive Test Selection: Only Run What Matters
&lt;/h2&gt;

&lt;p&gt;In CI/CD pipelines, we often run the entire test suite regardless of what’s changed. That’s like taking your whole car to the mechanic when all you did was swap out the air freshener.&lt;/p&gt;

&lt;p&gt;AI can analyze code changes, map them to relevant tests, and run only the impacted test cases. This technique called Predictive Test Selection or Test Impact Analysis which saves tons of time. Google does this. Facebook does this. And thanks to open-source tools and smart test orchestration platforms (hello, Launchable and GitHub’s ML-powered test selection), we can do it too.&lt;/p&gt;

&lt;p&gt;Less noise! Faster pipelines! Happier Devs! ❤&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Flaky Test Detection: AI is the Therapist for Your Tests
&lt;/h2&gt;

&lt;p&gt;We’ve all been haunted by flaky tests. They fail randomly, pass when re-run, and break our trust in automation. AI tools are now analyzing test behavior over time to detect flaky patterns, things like:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Test passes/fails under similar conditions
Network or timing dependencies
Resource bottlenecks (like memory or CPU spikes)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Once identified, AI can either auto-quarantine the test or suggest fixes. Think of it as an early warning system that tells you, “Hey buddy, test number 54 is emotionally unstable. Might wanna talk to it.”&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Natural Language Test Generation: Write Tests Like You Talk
&lt;/h2&gt;

&lt;p&gt;Now this one is mind-blowing. Tools like Testim, Katalon, and even ChatGPT-based platforms allow testers to write test cases in plain English, like:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;“Log in as admin, go to settings, and verify that dark mode is enabled.”
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then hola! it gets converted into actual automated scripts. Some tools even let you upload product documentation or user stories, and they auto-generate test cases. Yup, AI is now your junior test analyst.&lt;/p&gt;

&lt;p&gt;It’s not perfect (yet), but it’s evolving fast and already super handy for fast prototyping or non-technical testers.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Visual AI Testing: Seeing is Believing
&lt;/h2&gt;

&lt;p&gt;Traditional visual testing compares pixel by pixel. That means even a 1px shift could fail your test. But AI-powered visual testing (like Applitools Eyes) uses machine learning to understand layouts, context, and intent. It can tell the difference between meaningful changes (like a missing button) and ignorable ones (like a minor padding change).&lt;/p&gt;

&lt;p&gt;It’s like giving your tests a pair of smart glasses. They see what really matters.&lt;/p&gt;

&lt;h1&gt;
  
  
  So, What Happens to Manual Testers?
&lt;/h1&gt;

&lt;p&gt;Now, if you’re wondering whether all this means the end of manual testing — don’t panic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI is not replacing testers. It’s replacing repetition.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Manual testers are becoming exploratory thinkers, scenario designers, risk analysts, and yes, AI supervisors. We’ll be guiding the tools, validating the insights, and ensuring quality from a human perspective. That’s what I meant when I said testing is becoming more mindful.&lt;/p&gt;

&lt;h1&gt;
  
  
  My Take: AI + Testers = Super-powered Quality 🚀
&lt;/h1&gt;

&lt;p&gt;I’ve seen first-hand how AI can reduce noise in pipelines, catch issues earlier, and give teams confidence in their releases. Whether you’re a startup or an enterprise, AI-backed testing can be your best ally when used smartly.&lt;/p&gt;

&lt;p&gt;But it’s not plug-and-play magic. You still need a solid foundation, good test design, CI/CD discipline, and team collaboration. AI simply amplifies that.&lt;/p&gt;

&lt;p&gt;So next time you’re setting up your test strategy, ask not just what you’re testing, but how intelligently you’re doing it.&lt;/p&gt;

&lt;h1&gt;
  
  
  Wrapping Up
&lt;/h1&gt;

&lt;p&gt;AI in testing is here to stay and it’s only getting smarter. From self-healing to smart test selection, we’re moving toward a world where testing isn’t just faster, but smarter.&lt;/p&gt;

&lt;p&gt;We’re not just shifting left. We’re shifting up, towards intelligent, mindful quality!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>testing</category>
      <category>softwareengineering</category>
      <category>softwaretesting</category>
    </item>
    <item>
      <title>AI Code Reviewers: Can They Replace Your Teammates?</title>
      <dc:creator>S Chathuranga Jayasinghe</dc:creator>
      <pubDate>Mon, 21 Apr 2025 06:17:07 +0000</pubDate>
      <link>https://dev.to/s_chathuranga_j/ai-code-reviewers-can-they-replace-your-teammates-48f</link>
      <guid>https://dev.to/s_chathuranga_j/ai-code-reviewers-can-they-replace-your-teammates-48f</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Let’s be honest.&lt;/strong&gt; We’ve all been there waiting for a teammate to finally review our PR while we nervously hover over the refresh button like it’s a game of Flappy Bird. Sometimes, it takes hours. Sometimes, days. And sometimes… it just gets merged without any feedback at all.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now, imagine this: you open a PR, and within seconds, BOOM! — you get insightful, contextual code review suggestions. Not from your colleague who’s deep in meetings. But from an AI. Yup, GitHub Copilot Code Review is doing just that.&lt;/p&gt;




&lt;h2&gt;
  
  
  So, What Is GitHub Copilot Code Review?
&lt;/h2&gt;

&lt;p&gt;It’s an AI-powered feature baked into GitHub that reviews your code when you open a pull request. It reads your changes, understands the context (pretty impressively, I might add), and drops inline suggestions and comments. It even tells you why it suggests something, like a real reviewer.&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%2Fe7xufw7g658ok8ms6ddx.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%2Fe7xufw7g658ok8ms6ddx.png" alt="Copilot reviewer in the GitHub PR" width="644" height="216"&gt;&lt;/a&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%2Fwfod74fyht5hammbryyo.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%2Fwfod74fyht5hammbryyo.png" alt="Code review summary from Copilot" width="800" height="222"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  It’s Not Just About Catching Bugs
&lt;/h2&gt;

&lt;p&gt;Here’s what I found surprisingly useful. Copilot doesn’t just act like a nitpicky teammate. It actually helps you GROW.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For instance:&lt;br&gt;
I recently learned about the .closest() method in Cypress thanks to a Copilot Code Review suggestion. I had used cy.get(locator).find() chaining many times where I had to locate an element which is in a haystack without a unique identifier, but never realized I could chain it with .closest() to navigate up the DOM tree before finding something else.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you too didn’t know about that, here is a simple example:&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%2Fq4xxlg01foswraxsyt1p.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%2Fq4xxlg01foswraxsyt1p.png" alt="HTML Code" width="800" height="585"&gt;&lt;/a&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%2F2ib3k687fvmkr4guxs0b.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%2F2ib3k687fvmkr4guxs0b.png" alt="Cypress Code" width="800" height="233"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Back to the topic! It was a small tip, but one I hadn’t come across before and it genuinely improved how I write selectors..&lt;/p&gt;

&lt;p&gt;It actually helps you &lt;strong&gt;grow&lt;/strong&gt;. I've had it point out performance improvements, suggest better naming conventions, and even bring up edge cases I hadn't thought about.&lt;/p&gt;

&lt;p&gt;You know that feeling when a teammate shares a cool new way of doing something and you’re like, &lt;strong&gt;“Oh! I didn’t know that!”&lt;/strong&gt;. Copilot delivers that vibe, minus the Slack ping.&lt;/p&gt;

&lt;h2&gt;
  
  
  Apply Suggestions Directly from the PR Page
&lt;/h2&gt;

&lt;p&gt;One of my favorite things? You can apply its suggestions right from the PR interface. No jumping between tools. Just click, commit, and go. It’s a smooth workflow booster. And yes, you can also edit the suggestions before applying them if you want to tweak them your way.&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%2Ffsqd2dkn2olyr0his1dt.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%2Ffsqd2dkn2olyr0his1dt.png" alt="Suggested code changes by Copilot" width="800" height="330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Can It Really Replace Teammates?
&lt;/h2&gt;

&lt;p&gt;Okay, so let’s get to the spicy part.&lt;/p&gt;

&lt;p&gt;Can it replace your teammates? Short answer: &lt;strong&gt;No!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Long answer: AI code reviewers are incredibly helpful for catching low-hanging fruit, providing consistency, and teaching you a few tricks. But they still can’t replace the full context your teammates bring. Product knowledge, architecture decisions, design trade-offs, that’s still human territory (for now!).&lt;/p&gt;

&lt;p&gt;That said, AI reviewers can augment your team. They can take care of the boring stuff, speed up the review loop, and leave humans to focus on the deep stuff. And in that sense, &lt;strong&gt;it’s not a replacement, it’s a force multiplier.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What About Other AI Code Review Tools?
&lt;/h2&gt;

&lt;p&gt;While GitHub Copilot Code Review is a standout tool, it’s not the only one in the game. Tools like &lt;strong&gt;CodiumAI, AWS CodeWhisperer, DeepCode (now Snyk Code), Tabnine, and ReviewPad&lt;/strong&gt; are also helping developers get AI-driven insights on their code. Each of them brings something unique to the table, whether it’s test generation, security-focused reviews, or custom rule-based suggestions.&lt;/p&gt;

&lt;p&gt;That said, this article is all about Copilot’s review feature, because honestly, it’s super tightly integrated into GitHub and feels natural in everyday workflows.&lt;/p&gt;




&lt;h2&gt;
  
  
  In Short
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;GitHub Copilot Code Review is like a supercharged teammate that never sleeps.&lt;/li&gt;
&lt;li&gt;It gives smart, contextual suggestions and helps you learn better coding practices.&lt;/li&gt;
&lt;li&gt;You can apply changes directly from the PR interface.&lt;/li&gt;
&lt;li&gt;It won’t replace your team, but it sure can boost your productivity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re not already using it, give it a shot. Worst case? You’ll learn something new. Best case? You’ll wonder how you lived without it ❤&lt;/p&gt;

</description>
      <category>ai</category>
      <category>codereviewers</category>
      <category>softwareengineering</category>
      <category>github</category>
    </item>
    <item>
      <title>Level Up Your Cypress Game: Cypress Smart Tests Plugin</title>
      <dc:creator>S Chathuranga Jayasinghe</dc:creator>
      <pubDate>Mon, 31 Mar 2025 05:00:00 +0000</pubDate>
      <link>https://dev.to/s_chathuranga_j/level-up-your-cypress-game-cypress-smart-tests-plugin-4i8o</link>
      <guid>https://dev.to/s_chathuranga_j/level-up-your-cypress-game-cypress-smart-tests-plugin-4i8o</guid>
      <description>&lt;p&gt;A smart solution for dependant tests, conditional test execution on runtime and individual test-bound hooks and many more..&lt;/p&gt;

&lt;p&gt;In the realm of test automation, ensuring that end-to-end tests are both efficient and maintainable is a continuous challenge. While Cypress has revolutionized the way we approach testing, there are scenarios where we wish our tests could be more… SMART! &lt;/p&gt;

&lt;h1&gt;
  
  
  Introducing the Cypress Smart Tests plugin
&lt;/h1&gt;

&lt;p&gt;The plugin &lt;a href="https://www.npmjs.com/package/cypress-smart-tests" rel="noopener noreferrer"&gt;cypress-smart-tests&lt;/a&gt; is designed to elevate your test execution by introducing advanced control mechanisms directly into your Cypress tests. This plugin will solve common pain points in E2E test orchestration by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Letting you define dependencies between tests&lt;/li&gt;
&lt;li&gt;Running tests conditionally based on runtime logic&lt;/li&gt;
&lt;li&gt;Enabling &lt;code&gt;before/after&lt;/code&gt; hooks per test block&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Okie dokie! Let’s explore how to install, configure, and use it effectively..&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup and Configure
&lt;/h2&gt;

&lt;p&gt;First things first, initiate a project and install Cypress. It does not matter whether it is JavaScript or TypeScript. Then install the plugin as a dev dependency: &lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm install — save-dev cypress-smart-tests&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Whenever you are writing a Spec file, import the helpers to your file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { cytest, defineTestDependencies, configure, cyVariables } from 'cypress-smart-tests';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, instead of using the usual &lt;code&gt;it()&lt;/code&gt; block, use &lt;code&gt;cytest()&lt;/code&gt; — a drop-in replacement with smart capabilities ❤&lt;/p&gt;




&lt;h1&gt;
  
  
  Features
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Conditional Test Execution
&lt;/h2&gt;

&lt;p&gt;If you’ve been in the test automation realm for sometime, you already know that you will come across lots of scenarios where you have to execute your test based on various conditions. Either based on environment variables/feature or runtime conditions. Some of these scenarios could be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;test only on mobile viewports&lt;/li&gt;
&lt;li&gt;run only if a flag is enabled&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It doesn’t matter whatever the condition is, you can define your condition with the cytest() block and execute your test based on the conditions met. Check this out:&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%2F5ivtq19hxau4hy8mbhc9.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%2F5ivtq19hxau4hy8mbhc9.png" alt="Image description" width="800" height="486"&gt;&lt;/a&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%2Fw0wpljpemruu9rzzw9yl.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%2Fw0wpljpemruu9rzzw9yl.png" alt="Image description" width="800" height="513"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Problem solved! 😉 Let’s move on to the next pain-point..&lt;/p&gt;




&lt;h2&gt;
  
  
  Dependant Tests
&lt;/h2&gt;

&lt;p&gt;Well well well.. I know! The &lt;strong&gt;“Best practice is to avoid dependant tests!”&lt;/strong&gt;, BUT we all know that sometimes — at least rarely we get to situation where we have to write dependant tests. So, this plugin is gonna solve that problem too. &lt;/p&gt;

&lt;p&gt;First you have to define your test dependencies like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9yid5a94lsye3aavod0c.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%2F9yid5a94lsye3aavod0c.png" alt="Image description" width="800" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hold on! Let’s consider kind of a complex scenario. We have 6 tests in our Spec file: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Test2 &amp;amp; Test3 depends on Test 1&lt;/li&gt;
&lt;li&gt;Test5 depends only on Test4&lt;/li&gt;
&lt;li&gt;Test6 is an independant test&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Still you can achieve this with the plugin like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmhrep99zd8khwm2hthot.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%2Fmhrep99zd8khwm2hthot.png" alt="Image description" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When test execution happens, it behaves this way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If &lt;code&gt;Test1&lt;/code&gt; fails: Execution of the tests Test2 &amp;amp; Test3 will be skipped! &lt;/li&gt;
&lt;li&gt;If &lt;code&gt;Test4&lt;/code&gt; fails: Execution of the test Test5 will be skipped!&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Test6&lt;/code&gt; will be executed no matter what happens with the other tests&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What do we achieve through this?&lt;/strong&gt; You might be thinking so &lt;strong&gt;WHAT?&lt;/strong&gt; This makes a big difference, if you have dependant tests like this and you execute the tests in the traditional way, &lt;strong&gt;you will waste a lot of time!&lt;/strong&gt; WHY? Because when the parent test fails, the dependant tests will obviously fail because the pre-condition of those are not met! Yet the dependant tests execute (with all the Cypress retries and default waiting times) and waste the time. &lt;strong&gt;Time is Gold!&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So it’s important to fail-fast or skip-fast ❤ Here is a silly sample code with dependencies and tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { cytest, defineTestDependencies, configure, resetState } from 'cypress-smart-tests';

describe('Cypress Smart Tests Plugin - Dependencies', () =&amp;gt; {
    beforeEach(() =&amp;gt; {
        resetState();
    });

    context('Simple Dependent Test Execution', () =&amp;gt; {
        beforeEach(() =&amp;gt; {
            // Configure failFast mode
            configure({ failFast: true });

            // Define dependencies
            defineTestDependencies({
                'Critical Test': ['Subsequent Test 1', 'Subsequent Test 2'],
            });
        });

        cytest('Critical Test', () =&amp;gt; {
            // This test will fail
            cy.wrap(false).should('be.true');
        });

        cytest('Subsequent Test 1', () =&amp;gt; {
            cy.log('This test should be skipped in failFast mode');
            cy.wrap(true).should('be.true');
        });

        cytest('Subsequent Test 2', () =&amp;gt; {
            cy.log('This test should also be skipped in failFast mode');
            cy.wrap(true).should('be.true');
        });

        cytest('Subsequent Test 3', () =&amp;gt; {
            cy.log('This test should not be skipped in failFast mode');
            cy.wrap(true).should('be.true');
        });
    });

    context('Complex Dependent Test Execution', () =&amp;gt; {
        beforeEach(() =&amp;gt; {
            // Define dependencies
            defineTestDependencies({
                'Critical Test': ['Subsequent Test 1', 'Subsequent Test 2'],
                'Other Critical Test': ['Subsequent Test 4'],
            });
        });

        cytest('Critical Test', () =&amp;gt; {
            // This test will fail
            cy.wrap(false).should('be.true');
        });

        cytest('Subsequent Test 1', () =&amp;gt; {
            cy.log('This test should be skipped in failFast mode');
            cy.wrap(true).should('be.true');
        });

        cytest('Subsequent Test 2', () =&amp;gt; {
            cy.log('This test should also be skipped in failFast mode');
            cy.wrap(true).should('be.true');
        });

        cytest('Subsequent Test 3', () =&amp;gt; {
            cy.log('This test should not be skipped in failFast mode');
            cy.wrap(true).should('be.true');
        });

        cytest('Other Critical Test', () =&amp;gt; {
            // This test will fail
            cy.wrap(false).should('be.true');
        });

        cytest('Subsequent Test 4', () =&amp;gt; {
            cy.log('This test should be skipped in failFast mode');
            cy.wrap(true).should('be.true');
        });
        cytest('Subsequent Test 6', () =&amp;gt; {
            cy.log('This test should not be skipped in failFast mode');
            cy.wrap(true).should('be.true');
        });
    });
});

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

&lt;/div&gt;






&lt;h2&gt;
  
  
  Individual Test Hooks
&lt;/h2&gt;

&lt;p&gt;While global hooks are a staple in testing frameworks including Cypress, there are instances where individual tests require specific setup or cleanup operations. The Smart Tests Plugin allows you to define &lt;strong&gt;custom hooks for individual tests while having the global hooks!&lt;/strong&gt; Yes, you read it right!&lt;/p&gt;

&lt;p&gt;I’ll take a simple scenario and provide you the code for it. Let’s assume If you’re seeding a test DB for a specific test case:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzdri4dnyp5u116q1n9pu.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%2Fzdri4dnyp5u116q1n9pu.png" alt="Image description" width="800" height="407"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Persistent Variables
&lt;/h2&gt;

&lt;p&gt;Speaking of variables, it’s a very common scenario we want to share variable values between tests. But by default they reset after the execution of the &lt;code&gt;it()&lt;/code&gt; block. The &lt;code&gt;cypress-smart-tests&lt;/code&gt; plugin provides you with a solution to define and persist variables across tests (not across Spec files). You can store and retrieve test variables across &lt;code&gt;cytest()&lt;/code&gt; blocks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftv222rjt1wa4wuctfkfm.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%2Ftv222rjt1wa4wuctfkfm.png" alt="Image description" width="800" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is a full code snippet where variables are defined and used across tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { cytest, cyVariable, cyVariables, resetState } from 'cypress-smart-tests';

describe('Cypress Smart Tests Plugin - Persistent Variables', () =&amp;gt; {
    beforeEach(() =&amp;gt; {
        resetState();
    });

    context('Basic Variable Usage', () =&amp;gt; {
        // Set up a variable before tests
        before(() =&amp;gt; {
            cyVariable('testVar', 'initial value');
        });

        cytest('Set and get a variable', () =&amp;gt; {
            // Set a variable
            cyVariable('username', 'testuser');

            // Get the variable
            const username = cyVariable('username');

            // Verify the variable was set correctly
            expect(username).to.equal('testuser');
        });

        cytest('Variable persists across tests', () =&amp;gt; {
            // Get the variable set in the previous test
            const username = cyVariable('username');

            // Verify the variable still has the same value
            expect(username).to.equal('testuser');

            // Update the variable
            cyVariable('username', 'updateduser');

            // Verify the update worked
            expect(cyVariable('username')).to.equal('updateduser');
        });

        cytest('Variables can store different types', () =&amp;gt; {
            // Store a number
            cyVariable('count', 42);
            expect(cyVariable('count')).to.equal(42);

            // Store an object
            const user = { id: 1, name: 'Test User', active: true };
            cyVariable('user', user);
            expect(cyVariable('user')).to.deep.equal(user);

            // Store an array
            const items = ['item1', 'item2', 'item3'];
            cyVariable('items', items);
            expect(cyVariable('items')).to.deep.equal(items);

            // Store a boolean
            cyVariable('isActive', true);
            expect(cyVariable('isActive')).to.be.true;
        });

        cytest('Variable set in before hook is available', () =&amp;gt; {
            // Get the variable set in the before hook
            const testVar = cyVariable('testVar');

            // Verify the variable has the expected value
            expect(testVar).to.equal('initial value');
        });
    });

    context('Multiple Variables Management', () =&amp;gt; {
        before(() =&amp;gt; {
            // Reset variables to ensure a clean state
            resetState(true);
        });

        cytest('Add and get variables', () =&amp;gt; {
            // Add variables
            cyVariables().add('username', 'testuser');
            cyVariables().add('userId', 123);
            cyVariables().add('userPreferences', { theme: 'dark', language: 'en' });

            // Get variables
            const username = cyVariables().get('username');
            const userId = cyVariables().get('userId');
            const userPreferences = cyVariables().get('userPreferences');

            // Verify variables were set correctly
            expect(username).to.equal('testuser');
            expect(userId).to.equal(123);
            expect(userPreferences).to.deep.equal({ theme: 'dark', language: 'en' });
        });

        cytest('Check if variables exist', () =&amp;gt; {
            // Check existing variables
            expect(cyVariables().has('username')).to.be.true;
            expect(cyVariables().has('userId')).to.be.true;
            expect(cyVariables().has('userPreferences')).to.be.true;

            // Check non-existing variable
            expect(cyVariables().has('nonExistingVar')).to.be.false;
        });

        cytest('Get all variables', () =&amp;gt; {
            // Get all variables
            const allVariables = cyVariables().getAll();

            // Verify all variables are returned
            expect(allVariables).to.deep.equal({
                username: 'testuser',
                userId: 123,
                userPreferences: { theme: 'dark', language: 'en' }
            });
        });

        cytest('Remove a variable', () =&amp;gt; {
            // Remove a variable
            cyVariables().remove('username');

            // Verify the variable was removed
            expect(cyVariables().has('username')).to.be.false;

            // Other variables should still exist
            expect(cyVariables().has('userId')).to.be.true;
            expect(cyVariables().has('userPreferences')).to.be.true;
        });

        cytest('Clear all variables', () =&amp;gt; {
            // Clear all variables
            cyVariables().clear();

            // Verify all variables were cleared
            expect(cyVariables().getAll()).to.deep.equal({});
            expect(cyVariables().has('userId')).to.be.false;
            expect(cyVariables().has('userPreferences')).to.be.false;
        });
    });

    context('Variables with resetState', () =&amp;gt; {
        before(() =&amp;gt; {
            // Set up some variables
            cyVariable('testVar1', 'value1');
            cyVariable('testVar2', 'value2');
        });

        cytest('Variables persist after normal resetState', () =&amp;gt; {
            // Reset state without resetting variables
            resetState();

            // Variables should still exist
            expect(cyVariable('testVar1')).to.equal('value1');
            expect(cyVariable('testVar2')).to.equal('value2');
        });

        cytest('Variables are cleared with resetState(true)', () =&amp;gt; {
            // Reset state and variables
            resetState(true);

            // Variables should be cleared
            expect(cyVariable('testVar1')).to.be.undefined;
            expect(cyVariable('testVar2')).to.be.undefined;
        });
    });

    context('Practical Examples', () =&amp;gt; {
        before(() =&amp;gt; {
            // Reset variables to ensure a clean state
            resetState(true);
        });

        cytest('Store user credentials for reuse', () =&amp;gt; {
            // Store user credentials
            cyVariables().add('credentials', {
                username: 'testuser',
                password: 'password123'
            });

            // Verify credentials were stored
            const credentials = cyVariables().get('credentials');
            expect(credentials.username).to.equal('testuser');
            expect(credentials.password).to.equal('password123');

            // Simulate login
            cy.log(`Logging in with username: ${credentials.username}`);
        });

        cytest('Use stored credentials in another test', () =&amp;gt; {
            // Get credentials from previous test
            const credentials = cyVariables().get('credentials');

            // Verify credentials are still available
            expect(credentials).to.not.be.undefined;
            expect(credentials.username).to.equal('testuser');

            // Simulate using credentials
            cy.log(`Using stored credentials for user: ${credentials.username}`);
        });

        cytest('Store and update test data', () =&amp;gt; {
            // Store initial test data
            cyVariable('testData', { id: 1, status: 'pending' });

            // Simulate updating the data
            const testData = cyVariable('testData');
            testData.status = 'completed';
            cyVariable('testData', testData);

            // Verify the update
            expect(cyVariable('testData').status).to.equal('completed');
        });
    });
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Keep in mind, that the variables are &lt;strong&gt;not persistent across Spec files&lt;/strong&gt;. If you want variables that persist across Spec files, I highly recommend using the plugin ‘&lt;a href="https://www.npmjs.com/package/cypress-plugin-store" rel="noopener noreferrer"&gt;cypress-plugin-store&lt;/a&gt;’ by Lasitha Wijenayake!&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;The cypress-smart-testsplugin is a simple plugin I developed in my free-time ❤ This empowers you to write more smart and efficient tests by introducing mechanisms for defining dependencies, conditional execution, and custom hooks. By integrating this plugin into your Cypress test suite, you can enhance the maintainability and effectiveness of your end-to-end tests, ensuring that they remain robust and relevant as your application evolves.&lt;/p&gt;

&lt;p&gt;Here is a full-demo code: &lt;a href="https://github.com/s-chathuranga-j/cypress-smart-tests-demo-code" rel="noopener noreferrer"&gt;https://github.com/s-chathuranga-j/cypress-smart-tests-demo-code&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let me know how it works for you and feel free to contribute or suggest improvements!&lt;/p&gt;

&lt;p&gt;GitHub repo: &lt;a href="https://github.com/s-chathuranga-j/cypress-smart-tests" rel="noopener noreferrer"&gt;https://github.com/s-chathuranga-j/cypress-smart-tests&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cypress</category>
      <category>testautomation</category>
      <category>softwaretesting</category>
      <category>qualityassurance</category>
    </item>
    <item>
      <title>Zero to Hero: Email Automation with Cypress &amp; Mailosaur</title>
      <dc:creator>S Chathuranga Jayasinghe</dc:creator>
      <pubDate>Sat, 29 Mar 2025 13:25:43 +0000</pubDate>
      <link>https://dev.to/cypress/zero-to-hero-email-automation-with-cypress-mailosaur-20ok</link>
      <guid>https://dev.to/cypress/zero-to-hero-email-automation-with-cypress-mailosaur-20ok</guid>
      <description>&lt;p&gt;One of the trickiest parts of end-to-end testing has always been &lt;strong&gt;email verification&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Whether it’s account sign-ups, password resets, or email-based authentication flows, we often find ourselves writing hacky solutions or hitting a dead end altogether.&lt;/p&gt;

&lt;p&gt;That’s where &lt;a href="https://mailosaur.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;Mailosaur&lt;/strong&gt;&lt;/a&gt; comes in! An awesome tool that helps test emails (and even SMS) in an automated way. What’s even better? There’s an official &lt;a href="https://github.com/mailosaur/cypress-mailosaur" rel="noopener noreferrer"&gt;&lt;strong&gt;Cypress&lt;/strong&gt; &lt;strong&gt;plugin&lt;/strong&gt;&lt;/a&gt; that makes email testing a breeze for us, the Cypress fans.&lt;/p&gt;

&lt;p&gt;In this article, let’s dive into how you can &lt;strong&gt;seamlessly integrate Cypress and Mailosaur&lt;/strong&gt; to validate email flows like a pro ❤&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;The Setup&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Before we jump into code, let’s make sure the basics are covered.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Install the plugin:&lt;br&gt;&lt;br&gt;
Run this command to install the package in your Cypress project&lt;br&gt;&lt;br&gt;
&lt;code&gt;npm install — save-dev cypress-mailosaur&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configure Cypress support: In your &lt;strong&gt;cypress/support/e2e.ts&lt;/strong&gt; or &lt;strong&gt;e2e.js&lt;/strong&gt;, add:&lt;br&gt;&lt;br&gt;
&lt;code&gt;import ‘cypress-mailosaur’;&lt;/code&gt;This adds custom commands to your Cypress chain like &lt;code&gt;cy.mailosaurGetMessage()&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set your Mailosaur API key:&lt;br&gt;&lt;br&gt;
You can either set the API key in the Cypress environment configuration (&lt;strong&gt;cypress.config.ts)&lt;/strong&gt; like this:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&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%2Fojqcq1tw66yo07xcfhax.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%2Fojqcq1tw66yo07xcfhax.png" alt="Image description" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or, if you’re keeping secrets out of code (highly recommended), pass them via environment variables when running Cypress.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Real-World Test: Email Verification Flow&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Let’s say you have a sign-up form that sends a verification email with a link. Here’s how you can test it with Mailosaur:&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%2Fm0cnksekvt4ctp36ecn2.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%2Fm0cnksekvt4ctp36ecn2.png" alt="Image description" width="800" height="822"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip!&lt;/strong&gt; You can validate plain text content or even attachments if your use case demands it. Mailosaur’s API gives you access to email bodies (email.text.body, email.html.body) and much more. ❤&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%2Fpxbp82erznvg8j2pgzw3.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%2Fpxbp82erznvg8j2pgzw3.png" alt="Image description" width="800" height="1280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Email Deliverability Validation&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Mailosaur has some more &lt;strong&gt;Magic!&lt;/strong&gt; The &lt;code&gt;mailosaurGetSpamAnalysis()&lt;/code&gt; command runs a &lt;strong&gt;SpamAssassin&lt;/strong&gt; test against an email, which scores the email based on some rules. The lower the score the better and, in most cases, a score higher then &lt;strong&gt;5.0&lt;/strong&gt; will result in an email being seen as spam. The command will also return a list of rules that were triggered by the email. These rules are the same as those used by SpamAssassin itself and are based on a set of criteria that are common in spam emails. You can read more &lt;a href="https://mailosaur.com/blog/understanding-and-improving-your-spamassassin-score" rel="noopener noreferrer"&gt;here&lt;/a&gt;. Here’s how you can test it:&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%2Fq8dh836atxkiplcnc7e3.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%2Fq8dh836atxkiplcnc7e3.png" alt="Image description" width="800" height="735"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Retry Logic Built In&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;No need to manually add cy.wait() or retry loops. cy.mailosaurGetMessage() &lt;strong&gt;automatically waits and polls&lt;/strong&gt; for the email, up to the timeout you define (default is 10 seconds). You can customize this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr92eblh5uz0b7qdqwzp4.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%2Fr92eblh5uz0b7qdqwzp4.png" alt="Image description" width="800" height="410"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is incredibly useful when your email service takes a few extra seconds to deliver.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Verifying OTPs and Tokens&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;For apps sending OTPs or verification codes, you can extract values directly:&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%2F0qjotrmzs5h5zb2wi8i6.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%2F0qjotrmzs5h5zb2wi8i6.png" alt="Image description" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Easy peasy 🍋! ❤&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Bonus: Testing Edge Cases&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Want to test that an email &lt;strong&gt;is not&lt;/strong&gt; sent? You can use &lt;code&gt;cy.mailosaurGetMessage()&lt;/code&gt; with a very short timeout and assert a failure:&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%2Fiinecuc4ya6rrztz4pwt.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%2Fiinecuc4ya6rrztz4pwt.png" alt="Image description" width="800" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Keep in mind this will cause the test to fail if the email is received!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Cleanup&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Emails in Mailosaur are ephemeral, but if you want to delete them manually after each test:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6zop8m6coe7j533lbg0x.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%2F6zop8m6coe7j533lbg0x.png" alt="Image description" width="800" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Useful when your test suite runs in parallel or reuses the same inbox across tests.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Final Thoughts&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;I’ve used this setup in multiple real-world projects — especially when testing sign-up flows, password resets, and account recovery. It saves time, avoids flaky manual inbox checkers, and integrates cleanly with Cypress.&lt;/p&gt;

&lt;p&gt;If you’re building quality into your apps with Cypress, &lt;strong&gt;don’t skip email verification.&lt;/strong&gt; It’s an important part of the user journey — and now, testing it is super simple thanks to Mailosaur.&lt;/p&gt;

&lt;p&gt;Happy Testing!&lt;/p&gt;

</description>
      <category>cypress</category>
      <category>emailtesting</category>
      <category>testautomation</category>
      <category>emaildeliverability</category>
    </item>
  </channel>
</rss>
